Skip to content

插件

此笔记记录于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

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中使用

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 };

手动控制相机

剪切到新的摄像机位置

js
// 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方法中。但是,您需要在动画期间禁用控件,否则,如果用户在动画完成之前尝试移动相机,您最终会遇到与动画冲突的控件,通常会导致灾难性的后果。

js
controls.enabled = false;

保存和恢复视图状态

js
controls.saveState();

// sometime later:
controls.reset();

销毁控件

js
controls.dispose();

按需渲染

要使用轨道控件按需渲染,您必须在此事件触发时渲染一帧:

使用OrbitControls按需渲染

js
controls.addEventListener('change', () => {
renderer.render(scene, camera);
});

要在 World.js 中进行设置,您将使用this.render

World.js: 使用OrbitControls按需渲染

js
controls.addEventListener('change', () => {
this.render();
});

接下来,在 main.js 中,确保我们不再启动循环。相反,渲染初始帧:

main.js: 渲染单个帧而不是开始循环

js
// render the inital frame
world.render();

如果您在应用程序中进行这些更改,您会发现这会导致一个小问题。当我们在 main.js 中渲染初始帧时,纹理还没有加载,所以立方体看起来是黑色的。如果我们运行循环,则在纹理加载后,这一帧几乎会立即被新帧替换,因此只有在几毫秒内立方体是黑色的甚至可能都不会引起注意。然而,通过按需渲染,我们现在只在用户与场景交互和移动相机时生成新帧。一旦您移动控件,果然,将创建一个新帧并显示纹理。

因此,您还需要在纹理加载后生成一个新帧。我们不会在这里介绍如何做到这一点,但希望它能强调为什么按需渲染比使用循环更棘手。您必须考虑需要新帧的所有情况(例如,不要忘记您还需要在 resize时渲染一帧)。