简体   繁体   English

Three.js旋转球体,然后跟着镜头

[英]Three.js rotate a sphere that's followed by a camera

I'm working on a toy Three.js scene in which I want to follow a sphere with a camera [demo] . 我正在制作一个Three.js玩具场景,在该场景中,我想使用相机[demo]跟踪球体。 Right now, though, I can't figure out how to make the sphere "roll" without also rotating the camera. 但是,现在,我不知道如何在不旋转照相机的情况下使球体“滚动”。

Here's the code I use to update the sphere's position each frame: 这是我用来更新每帧球体位置的代码:

function moveSphere() {
  var delta = clock.getDelta(); // seconds
  var moveDistance = 200 * delta; // 200 pixels per second
  var rotateAngle = Math.PI / 2 * delta; // pi/2 radians (90 deg) per sec

  // move forwards/backwards/left/right
  if ( pressed['W'] ) {
    sphere.translateZ( -moveDistance );
  }
  if ( pressed['S'] ) 
    sphere.translateZ(  moveDistance );
  if ( pressed['Q'] )
    sphere.translateX( -moveDistance );
  if ( pressed['E'] )
    sphere.translateX(  moveDistance ); 

  // rotate left/right/up/down
  var rotation_matrix = new THREE.Matrix4().identity();
  if ( pressed['A'] )
    sphere.rotateOnAxis(new THREE.Vector3(0,1,0), rotateAngle);
  if ( pressed['D'] )
    sphere.rotateOnAxis(new THREE.Vector3(0,1,0), -rotateAngle);
  if ( pressed['R'] )
    sphere.rotateOnAxis(new THREE.Vector3(1,0,0), rotateAngle);
  if ( pressed['F'] )
    sphere.rotateOnAxis(new THREE.Vector3(1,0,0), -rotateAngle);
}

And the code to follow the sphere each tick of time: 以及每时每刻遵循该领域的代码:

function moveCamera() {
  var relativeCameraOffset = new THREE.Vector3(0,50,200);
  var cameraOffset = relativeCameraOffset.applyMatrix4(sphere.matrixWorld);
  camera.position.x = cameraOffset.x;
  camera.position.y = cameraOffset.y;
  camera.position.z = cameraOffset.z;
  camera.lookAt(sphere.position);
}

Is there an easy way to make the ball roll without making the camera spiral all over the place? 有没有一种简单的方法可以使球滚动而又不会使相机到处旋转? Inside of the if (pressed['W']) block, I tried various permutations of sphere.rotateOnAxis(new THREE.Vector3(0,0,1), rotateAngle); if (pressed['W'])块内部,我尝试了sphere.rotateOnAxis(new THREE.Vector3(0,0,1), rotateAngle);各种排列sphere.rotateOnAxis(new THREE.Vector3(0,0,1), rotateAngle); but haven't found a natural way to make the ball roll. 但还没有找到使球滚动的自然方法。 I would be very grateful for any advice others can offer on this! 我将非常感谢其他人对此提供的任何建议!

Your issue is this line: 您的问题是此行:

var cameraOffset = relativeCameraOffset.applyMatrix4(sphere.matrixWorld);

This takes the offset you specify and applies not only the sphere position, but it's rotation as well. 这将采用您指定的偏移量,不仅应用球体位置,而且还应用旋转 For example, if your sphere is rotated 180 degrees on the Y-axis, the resulting vector is (0, 50, -200) + (sphere position). 例如,如果您的球体在Y轴上旋转了180度,则结果向量为(0,50,-200)+(球体位置)。

You need to extract the translation component from the sphere matrix, and apply it to the offset. 您需要从球体矩阵中提取平移分量,并将其应用于偏移量。 The code below uses an intermediate matrix to store the position of the sphere. 下面的代码使用中间矩阵来存储球体的位置。

    /**
     * Follow the sphere
     **/
    var sphereTranslation = new THREE.Matrix4(); // only make it once to reduce overhead

    function moveCamera() {
        var relativeCameraOffset = new THREE.Vector3(0,50,200);
        sphereTranslation.copyPosition(sphere.matrixWorld); // get sphere position only
        var cameraOffset = relativeCameraOffset.applyMatrix4(sphereTranslation);
        camera.position.x = cameraOffset.x;
        camera.position.y = cameraOffset.y;
        camera.position.z = cameraOffset.z;
        camera.lookAt(sphere.position);
    }

The key here was to create a sphere, then add that sphere to a group, so that I could translate and rotate the group (which controls the ball's position) while also rotating the sphere inside the group (which allows the ball to "roll"). 这里的关键是创建一个球体,然后将该球体添加到组中,这样我就可以平移和旋转该组(控制球的位置),同时还旋转该组内的球体(使球“滚动”) )。 Separating these entities out allows the camera to follow the sphere group just as before while allowing the ball to rotate independently of the sphere group's movement [ updated demo ]: 将这些实体分开可以使照相机像以前一样跟随球体组,同时使球独立于球体组的移动而旋转[ 更新的演示 ]:

  /** * Generate a scene object with a background color **/ function getScene() { var scene = new THREE.Scene(); scene.background = new THREE.Color(0x111111); return scene; } /** * Generate the camera to be used in the scene. Camera args: * [0] field of view: identifies the portion of the scene * visible at any time (in degrees) * [1] aspect ratio: identifies the aspect ratio of the * scene in width/height * [2] near clipping plane: objects closer than the near * clipping plane are culled from the scene * [3] far clipping plane: objects farther than the far * clipping plane are culled from the scene **/ function getCamera() { var aspectRatio = window.innerWidth / window.innerHeight; var camera = new THREE.PerspectiveCamera(75, aspectRatio, 0.1, 10000); camera.position.set(0,150,400); camera.lookAt(scene.position); return camera; } /** * Generate the light to be used in the scene. Light args: * [0]: Hexadecimal color of the light * [1]: Numeric value of the light's strength/intensity * [2]: The distance from the light where the intensity is 0 * @param {obj} scene: the current scene object **/ function getLight(scene) { var lights = []; lights[0] = new THREE.PointLight( 0xffffff, 0.6, 0 ); lights[0].position.set( 100, 200, 100 ); scene.add( lights[0] ); var ambientLight = new THREE.AmbientLight(0x111111); scene.add(ambientLight); return light; } /** * Generate the renderer to be used in the scene **/ function getRenderer() { // Create the canvas with a renderer var renderer = new THREE.WebGLRenderer({antialias: true}); // Add support for retina displays renderer.setPixelRatio(window.devicePixelRatio); // Specify the size of the canvas renderer.setSize(window.innerWidth, window.innerHeight); // Add the canvas to the DOM document.body.appendChild(renderer.domElement); return renderer; } /** * Generate the controls to be used in the scene * @param {obj} camera: the three.js camera for the scene * @param {obj} renderer: the three.js renderer for the scene **/ function getControls(camera, renderer) { var controls = new THREE.TrackballControls(camera, renderer.domElement); controls.zoomSpeed = 0.4; controls.panSpeed = 0.4; return controls; } /** * Get grass **/ function getPlane(scene, loader) { var texture = loader.load('grass.jpg'); texture.wrapS = texture.wrapT = THREE.RepeatWrapping; texture.repeat.set( 10, 10 ); var material = new THREE.MeshBasicMaterial({ map: texture, side: THREE.DoubleSide }); var geometry = new THREE.PlaneGeometry(1000, 1000, 10, 10); var plane = new THREE.Mesh(geometry, material); plane.position.y = -0.5; plane.rotation.x = Math.PI / 2; scene.add(plane); return plane; } /** * Add background **/ function getBackground(scene, loader) { var imagePrefix = 'sky-parts/'; var directions = ['right', 'left', 'top', 'bottom', 'front', 'back']; var imageSuffix = '.bmp'; var geometry = new THREE.BoxGeometry( 1000, 1000, 1000 ); var materialArray = []; for (var i = 0; i < 6; i++) materialArray.push( new THREE.MeshBasicMaterial({ map: loader.load(imagePrefix + directions[i] + imageSuffix), side: THREE.BackSide })); var sky = new THREE.Mesh( geometry, materialArray ); scene.add(sky); } /** * Add a character **/ function getSphere(scene) { var geometry = new THREE.SphereGeometry( 30, 12, 9 ); var material = new THREE.MeshPhongMaterial({ color: 0xd0901d, emissive: 0xaf752a, side: THREE.DoubleSide, flatShading: true }); var sphere = new THREE.Mesh( geometry, material ); // create a group for translations and rotations var sphereGroup = new THREE.Group(); sphereGroup.add(sphere) sphereGroup.position.set(0, 24, 100); scene.add(sphereGroup); return [sphere, sphereGroup]; } /** * Store all currently pressed keys **/ function addListeners() { window.addEventListener('keydown', function(e) { pressed[e.key.toUpperCase()] = true; }) window.addEventListener('keyup', function(e) { pressed[e.key.toUpperCase()] = false; }) } /** * Update the sphere's position **/ function moveSphere() { var delta = clock.getDelta(); // seconds var moveDistance = 200 * delta; // 200 pixels per second var rotateAngle = Math.PI / 2 * delta; // pi/2 radians (90 deg) per sec // move forwards/backwards/left/right if ( pressed['W'] ) { sphere.rotateOnAxis(new THREE.Vector3(1,0,0), -rotateAngle) sphereGroup.translateZ( -moveDistance ); } if ( pressed['S'] ) sphereGroup.translateZ( moveDistance ); if ( pressed['Q'] ) sphereGroup.translateX( -moveDistance ); if ( pressed['E'] ) sphereGroup.translateX( moveDistance ); // rotate left/right/up/down var rotation_matrix = new THREE.Matrix4().identity(); if ( pressed['A'] ) sphereGroup.rotateOnAxis(new THREE.Vector3(0,1,0), rotateAngle); if ( pressed['D'] ) sphereGroup.rotateOnAxis(new THREE.Vector3(0,1,0), -rotateAngle); if ( pressed['R'] ) sphereGroup.rotateOnAxis(new THREE.Vector3(1,0,0), rotateAngle); if ( pressed['F'] ) sphereGroup.rotateOnAxis(new THREE.Vector3(1,0,0), -rotateAngle); } /** * Follow the sphere **/ function moveCamera() { var relativeCameraOffset = new THREE.Vector3(0,50,200); var cameraOffset = relativeCameraOffset.applyMatrix4(sphereGroup.matrixWorld); camera.position.x = cameraOffset.x; camera.position.y = cameraOffset.y; camera.position.z = cameraOffset.z; camera.lookAt(sphereGroup.position); } // Render loop function render() { requestAnimationFrame(render); renderer.render(scene, camera); moveSphere(); moveCamera(); }; // state var pressed = {}; var clock = new THREE.Clock(); // globals var scene = getScene(); var camera = getCamera(); var light = getLight(scene); var renderer = getRenderer(); // add meshes var loader = new THREE.TextureLoader(); var floor = getPlane(scene, loader); var background = getBackground(scene, loader); var sphereData = getSphere(scene); var sphere = sphereData[0]; var sphereGroup = sphereData[1]; addListeners(); render(); 
 body { margin: 0; overflow: hidden; } canvas { width: 100%; height: 100%; } 
 <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/88/three.min.js"></script> 

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM