首页 > 解决方案 > Three.js:将对象投影到垂直于相机的平面上

问题描述

我在三中有一个简单的场景,有一些飞机。现在点击飞机移动到随机位置。

单击后,我想将平面移动到垂直于相机的新网格中,以便投影网格的 x 和 y 轴与屏幕的 x 和 y 轴平行。

这是场景:

// generate a scene object
var scene = new THREE.Scene();
scene.background = new THREE.Color(0xffffff);

// generate a camera
var aspectRatio = window.innerWidth / window.innerHeight;
var camera = new THREE.PerspectiveCamera(75, aspectRatio, 0.01, 10000);

// position the camera
camera.position.set(51.389, -451.056, 839.455);
var rotObjectMatrix = new THREE.Matrix4();
var q = {_x: 0.0184, _y: -0.2122, _z: 0.9770, _w: -0.0081};
rotObjectMatrix.makeRotationFromQuaternion(q);
camera.quaternion.setFromRotationMatrix(rotObjectMatrix);
camera.up.set(0.00806, -0.91008, -0.41432);

// generate a renderer
var renderer = new THREE.WebGLRenderer({antialias: true});
renderer.setPixelRatio(window.devicePixelRatio); // <3 retina
renderer.setSize(window.innerWidth, window.innerHeight); // canvas size
document.body.appendChild(renderer.domElement);

// generate controls
var controls = new THREE.TrackballControls(camera, renderer.domElement);
controls.target.set(19.053, -111.316, 93.996);

// generate some lights
var ambientLight = new THREE.AmbientLight(0xeeeeee);
scene.add(ambientLight);

// render loop
function render() {
  requestAnimationFrame(render);
  renderer.render(scene, camera);
  controls.update();
};

// draw a grid
var grid = new THREE.GridHelper(2000, 20, 0x000000, 0x000000);
grid.material.opacity = 0.2;
grid.material.transparent = true;
grid.rotation.x = -Math.PI;
scene.add(grid);

// draw some
planes = [];
for (var i=0; i<2**10; i++) {
  var geometry = new THREE.PlaneGeometry( 20, 20, 32 );
  var material = new THREE.MeshBasicMaterial(
    {color: 0xff0000, side: THREE.DoubleSide} );
  var plane = new THREE.Mesh( geometry, material );
  var x = ((i % 2**5) * 40) - (2**5 * 40)/2;
  var z = (Math.floor(i/2**5) * 40) - (2**5 * 40)/2;
  plane.position.set(x, 0, z);
  scene.add(plane);
  planes.push(plane);
}

// transition the planes on body click
document.querySelector('body').addEventListener('click', function() {
  planes.forEach(function(plane) {
    // placeholder
    plane.position.set(
      Math.random() * 500 - (Math.random() * 500)/2,
      0,
      Math.random() * 500 - (Math.random() * 500)/2,
    )
  })
})

render();
html, body { width: 100%; height: 100%; background: #000; }
body { margin: 0; overflow: hidden; }
canvas { width: 100%; height: 100%; }
<script src='https://cdnjs.cloudflare.com/ajax/libs/three.js/97/three.min.js'></script>
<script src='https://threejs.org/examples/js/controls/TrackballControls.js'></script>

使用以下方法很接近,但不会倾斜平面以使它们垂直于相机:

planes.forEach(function(plane) {
  // close to projection discussed above...
  plane.position.set(
    plane.position.x,
    plane.position.z,
    0,
  )
})

有谁知道如何实现上述投影?其他人可以提供的任何建议将不胜感激!

标签: javascriptmaththree.jsgeometry

解决方案


这是一个捷径,但它可以帮助您避免大量的数学计算:只需将您的飞机组合成一个Group,然后使用Group.lookAt(camera.position)将它们全部指向相机。

// generate a scene object
var scene = new THREE.Scene();
scene.background = new THREE.Color(0xffffff);

// generate a camera
var aspectRatio = window.innerWidth / window.innerHeight;
var camera = new THREE.PerspectiveCamera(75, aspectRatio, 0.01, 10000);

// position the camera
camera.position.set(51.389, -451.056, 839.455);
var rotObjectMatrix = new THREE.Matrix4();
var q = {_x: 0.0184, _y: -0.2122, _z: 0.9770, _w: -0.0081};
rotObjectMatrix.makeRotationFromQuaternion(q);
camera.quaternion.setFromRotationMatrix(rotObjectMatrix);

// Not sure why you're changing camera's up axis, but this will
// need to be duplicated on planeGroup;
camera.up.set(0.00806, -0.91008, -0.41432);

// generate a renderer
var renderer = new THREE.WebGLRenderer({antialias: true});
renderer.setPixelRatio(window.devicePixelRatio); // <3 retina
renderer.setSize(window.innerWidth, window.innerHeight); // canvas size
document.body.appendChild(renderer.domElement);

// generate controls
var controls = new THREE.TrackballControls(camera, renderer.domElement);

// If you change the pos where the camera is looking at,
// you'll need to place planeGroup at this position.
controls.target.set(19.053, -111.316, 93.996);

// generate some lights
var ambientLight = new THREE.AmbientLight(0xeeeeee);
scene.add(ambientLight);

// render loop
function render() {
  requestAnimationFrame(render);
  renderer.render(scene, camera);
  controls.update();
};

// draw a grid
var grid = new THREE.GridHelper(2000, 20, 0x000000, 0x000000);
grid.material.opacity = 0.2;
grid.material.transparent = true;
grid.rotation.x = -Math.PI;
scene.add(grid);

// draw some
planes = [];
var planeGroup = new THREE.Group();

for (var i=0; i<2**10; i++) {
  var geometry = new THREE.PlaneGeometry( 20, 20, 32 );
  var material = new THREE.MeshBasicMaterial(
    {color: 0xff0000, side: THREE.DoubleSide} );
  var plane = new THREE.Mesh( geometry, material );
  var x = ((i % 2**5) * 40) - (2**5 * 40)/2;
  var y = (Math.floor(i/2**5) * 40) - (2**5 * 40)/2;

  // Setting x,y instead of x,z
  plane.position.set(x, y, 0);
  planeGroup.add(plane);
  planes.push(plane);
}
scene.add(planeGroup);

// Copying camera.up and controls.target from above
planeGroup.up.copy(camera.up);
planeGroup.position.copy(controls.target);
planeGroup.lookAt(camera.position);

// transition the planes on body click
document.querySelector('body').addEventListener('click', function() {
  planes.forEach(function(plane) {
    // placeholder
    plane.position.set(
      Math.random() * 500 - (Math.random() * 500)/2,
      0,
      Math.random() * 500 - (Math.random() * 500)/2,
    )
  })
})

render();
html, body { width: 100%; height: 100%; background: #000; }
body { margin: 0; overflow: hidden; }
canvas { width: 100%; height: 100%; }
<script src='https://cdnjs.cloudflare.com/ajax/libs/three.js/97/three.min.js'></script>
<script src='https://threejs.org/examples/js/controls/TrackballControls.js'></script>

然后,如果你想随机化飞机的位置,你仍然可以这样做,但你只需要担心之后将它们恢复到原来的x,y,0位置,它们会自动排列“看着”相机。

编辑:我必须复制您所做的更改camera.upcontrols.target使其正常工作。如果您更改这 2 个属性,则需要将它们复制planeGroup到此方法才能工作。

这是 .lookAt() 方法的文档


推荐阅读