使用Three.js将物体射线投射以启用鼠标点击事件

3
我正在为数据库中的每个条目向场景添加对象。我已经让一个立方体出现在入口场景中,但是当我尝试添加点击对象的光线投射时,它不起作用,对象不会出现并且控制台显示“表达式不可用”。我从three.js网站获取了光线投射的代码部分,所以不确定我做错了什么。
以下是JS代码:
var renderer, scene, container, camera;
var geometry, material;
var controls, group;

var raycaster = new THREE.Raycaster();
var mouse = new THREE.Vector2();


init()

function onMouseMove( event ) {

    // calculate mouse position in normalized device coordinates
    // (-1 to +1) for both components

    mouse.x = ( event.clientX / window.innerWidth ) * 2 - 1;
    mouse.y = - ( event.clientY / window.innerHeight ) * 2 + 1;

}

function init() {
    // init renderer
    renderer = new THREE.WebGLRenderer( { antialias: true } );
    renderer.setPixelRatio( window.devicePixelRatio );
    renderer.setSize( window.innerWidth, window.innerHeight );
    // document.body.appendChild( renderer.domElement );

    container = document.getElementById('container');
    container.appendChild( renderer.domElement );

    // init scene
    scene = new THREE.Scene();
    scene.background = new THREE.Color( 0xffffff );

    group = new THREE.Group();
    scene.add( group )

    //fetch data from database and add object for each entry
    getData()
    async function getData() {
        var response = await fetch('/api/indexvr');
        var data = await response.json();
        console.log(data) 

        for (var i=0; i<data.length; i++) {
            cube = new THREE.Mesh( geometry, material );
            cube.position.x = i;
            scene.add(cube);
            //group.add(data)
        }
    }

    // init camera
    camera = new THREE.PerspectiveCamera( 50, window.innerWidth / window.innerHeight, 1, 1000 );
    camera.position.set( 15, 15, 15 ); //camera.position.set( 5, 0, 10 );
    camera.lookAt( scene.position );
    // controls = new OrbitControls( camera, renderer.domElement );
    // controls.enableRotate = true;
}

function render() {

    // update the picking ray with the camera and mouse position
    raycaster.setFromCamera( mouse, camera );

    // calculate objects intersecting the picking ray
    var intersects = raycaster.intersectObjects( scene.children );

    for ( var i = 0; i < intersects.length; i++ ) {

        intersects[ i ].object.material.color.set( 0xff0000 );

    }

    renderer.render( scene, camera );

}

window.addEventListener( 'mousemove', onMouseMove, false );

window.requestAnimationFrame(render);

HTML中只有一个名为 "container" 的div标签,以及这个标签:
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/108/three.js"></script>

它并没有产生任何错误,只是在控制台中显示了这个信息: enter image description here

所以它正在获取数据,但无法渲染场景。

var renderer, scene, container, camera;
var geometry, material;
var controls, group;

var raycaster = new THREE.Raycaster();
var mouse = new THREE.Vector2();


init()

function onMouseMove(event) {

  // calculate mouse position in normalized device coordinates
  // (-1 to +1) for both components

  mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
  mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;

}

function init() {
  // init renderer
  renderer = new THREE.WebGLRenderer({
    antialias: true
  });
  renderer.setPixelRatio(window.devicePixelRatio);
  renderer.setSize(window.innerWidth, window.innerHeight);
  // document.body.appendChild( renderer.domElement );

  container = document.getElementById('container');
  container.appendChild(renderer.domElement);

  // init scene
  scene = new THREE.Scene();
  scene.background = new THREE.Color(0xffffff);


  group = new THREE.Group();
  scene.add(group)

  //fetch data from database and add object for each entry
  getData()
  async function getData() {
    /**
     * @author TheJim01
     * Replacing DB call with fake data to make it work here.
     * Nancy: Please feel free to add appropriate data.
     */
    // var response = await fetch('/api/indexvr');
    // var data = await response.json();
    var data = [{}, {}, {}, {}, {}]
    console.log(data)

    for (var i = 0; i < data.length; i++) {
      cube = new THREE.Mesh(geometry, material);
      cube.position.x = i;
      scene.add(cube);
      //group.add(data)
    }
  }

  // init camera
  camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 1, 1000);
  camera.position.set(15, 15, 15); //camera.position.set( 5, 0, 10 );
  camera.lookAt(scene.position);
  // controls = new OrbitControls( camera, renderer.domElement );
  // controls.enableRotate = true;
}

function render() {

  // update the picking ray with the camera and mouse position
  raycaster.setFromCamera(mouse, camera);

  // calculate objects intersecting the picking ray
  var intersects = raycaster.intersectObjects(scene.children);

  for (var i = 0; i < intersects.length; i++) {

    intersects[i].object.material.color.set(0xff0000);

  }

  renderer.render(scene, camera);

}

window.addEventListener('mousemove', onMouseMove, false);

window.requestAnimationFrame(render);
<script src="//threejs.org/build/three.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/108/three.js"></script>
<script src="https://unpkg.com/spritejs/dist/spritejs.min.js"></script>

<div id="container"></div>


请发布完整的错误信息文本。 - TheJim01
实际上并没有错误信息,我已经添加了控制台消息的截图。 - Nancy Collins
当我像这样注释掉窗口方法时:// window.addEventListener( 'mousemove', onMouseMove, false ); // window.requestAnimationFrame(render);场景的背景被渲染出来,但方块没有,控制台输出完全相同的消息。 - Nancy Collins
1
当我运行代码片段时,我的浏览器没有显示“表达式:不可用”的消息。你的调试器可能启用了某种“watch”功能?另外,只需要一个<script>引用three.js - 目前指向cdnjs.cloudflare.com和threejs.org的文件实际上是相同的文件。我建议只使用来自cdnjs的那个,因为最终threejs.org文件将被最新版本替换。 - TheJim01
哦,好的,谢谢。很奇怪,这个消息只出现在我的浏览器上,我没有运行任何调试器。 - Nancy Collins
当您运行代码片段时,光线投射代码是否实际起作用? - Nancy Collins
2个回答

3
我看到有几个问题。无论是因为您省略了代码部分还是其他原因,我都不能确定。
首先,您提供的代码没有定义geometry或material。您暗示每个DB结果绘制一个立方体,因此我做出假设并使用BoxBufferGeometry。您还没有定义任何灯光,因此我将只使用MeshBasicMaterial,它不需要灯光。
一旦解决了这些问题,就好像您已经在设置一个render loop使用window.requestAnimationFrame,但您仍然只调用一个render,即使您的DB获取是异步的。换句话说,在您从DB获得响应之前,渲染可能会发生,因此您将看不到任何内容。我添加了一些样板代码来设置一个渲染循环,类似于three.js在其示例中所做的方式。
有趣的是,这就是所有需要的。raycaster开始工作,并且我能够console log结果。当场景开始呈现时,我确实得到了一些误报,但那是因为还没有鼠标输入,因此它是从屏幕中间(第一个立方体存在的地方)进行射线投射的。
通常,您不希望为每个帧进行射线投射,但我理解VR情况可能有所不同(讨厌烦躁的人类)。
最后,我所做的最后一个更改是为每个立方体提供自己的材料(原始材料的克隆)。这是必要的,以确保您可以针对每个立方体进行射线投射。

// Need to create geometry and material
var geometry = new THREE.BoxBufferGeometry(0.5, 0.5, 0.5);
var material = new THREE.MeshBasicMaterial({
  color: "green"
});

var renderer, scene, container, camera;
var controls, group;

var raycaster = new THREE.Raycaster();
var mouse = new THREE.Vector2();

init()

function onMouseMove(event) {

  // calculate mouse position in normalized device coordinates
  // (-1 to +1) for both components

  mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
  mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;

}

function init() {
  // init renderer
  renderer = new THREE.WebGLRenderer({
    antialias: true
  });
  renderer.setPixelRatio(window.devicePixelRatio);
  renderer.setSize(window.innerWidth, window.innerHeight);
  // document.body.appendChild( renderer.domElement );

  container = document.getElementById('container');
  container.appendChild(renderer.domElement);

  // init scene
  scene = new THREE.Scene();
  scene.background = new THREE.Color(0xffffff);


  group = new THREE.Group();
  scene.add(group)

  //fetch data from database and add object for each entry
  getData()
  async function getData() {
    /**
     * @author TheJim01
     * Replacing DB call with fake data to make it work here.
     * Nancy: Please feel free to add appropriate data.
     */
    // var response = await fetch('/api/indexvr');
    // var data = await response.json();
    var data = [{}, {}, {}, {}, {}]
    //console.log(data)

    for (var i = 0; i < data.length; i++) {
      cube = new THREE.Mesh(geometry, material.clone());
      cube.position.x = i;
      scene.add(cube);
      //group.add(data)
    }
  }

  // init camera
  camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 1, 1000);
  camera.position.set(15, 15, 15); //camera.position.set( 5, 0, 10 );
  camera.lookAt(scene.position);
  // controls = new OrbitControls( camera, renderer.domElement );
  // controls.enableRotate = true;
}

function render() {

  // update the picking ray with the camera and mouse position
  raycaster.setFromCamera(mouse, camera);

  // calculate objects intersecting the picking ray
  var intersects = raycaster.intersectObjects(scene.children);
  if (intersects.length > 0) {
    console.log(intersects);
  }

  for (var i = 0; i < intersects.length; i++) {

  intersects[i].object.material.color.set(0xff0000);
  

  }

  renderer.render(scene, camera);

}

window.addEventListener('mousemove', onMouseMove, false);

// Here's the bbasic render loop implementation
function animate() {
  requestAnimationFrame(animate);
  render();
}
animate();
html,
body {
  width: 100%;
  height: 100%;
  margin: 0;
  padding: 0;
  overflow: hidden;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/108/three.min.js"></script>

<div id="container"></div>


太棒了!非常感谢你的努力,TheJim01。我发现three.js相当棘手,所以非常感激你的帮助。 - Nancy Collins

1

请提供详细答案,然后附上参考资料。 - Sachith Muhandiram
1
你好,Alexander。欢迎来到 Stack Overflow。感谢您主动参加了导览。关于您的回答,在 three.js 项目中不需要使用任何类型的鼠标控制器,Nancy 已经注释掉了引用 OrbitControls 的代码行。因此,包含 OrbitControls.js 文件是不必要的。 - TheJim01

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接