插件
此笔记记录于DISCOVER three.js,大多数为其中的摘要,少数为笔者自己的理解
简介
three.js 核心是一个功能强大、轻量级且专注的渲染框架,具有故意限制的功能。它拥有创建和渲染物理上正确的场景所需的一切,但是,它不具备创建游戏或产品配置器所需的一切。即使在构建相对简单的应用程序时,您也会经常发现自己需要的功能不在核心库中。发生这种情况时,在您自己编写任何代码之前,请检查是否有可用的插件。three.js 仓库包含数百个扩展,位于 examples/jsm文件夹中。对于那些使用包管理器的人,这些也包含在 NPM 包中。
您可以在 three.js 仓库中的 examples/jsm/controls/ 文件夹中的名为 OrbitControls.js 的文件中找到包含OrbitControls
的模块。还有一个 官方示例展示OrbitControls
。 要快速参考所有控件的设置和功能,请转到 OrbitControls
文档页面。
创建 controls.js
创建systems/controls.js
:
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';
function createControls(camera, canvas) {
const controls = new OrbitControls(camera, canvas);
// damping and auto rotation require
// the controls to be updated each frame
// this.controls.autoRotate = true;
controls.enableDamping = true;
controls.tick = () => controls.update();
return controls;
}
export { createControls };
enableDamping
为启动阻尼以增加真实感- 默认情况下,控件围绕场景中心旋转,即点(0,0,0)。 这存储在
controls.target
属性中,即Vector3
。我们可以将这个目标移动到一个新的位置:controls.target.set(1,2,3);
- 每当用户触发该插件时,该插件就会产生一些动画,所以增加
controls.tick = () => controls.update();
World.js 中使用
import { createCamera } from './components/camera.js';
import { createCube } from './components/cube.js';
import { createLights } from './components/lights.js';
import { createScene } from './components/scene.js';
import { createControls } from './systems/controls.js';
import { createRenderer } from './systems/renderer.js';
import { Resizer } from './systems/Resizer.js';
import { Loop } from './systems/Loop.js';
let camera;
let renderer;
let scene;
let loop;
class World {
constructor(container) {
camera = createCamera();
renderer = createRenderer();
scene = createScene();
loop = new Loop(camera, scene, renderer);
container.append(renderer.domElement);
const controls = createControls(camera, renderer.domElement);
const cube = createCube();
const light = createLights();
loop.updatables.push(controls);
// stop the cube's animation
// loop.updatables.push(cube);
scene.add(cube, light);
const resizer = new Resizer(container, camera, renderer);
}
render() {
// draw a single frame
renderer.render(scene, camera);
}
start() {
loop.start();
}
stop() {
loop.stop();
}
}
export { World };
手动控制相机
剪切到新的摄像机位置
// move the camera
camera.position.set(1,2,3);
// and/or rotate the camera
camera.rotation.set(0.5, 0, 0);
// then tell the controls to update
controls.update();
如果您在循环中调用.update
,则无需手动操作,只需移动相机即可。如果你 不 调用.update
就移动相机,会发生奇怪的事情,所以要小心!
当您移动相机时,controls.target
不会移动。如果您没有移动它,它将保持在场景的中心。当您将相机移动到新位置但保持目标不变时,相机不仅会移动,还会旋转,以便继续指向目标。这意味着在使用控件时,相机移动可能无法按预期工作。通常,您需要同时移动相机和目标以获得所需的结果。
平滑过渡到新的相机位置
如果您想将相机平滑地动画移动到一个新位置,您可能需要同时转换相机和目标,而最好的做这件事的地方就是controls.tick
方法中。但是,您需要在动画期间禁用控件,否则,如果用户在动画完成之前尝试移动相机,您最终会遇到与动画冲突的控件,通常会导致灾难性的后果。
controls.enabled = false;
保存和恢复视图状态
controls.saveState();
// sometime later:
controls.reset();
销毁控件
controls.dispose();
按需渲染
要使用轨道控件按需渲染,您必须在此事件触发时渲染一帧:
使用OrbitControls
按需渲染
controls.addEventListener('change', () => {
renderer.render(scene, camera);
});
要在 World.js 中进行设置,您将使用this.render
:
World.js: 使用OrbitControls
按需渲染
controls.addEventListener('change', () => {
this.render();
});
接下来,在 main.js 中,确保我们不再启动循环。相反,渲染初始帧:
main.js: 渲染单个帧而不是开始循环
// render the inital frame
world.render();
如果您在应用程序中进行这些更改,您会发现这会导致一个小问题。当我们在 main.js 中渲染初始帧时,纹理还没有加载,所以立方体看起来是黑色的。如果我们运行循环,则在纹理加载后,这一帧几乎会立即被新帧替换,因此只有在几毫秒内立方体是黑色的甚至可能都不会引起注意。然而,通过按需渲染,我们现在只在用户与场景交互和移动相机时生成新帧。一旦您移动控件,果然,将创建一个新帧并显示纹理。
因此,您还需要在纹理加载后生成一个新帧。我们不会在这里介绍如何做到这一点,但希望它能强调为什么按需渲染比使用循环更棘手。您必须考虑需要新帧的所有情况(例如,不要忘记您还需要在 resize 时渲染一帧)。