首页 > 解决方案 > Three.js 使用正交相机将 2D 映射到 3D

问题描述

我有一个使用正交相机的 three.js 场景

this.camera = new THREE.OrthographicCamera(this.width / - 2, this.width / 2, this.height / 2, this.height / - 2, 1, 1000);
this.camera.position.z = this.width / 2;
this.scene.add(this.camera);

然后我有一个立方体(红色),我想将它定位在一些 2D 坐标(绿色)上。我目前正在使用此功能将 2D 坐标转换为 3D 坐标。

from2Dto3D( position2d ) {
    var vector = new THREE.Vector3(),
        camera = this.camera,
        x = (position2d.x / this.width) * 2 - 1,
        y = (position2d.y / this.height) * 2 + 1;
    vector.set(x, y, 0.5);
    vector.unproject(this.camera);
    var direction = vector.sub(this.camera.position).normalize(),
        distance = - this.camera.position.z / direction.z,
        scaled = direction.multiplyScalar(distance),
        coords = this.camera.position.clone().add(scaled);
    return new THREE.Vector3(coords.x, coords.y, 0);
  }

...

let position3d = this.from2Dto3D(position2d);

...

this.cube.position.x = position3d.x; // updating only x position for the moment

如何将立方体准确定位在 2D 位置?

在此处输入图像描述

标签: javascriptthree.js3d

解决方案


您需要显示更多代码。我们怎么知道问题不在于您的鼠标位置计算?

无论相机方向如何,只要没有对画布应用 css 变换,这里的代码都应该可以工作。

      canvas.addEventListener('mousemove', (e) => {
        const rect = canvas.getBoundingClientRect();
        // get a canvas content pixel position
        const {width, height} = renderer.domElement;
        const canvasX = (e.clientX - rect.left) * width / rect.width;
        const canvasY = (e.clientY - rect.top) * height / rect.height;

        const clipX = (canvasX / width)  *  2 - 1;
        const clipY = (canvasY / height) * -2 + 1;

        // get the object's clip space Z
        const clipPos = new THREE.Vector3();
        mesh.getWorldPosition(clipPos);
        clipPos.project(camera);

        const pos = new THREE.Vector3(clipX, clipY, clipPos.z);
        pos.unproject(camera);
        mesh.position.copy(pos);
        render();
      });

function main() {
  const canvas = document.querySelector('#c');
  const renderer = new THREE.WebGLRenderer({canvas});

  const size = 5;
  const near = 5;
  const far = 50;
  const camera = new THREE.OrthographicCamera(-size, size, size, -size, near, far);
  camera.position.set(5, 7, 12);
  camera.lookAt(0, 0, 0);

  const scene = new THREE.Scene();
  scene.background = new THREE.Color('white');
  
  const gridHelper = new THREE.GridHelper(10, 10);
  scene.add(gridHelper);

  const cubeSize = 1;
  const cubeGeo = new THREE.BoxBufferGeometry(cubeSize, cubeSize, cubeSize);
  const cubeMat = new THREE.MeshBasicMaterial({color: 'red'});
  const mesh = new THREE.Mesh(cubeGeo, cubeMat);
  scene.add(mesh);

  function resizeRendererToDisplaySize(renderer) {
    const canvas = renderer.domElement;
    const width = canvas.clientWidth;
    const height = canvas.clientHeight;
    const needResize = canvas.width !== width || canvas.height !== height;
    if (needResize) {
      renderer.setSize(width, height, false);
    }
    return needResize;
  }

  function render() {
    if (resizeRendererToDisplaySize(renderer)) {
      const aspect = renderer.domElement.clientWidth / renderer.domElement.clientHeight;
      camera.left  = -size * aspect;
      camera.right =  size * aspect;
      camera.updateProjectionMatrix();
    }
    renderer.render(scene, camera);
  }
  render();
  
  canvas.addEventListener('mousemove', (e) => {
    const rect = canvas.getBoundingClientRect();
    // get a canvas content pixel position
    const {width, height} = renderer.domElement;
    const canvasX = (e.clientX - rect.left) * width / rect.width;
    const canvasY = (e.clientY - rect.top) * height / rect.height;
    
    const clipX = (canvasX / width)  *  2 - 1;
    const clipY = (canvasY / height) * -2 + 1;
        
    // get the object's clip space Z
    const clipPos = new THREE.Vector3();
    mesh.getWorldPosition(clipPos);
    clipPos.project(camera);

    // note this code moves the object in the plane
    // of the camera, not the plane of the grid
    const pos = new THREE.Vector3(clipX, clipY, clipPos.z);
    pos.unproject(camera);
    mesh.position.copy(pos);
    render();
  });
  
  window.addEventListener('resize', render);
}

main();
html, body {
  margin: 0;
  height: 100%;
}
#c {
  width: 100%;
  height: 100%;
  display: block;
}
<script src="https://threejsfundamentals.org/threejs/resources/threejs/r112/build/three.min.js"></script>
<canvas id="c"></canvas>

请注意,相同的代码也适用于PerspecitveCamera

function main() {
  const canvas = document.querySelector('#c');
  const renderer = new THREE.WebGLRenderer({canvas});

  const fov = 60;
  const near = 0.1;
  const far = 50;
  const camera = new THREE.PerspectiveCamera(fov, 1, near, far);
  camera.position.set(2, 5, 10);
  camera.lookAt(0, 0, 0);

  const scene = new THREE.Scene();
  scene.background = new THREE.Color('white');
  
  const gridHelper = new THREE.GridHelper(10, 10);
  scene.add(gridHelper);

  const cubeSize = 1;
  const cubeGeo = new THREE.BoxBufferGeometry(cubeSize, cubeSize, cubeSize);
  const cubeMat = new THREE.MeshBasicMaterial({color: 'red'});
  const mesh = new THREE.Mesh(cubeGeo, cubeMat);
  scene.add(mesh);

  function resizeRendererToDisplaySize(renderer) {
    const canvas = renderer.domElement;
    const width = canvas.clientWidth;
    const height = canvas.clientHeight;
    const needResize = canvas.width !== width || canvas.height !== height;
    if (needResize) {
      renderer.setSize(width, height, false);
    }
    return needResize;
  }

  function render() {
    if (resizeRendererToDisplaySize(renderer)) {
      camera.aspect = renderer.domElement.clientWidth / renderer.domElement.clientHeight;
      camera.updateProjectionMatrix();
    }
    renderer.render(scene, camera);
  }
  render();
  
  canvas.addEventListener('mousemove', (e) => {
    const rect = canvas.getBoundingClientRect();
    // get a canvas content pixel position
    const {width, height} = renderer.domElement;
    const canvasX = (e.clientX - rect.left) * width / rect.width;
    const canvasY = (e.clientY - rect.top) * height / rect.height;
    
    const clipX = (canvasX / width)  *  2 - 1;
    const clipY = (canvasY / height) * -2 + 1;
        
    // get the object's clip space Z
    const clipPos = new THREE.Vector3();
    mesh.getWorldPosition(clipPos);
    clipPos.project(camera);

    // note: this code moves the object in the plane
    // of the camera not the plane of the grid
    const pos = new THREE.Vector3(clipX, clipY, clipPos.z);
    pos.unproject(camera);
    mesh.position.copy(pos);
    render();
  });
  
  window.addEventListener('resize', render);
}

main();
html, body {
  margin: 0;
  height: 100%;
}
#c {
  width: 100%;
  height: 100%;
  display: block;
}
<script src="https://threejsfundamentals.org/threejs/resources/threejs/r112/build/three.min.js"></script>
<canvas id="c"></canvas>

如果你的画布是 css 转换然后看这个

请注意,如果您想在网格(或任意平面)上移动对象,那么一个简单的解决方案是将 RayCaster 用于不可见平面。

此外,如果您的对象在场景中有被平移、旋转或缩放的父对象,那么只需

    const parent = object.parent;
    scene.attach(object);

    // move the object as above

    parent.attach(object);

推荐阅读