如何更新对象

默认情况下,只要对象被添加到场景中,就会自动更新其矩阵:

const object = new THREE.Object3D();
scene.add( object );
或者,它是某个已加入场景对象的子对象:
const object1 = new THREE.Object3D();
const object2 = new THREE.Object3D();

object1.add( object2 );
scene.add( object1 ); // object1 和 object2 都会自动更新它们的矩阵

但如果你确定对象是静态的,可以关闭自动更新,仅在需要时手动更新变换矩阵。

object.matrixAutoUpdate = false;
object.updateMatrix();

BufferGeometry

BufferGeometry 把顶点位置、面索引、法线、颜色、UV 以及自定义属性等信息 存在属性缓冲中,也就是 [link:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Typed_arrays 类型化数组]。 这种结构通常比旧版 Geometry 更快,但使用起来通常更复杂。

更新 BufferGeometry 时最重要的一点是:不能调整缓冲区大小 (开销很大,基本等同于新建几何体),但可以更新缓冲区已有内容。

这意味着如果你知道某个属性会增长(如顶点数增加), 必须预先分配足够大的缓冲区来容纳新增数据。 同时也意味着 BufferGeometry 必然存在最大容量, 无法做到无限高效扩展。

下面以“运行时不断延长的线段”为例: 先为 500 个顶点分配空间,但起初只绘制 2 个点, 通过 `BufferGeometry.drawRange` 控制绘制范围。

const MAX_POINTS = 500;

// 几何体
const geometry = new THREE.BufferGeometry();

// 属性
const positions = new Float32Array( MAX_POINTS * 3 ); // 每个点使用 3 个浮点数(x、y、z)
geometry.setAttribute( 'position', new THREE.BufferAttribute( positions, 3 ) );

// 绘制范围
const drawCount = 2; // 仅绘制前 2 个点
geometry.setDrawRange( 0, drawCount );

// 材质
const material = new THREE.LineBasicMaterial( { color: 0xff0000 } );

// 线段
const line = new THREE.Line( geometry, material );
scene.add( line );

接着按如下方式随机写入线段点位:

const positionAttribute = line.geometry.getAttribute( 'position' );

let x = 0, y = 0, z = 0;

for ( let i = 0; i < positionAttribute.count; i ++ ) {

    positionAttribute.setXYZ( i, x, y, z );

    x += ( Math.random() - 0.5 ) * 30;
    y += ( Math.random() - 0.5 ) * 30;
    z += ( Math.random() - 0.5 ) * 30;

}

如果要在首次渲染后改变绘制点数量,这样做:

line.geometry.setDrawRange( 0, newValue );

如果要在首次渲染后修改位置数据,需要设置 `needsUpdate`:

positionAttribute.needsUpdate = true; // 首次渲染后修改数据时必须设置

首次渲染后修改位置数据时,通常还需要重新计算包围体, 以保证视锥体裁剪、辅助器等功能正常工作。

line.geometry.computeBoundingBox();
line.geometry.computeBoundingSphere();

[link:https://jsfiddle.net/t4m85pLr/1/ 这个 fiddle 示例] 展示了一个动画线段,你可以按需改造。

示例

[example:webgl_custom_attributes WebGL / custom / attributes]
[example:webgl_buffergeometry_custom_attributes_particles WebGL / buffergeometry / custom / attributes / particles]

材质

所有 uniforms 都可以自由修改(如颜色、纹理、不透明度等),并会在每帧传入着色器。

GL 状态相关参数也可随时修改(如 depthTest、blending、polygonOffset 等)。

以下属性在运行时不易修改(尤其材质至少渲染过一次后):

  • uniform 的数量与类型
  • 是否启用以下特性
    • texture(纹理)
    • fog(雾)
    • vertex colors(顶点色)
    • morphing(变形)
    • shadow map(阴影贴图)
    • alpha test(Alpha 测试)
    • transparent(透明)

这些变化会触发重建着色器程序,你需要设置:

material.needsUpdate = true

注意这一步可能较慢并导致帧率抖动或卡顿(尤其在 Windows 上,DirectX 下编译 shader 往往比 OpenGL 更慢)。

为获得更平滑体验,可以通过“占位值”模拟部分开关效果,比如强度为 0 的灯光、纯白纹理或密度为 0 的雾。

你可以替换几何体分块所用材质,但无法在运行时改变对象按面材质划分分块的方式。

如果你需要在运行时切换多套材质配置:

若材质/分块数量较少,可提前分块(例如人物:头发/脸/身体/上衣/裤子;汽车:前/侧/顶/玻璃/轮胎/内饰)。

若数量很大(例如每个面都可能不同),建议改用属性或纹理驱动每面外观,而非大量分块材质。

示例

[example:webgl_materials_car WebGL / materials / car]
[example:webgl_postprocessing_dof WebGL / webgl_postprocessing / dof]

纹理

图像、Canvas、视频和数据纹理若内容有变更,需要设置:

texture.needsUpdate = true;

渲染目标会自动更新。

示例

[example:webgl_materials_video WebGL / materials / video]
[example:webgl_rtt WebGL / rtt]

相机

相机位置和目标会自动更新。如果你要修改以下参数:

  • fov(视野范围)
  • aspect(宽高比)
  • near(近裁剪面)
  • far(远裁剪面)

则需要重新计算投影矩阵:

camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();

InstancedMesh

`InstancedMesh` 用于在 `three.js` 中便捷地进行实例化渲染。 视锥体裁剪、射线检测等功能依赖最新包围体(包围盒和包围球)。 由于 `InstancedMesh` 的工作方式,它具有自己的 `boundingBox` 与 `boundingSphere`, 会覆盖几何体级别的包围体。

与几何体类似,只要底层数据变化就应重算包围体。 对 `InstancedMesh` 来说,常见场景是通过 `setMatrixAt()` 修改实例变换矩阵后, 再重算包围体。处理方式与几何体相同。

instancedMesh.computeBoundingBox();
instancedMesh.computeBoundingSphere();

SkinnedMesh

在包围体机制上,`SkinnedMesh` 与 `InstancedMesh` 原则相同: 它拥有自己的 `boundingBox` 与 `boundingSphere`,用于正确包裹动画中的网格。 当调用 `computeBoundingBox()` 与 `computeBoundingSphere()` 时, 会基于当前骨骼变换(即当前动画状态)计算对应包围体。