有这么一个需求,屏幕上有两个魔方 A 和 B,然后用THREE.TrackballControls 来控制场景视角和方位,魔方 A 处于屏幕中央,较大,魔方B处于屏幕右上角,较小,当用户旋转视角时,魔方 A 会相对于摄像机旋转,而魔方 B 不会做相对于摄像机的旋转,也就是说,我们可以通过改变视角,看到A的所有六个面,但是始终看不到 B 背对着相机的三个面
B 始终相对于摄像机静止,当我们拉近镜头时,A变大,B的大小不变,当镜头移动时,A 会相对于镜头做反方向移动,而B静止不动。
实现这个需求有好几种方法,一种思路是,把add 到 camera 上 而不是 scene上,
https://stackoverflow.com/questions/31831425/static-object-in-scene-three-js
scene.add( camera ); // required when the camera has a child
camera.add( object );
object.position.set( 0, 0, - 100 ); // or whatever distance you want
另一种思路,在渲染循环中,获取camera的position和rotation,然后调整“静态”物体的位置和旋转方向
https://github.com/mrdoob/three.js/issues/641
object.position.copy( camera.position );
object.rotation.copy( camera.rotation );
object.updateMatrix();
object.translateZ( - 10 );
另一种思路,使用THREE.Vector3.project() 和 THREE.Vector3.unproject() , 原理就是把三维坐标投射的二维屏幕的坐标,然后记录下这个屏幕坐标,每当相机的方位改变时,使用unproject()方法,把屏幕坐标转换成新的相机空间的三维坐标
let geom = new THREE.Geometry();
let p1 = new THREE.Vector3(-145, 95, 20), p2 = new THREE.Vector3(-115, 95, 20), p3 = new THREE.Vector3(-125, 55, 10);
geom.vertices = [p1, p2, p3];
geom.faces = [new THREE.Face3(0, 1, 2)];
geom.computeFaceNormals();
var customMesh = new THREE.Mesh(geom, new THREE.MeshLambertMaterial({color: 0x00cc00, side: THREE.DoubleSide}));
scene.add(customMesh);
var pp1, pp2, pp3;
//below code should run in render loop, but better run only after camera's position or rotation change
if (!pp1) {
pp1 = p1.clone().project(camera);
pp2 = p2.clone().project(camera);
pp3 = p3.clone().project(camera);
} else {
let q1 = pp1.clone().unproject(camera);
let q2 = pp2.clone().unproject(camera);
let q3 = pp3.clone().unproject(camera);
updateVertices([q1, q2, q3], customMesh.geometry.vertices);
customMesh.geometry.verticesNeedUpdate = true;
customMesh.geometry.computeFaceNormals();
}
又另一思路,修改TrackballControls.js ,修改其中的rotateCamera 方法
this.qua = new THREE.Quaternion();
this.rotateCamera = (function(){ }
return function () {
var angle = Math.acos( _rotateStart.dot( _rotateEnd ) / _rotateStart.length() / _rotateEnd.length() );
...
...
...
this.qua = quaternion;
}
}
然后监听TrackballControls的事件
var planeGeometry = new THREE.PlaneGeometry(30, 30);
var planeMaterial = new THREE.MeshLambertMaterial({color: 0xffffff, side: THREE.DoubleSide});
var plane = new THREE.Mesh(planeGeometry, planeMaterial);
plane.receiveShadow = true;
trackballControls.addEventListener("change", function ( ) {
let ab = projected.clone().unproject(camera);
plane.position.copy(ab);
let m = new THREE.Matrix4();
m.makeRotationFromQuaternion(trackballControls.qua);
plane.applyMatrix(m);
// plane.setRotationFromQuaternion(trackballControls.qua);
} );
完整代码, InvestigateStaticMesh.html
<!DOCTYPE html>
<html>
<head>
<title>Investigate mesh be static in camera </title>
<script type="text/javascript" src="../libs/three.js"></script>
<script type="text/javascript" src="../libs/stats.js"></script>
<script type="text/javascript" src="../libs/dat.gui.js"></script>
<script type="text/javascript" src="../libs/TrackballControls.js"></script>
<style>
body {
/* set margin to 0 and overflow to hidden, to go fullscreen */
margin: 0;
overflow: hidden;
}
</style>
</head>
<body>
<div id="Stats-output">
</div>
<!-- Div which will hold the Output -->
<div id="WebGL-output">
</div>
<!-- Javascript code that runs our Three.js examples -->
<script type="text/javascript">
// once everything is loaded, we run our Three.js stuff.
function init() {
var clock = new THREE.Clock();
var stats = initStats();
// create a scene, that will hold all our elements such as objects, cameras and lights.
var scene = new THREE.Scene();
// create a camera, which defines where we're looking at.
var camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000);
// create a render and set the size
var webGLRenderer = new THREE.WebGLRenderer();
webGLRenderer.setClearColor(new THREE.Color(0x000, 1.0));
webGLRenderer.setSize(window.innerWidth, window.innerHeight);
webGLRenderer.shadowMapEnabled = true;
// position and point the camera to the center of the scene
camera.position.x = 100;
camera.position.y = 100;
camera.position.z = 300;
camera.lookAt(new THREE.Vector3(0, 0, 0));
var trackballControls = new THREE.TrackballControls(camera);
trackballControls.rotateSpeed = 1.0;
trackballControls.zoomSpeed = 1.0;
trackballControls.panSpeed = 1.0;
// trackballControls.noZoom=false;
// trackballControls.noPan=false;
trackballControls.staticMoving = true;
// trackballControls.dynamicDampingFactor=0.3;
// trackballControls.addEventListener( 'end', function ( event ) {
// alert( event.type );
// } );
// trackballControls.addEventListener("change", function ( ) {
// let ab = projected.clone().unproject(camera);
// plane.position.copy(ab);
// let m = new THREE.Matrix4();
// m.makeRotationFromQuaternion(trackballControls.qua);
// plane.applyMatrix(m);
// // plane.setRotationFromQuaternion(trackballControls.qua);
// } );
var ambientLight = new THREE.AmbientLight(0x383838);
scene.add(ambientLight);
// add spotlight for the shadows
var spotLight = new THREE.SpotLight(0xffffff);
spotLight.position.set(300, 300, 300);
spotLight.intensity = 1;
scene.add(spotLight);
// add the output of the renderer to the html element
document.getElementById("WebGL-output").appendChild(webGLRenderer.domElement);
// call the render function
let geom = new THREE.Geometry();
let p1 = new THREE.Vector3(-145, 95, 20), p2 = new THREE.Vector3(-115, 95, 20), p3 = new THREE.Vector3(-125, 55, 10);
geom.vertices = [p1, p2, p3];
geom.faces = [new THREE.Face3(0, 1, 2)];
geom.computeFaceNormals();
var customMesh = new THREE.Mesh(geom, new THREE.MeshLambertMaterial({color: 0x00cc00, side: THREE.DoubleSide}));
scene.add(customMesh);
var planeGeometry = new THREE.PlaneGeometry(30, 30);
var planeMaterial = new THREE.MeshLambertMaterial({color: 0xffffff, side: THREE.DoubleSide});
var plane = new THREE.Mesh(planeGeometry, planeMaterial);
plane.receiveShadow = true;
// rotate and position the plane
plane.rotation.x = -0.1 * Math.PI;
var vector = new THREE.Vector3(-135, 85, 0);
plane.position.copy(vector);
// plane.position.x = vector.x;
// plane.position.y = vector.y;
// plane.position.z = vector.z;
// add the plane to the scene
scene.add(plane);
render();
var projected = null, matrix = new THREE.Matrix4();
matrix.extractRotation(plane.matrixWorld);
matrix.multiply(camera.projectionMatrix);
var pp1, pp2, pp3;
var k = 1;
function updateVertices(src, dst) {
for (var i = 0; i < src.length; i++) {
dst[i].set(src[i].x, src[i].y, src[i].z);
}
}
function render() {
stats.update();
var delta = clock.getDelta();
trackballControls.update(delta);
//https://github.com/mrdoob/three.js/issues/641
plane.position.copy( camera.position );
plane.rotation.copy( camera.rotation );
plane.updateMatrix();
plane.translateZ( -270 );
//webGLRenderer.clear();
// render using requestAnimationFrame
requestAnimationFrame(render);
webGLRenderer.render(scene, camera);
// if (!pp1) {
// pp1 = p1.clone().project(camera);
// pp2 = p2.clone().project(camera);
// pp3 = p3.clone().project(camera);
// } else {
// let q1 = pp1.clone().unproject(camera);
// let q2 = pp2.clone().unproject(camera);
// let q3 = pp3.clone().unproject(camera);
// updateVertices([q1, q2, q3], customMesh.geometry.vertices);
// customMesh.geometry.verticesNeedUpdate = true;
// customMesh.geometry.computeFaceNormals();
// }
if (!projected) {
projected = vector.clone().project(camera);
let k = 1;
} else {
// let m = new THREE.Matrix4();
// let mm = matrix.clone().multiply(m.getInverse( camera.projectionMatrix ));
// plane.setRotationFromMatrix(mm);
}
}
function initStats() {
var stats = new Stats();
stats.setMode(0); // 0: fps, 1: ms
// Align top-left
stats.domElement.style.position = 'absolute';
stats.domElement.style.left = '0px';
stats.domElement.style.top = '0px';
document.getElementById("Stats-output").appendChild(stats.domElement);
return stats;
}
}
window.onload = init;
</script>
</body>
</html>
https://github.com/mrdoob/three.js/tree/master/examples/js/controls
https://software.intel.com/en-us/articles/simulating-cloth-for-3d-games