three.js Object3D 深度解析:3D 场景图中一切对象的基类、变换矩阵与父子层级机制
2026/9/8 15:35:46 网站建设 项目流程

three.js Object3D 深度解析:3D 场景图中一切对象的基类、变换矩阵与父子层级机制

【免费下载链接】three.jsJavaScript 3D Library.项目地址: https://gitcode.com/GitHub_Trending/th/three.js

Object3D是 three.js 中几乎所有 3D 对象的基类,MeshCameraLightGroupScene都从它继承而来。本文基于仓库中的官方文档 docs/pages/Object3D.html.md 与源码实现 src/core/Object3D.js,系统梳理它的继承关系、全部属性与方法的语义、位置/旋转/缩放与变换矩阵的双向同步机制,以及add/attach/traverse/toJSON等关键 API 的底层原理与适用限制,帮助你在构建场景图、做拾取检测、动态重挂对象和序列化资源时做出正确判断。

一、类定位与继承关系

Object3D的继承链为EventDispatcher → Object3D。它自身不持有任何几何体或材质,只负责三件事:在 3D 空间中定位一个对象(变换状态)组织对象的父子层级(场景图)提供遍历与查找工具。可渲染对象MeshLinePoints在其之上补充了几何与材质;CameraLight则补充了各自的观察/发光属性。

源码中类声明位于 src/core/Object3D.js#L64,构造函数new Object3D()无参数,创建一个新的 3D 对象。构造函数内部完成的工作比文档罗列的更细(src/core/Object3D.js#L69-L390):

  • 模块级自增计数器_object3DId为每个实例分配只读整数idObject.defineProperty保证不可重写);
  • uuidgenerateUUID()生成,全局唯一,是序列化与资源缓存的键;
  • type固定为字符串'Object3D',只读,子类各自覆盖为'Mesh''Group'等,供序列化/反序列化时识别类型;
  • rotation(Euler)与quaternion之间注册了_onChange回调实现双向联动——修改任一方都会自动同步另一方(src/core/Object3D.js#L145-L158)。这是理解“为什么改了rotationquaternion也变了”的关键。

二、实例标识与命名

属性类型说明
idnumber(只读)对象 ID,进程内自增,稳定可用于getObjectById
uuidstring(只读)全局唯一标识,序列化、Raycaster去重等场景使用
namestring名称,默认空字符串,用于getObjectByName
typestring(只读)对象类型,用于序列化/反序列化识别
isObject3Dboolean(只读)恒为true,是 three.js 惯例的类型测试标志

add()的实现可以看到这些标志的实际用途:添加子对象前先做object && object.isObject3D判断,不是Object3D实例会打印错误提示并静默忽略(src/core/Object3D.js#L767-L783)。

三、变换状态:position / rotation / quaternion / scale

本地变换由四个属性共同描述,构造函数中以Object.defineProperties定义(src/core/Object3D.js#L160-L226):

属性类型默认值说明
positionVector3(0,0,0)本地位置
rotationEuler(0,0,0)本地旋转,欧拉角(弧度)
quaternionQuaternion单位四元数本地旋转,四元数表示
scaleVector3(1,1,1)本地缩放

rotationquaternion是同一旋转状态的两种表示,源码通过_onChange机制自动互相同步,因此你可以任选其一操作,无需手动换算。选择建议:需要插值(球面线性插值slerp)或避免万向锁时用四元数;需要直观调试单个轴角时用欧拉角。

此外还有两个矩阵相关属性与变换状态配套:

  • modelViewMatrixMatrix4):模型视图矩阵,由渲染器在使用时填充;
  • normalMatrixMatrix3):法线矩阵,供着色器中法线变换使用。

pivot:绕指定点旋转/缩放

.pivot : Vector3属性(默认null)是较新的能力:设置后,旋转与缩放将围绕该点而非对象原点施加。其实现藏在updateMatrix()中——先用position/quaternion/scale合成矩阵,再直接对矩阵平移动量(elements[12..14])做补偿,使变换绕pivot生效(src/core/Object3D.js#L1144-L1163)。toJSON()copy()也已覆盖该字段,说明它已进入序列化契约。

四、矩阵系统与自动更新标志

这是Object3D最核心的性能与正确性机制。

属性默认值说明
matrixMatrix4本地空间变换矩阵
matrixWorldMatrix4世界空间变换矩阵;无父对象时与matrix相同
matrixAutoUpdatetrue(由DEFAULT_MATRIX_AUTO_UPDATE决定)true时每帧由引擎从 position/rotation/scale 自动计算matrixfalse时需手动调用updateMatrix()
matrixWorldAutoUpdatetrue(由DEFAULT_MATRIX_WORLD_AUTO_UPDATE决定)true时引擎自动根据父级matrixWorld与本级matrix计算matrixWorldfalse时由应用直接维护
matrixWorldNeedsUpdatefalsetrue后,本帧会重算世界矩阵并自动复位为false

三个静态默认值定义在文件末尾(src/core/Object3D.js#L1687-L1707):

Object3D.DEFAULT_UP = new Vector3( 0, 1, 0 ); // 也用于 DirectionalLight / HemisphereLight 默认位置 Object3D.DEFAULT_MATRIX_AUTO_UPDATE = true; Object3D.DEFAULT_MATRIX_WORLD_AUTO_UPDATE = true;

updateMatrix()

从当前position / quaternion / scale重新合成matrixMatrix4.compose),处理pivot补偿,并置matrixWorldNeedsUpdate = true标记脏状态(src/core/Object3D.js#L1144-L1163)。

updateMatrixWorld( force )

更新自身及所有后代的世界矩阵:

  • matrixAutoUpdatetrue,先调updateMatrix()
  • matrixWorldNeedsUpdate || forcematrixWorldAutoUpdate === true:无父级时matrixWorld直接拷贝matrix,有父级时matrixWorld = parent.matrixWorld * matrix
  • 关键点:一旦本节点需要重算,force会被内部提升为true并逐层传给子节点,保证脏状态沿子树向下传播(src/core/Object3D.js#L1176-L1214)。

updateWorldMatrix( updateParents, updateChildren, force )

updateMatrixWorld的受控版本,可精确控制更新范围(src/core/Object3D.js#L1225-L1275):

  • updateParents(默认false):是否向上递归更新祖先链;
  • updateChildren(默认false):是否向下递归更新后代;
  • force(默认false):即使matrixWorldNeedsUpdatefalse也强制重算。

localToWorldworldToLocalgetWorldPositionlookAt等方法的内部实现统一采用this.updateWorldMatrix( true, false )这一模式——先确保祖先链最新,再取matrixWorld,避免整棵子树无谓重算。这也是官方示例(如 examples/jsm/utils/SceneUtils.js#L174 的detach辅助流程)遵循的惯例。

五、父子层级操作

add / remove / removeFromParent / clear

  • add( ...objects ) : Object3D:将任意多个对象作为子对象加入。一个对象最多只有一个父对象,源码会先对传入对象执行removeFromParent()再挂入,因此对象若已有父级会被自动摘走。同时触发added(挂在子对象上)与childadded(挂在父对象上,事件携带child)两个事件。把对象加到自身会触发error提示并中止(src/core/Object3D.js#L746-L787)。
  • remove( ...objects ) : Object3D:移除子对象,将parentnull,触发removed/childremoved
  • removeFromParent() : Object3D:等价于parent?.remove(this),是重挂对象前的标准清理步骤。
  • clear() : Object3D:移除全部子对象,实现为一行return this.remove( ... this.children )(src/core/Object3D.js#L859-L863)。

单元测试 test/unit/src/core/Object3D.tests.js 对add/clear/removeFromParent的父子指针一致性做了断言验证。

attach:保持世界变换的重挂

attach( object ) : Object3D是最具实战价值的方法:把对象挂到当前对象之下,同时保持其世界变换(matrixWorld)不变(src/core/Object3D.js#L874-L908)。源码流程:

  1. this.updateWorldMatrix( true, false )更新自身世界矩阵,求逆得_m1
  2. 若对象原本有父级,乘上旧父级的matrixWorld,得到“从新父系到旧父系”的补偿矩阵;
  3. object.applyMatrix4( _m1 )把补偿直接写入对象的 position/rotation/scale;
  4. 摘除旧父级、挂入新父级,再updateWorldMatrix( false, true )向下刷新。

限制:文档与源码注释均明确不支持场景图中存在非均匀缩放的节点。测试用例中专门验证了“attach 前后 world matrix 逐元素相等”(test/unit/src/core/Object3D.tests.js#L456-L511)。仓库内真实使用案例可见 examples/jsm/misc/ProgressiveLightMap.js 将对象在多个光照容器场景间attach,以及 examples/jsm/csm/CSMHelper.js#L185。

六、查找与世界空间查询

属性检索

方法说明
getObjectById( id )从自身开始深度优先查找,返回第一个 ID 匹配的对象,未找到返回undefined
getObjectByName( name )同上,按name匹配
getObjectByProperty( name, value )按任意属性值匹配,是前两者的底层实现
getObjectsByProperty( name, value, result = [] )返回所有匹配对象,结果写入传入数组

从源码看(src/core/Object3D.js#L944-L988),查找是严格的“自身优先、子节点递归”的 DFS,getObjectByProperty===做全等比较。

世界空间分解查询

四个方法都要求传入target复用内存(three.js 零分配惯例),且内部都会先updateWorldMatrix( true, false )保证数据新鲜:

  • getWorldPosition( target : Vector3 ) : Vector3:从matrixWorld提取平移分量;
  • getWorldQuaternion( target : Quaternion ) : Quaternion:对matrixWorlddecompose提取旋转;
  • getWorldScale( target : Vector3 ) : Vector3:同上提取缩放;
  • getWorldDirection( target : Vector3 ) : Vector3:直接取矩阵第 3 列elements[8..10]并归一化,即对象的“朝向”(注意:对非相机/灯光对象,这是其局部 Z 轴在世界的方向)。

七、坐标转换与朝向

  • localToWorld( vector ) : Vector3matrixWorld作用于向量,本地 → 世界;
  • worldToLocal( vector ) : Vector3:对matrixWorld求逆后作用于向量,世界 → 本地(src/core/Object3D.js#L663-L683)。两者都原地修改并返回传入的向量;
  • lookAt( x, y, z ):旋转对象以面向世界空间中的目标点,也接受一个Vector3参数。源码中有两个易踩的细节(src/core/Object3D.js#L694-L734):
    • 相机与灯光面向目标时是“正轴指向目标”(lookAt( position, target, up )),而普通对象是“负 Z 轴指向目标”(lookAt( target, position, up ),因此普通网格用lookAt后其正面(-Z)朝向目标;
    • 若对象有父级,会从parent.matrixWorld提取旋转并左乘其逆四元数做抵消,最终只改变对象在父坐标系中的朝向;
    • 限制:不支持父级存在非均匀缩放的场景。

八、增量旋转与平移 API

所有方法都以四元数/向量运算实现并返回this,可链式调用。旋转类方法(局部空间)内部均为“构造轴角四元数后quaternion.multiply(_q1)”,世界空间版则是premultiply(src/core/Object3D.js#L531-L599)。

方法语义
rotateOnAxis( axis, angle )沿局部空间指定轴旋转(axis需归一化)
rotateOnWorldAxis( axis, angle )沿世界空间指定轴旋转;源码注释说明假定父级无旋转
rotateX / rotateY / rotateZ( angle )沿局部 X/Y/Z 轴旋转,分别复用静态_xAxis/_yAxis/_zAxis
setRotationFromAxisAngle( axis, angle )用轴角覆盖当前旋转(写入 quaternion)
setRotationFromEuler( euler )用欧拉角覆盖当前旋转
setRotationFromQuaternion( q )用四元数覆盖当前旋转
setRotationFromMatrix( m )从 4x4 矩阵提取旋转(要求上 3x3 为纯旋转、无缩放)
translateOnAxis( axis, distance )沿局部轴平移:先将轴向量应用对象自身四元数再累加到position
translateX / translateY / translateZ( distance )沿局部轴平移的快捷形式
applyQuaternion( q )quaternion.premultiply(q),把一次旋转叠加到对象
applyMatrix4( matrix )将矩阵左乘进matrix,再decompose回写 position/quaternion/scale(src/core/Object3D.js#L448-L456)

“set”系列是赋值语义,rotate*/translate*/apply*增量语义,二者不要混用。

九、渲染相关属性与回调

属性默认值作用
visibletruetrue时对象参与渲染;traverseVisible也以它为剪枝条件
frustumCulledtruetrue时对象受视锥剔除;边界情况(如粒子系统跨越视锥)可置false
renderOrder0覆盖默认渲染排序;不透明与透明对象仍各自独立排序,从低到高渲染。设置在Group上时,其所有后代会被聚合到一起排序渲染
castShadowfalsetrue时对象被渲染进阴影贴图
receiveShadowfalsetrue时对象受场景阴影影响
layersLayers层级成员;对象与相机至少共享一个 layer 才可见,也可用于Raycaster拾取过滤
customDepthMaterialundefined写深度缓冲时使用的自定义深度材质(仅 Mesh 相关);用平行光/聚光灯投影且顶点着色器修改了顶点位置时必须提供,否则阴影不正确。仅 WebGLRenderer 相关
customDistanceMaterialundefinedcustomDepthMaterial,用于点光源(PointLight)距离阴影
staticfalse声明对象在首次渲染后不再变化(含几何与材质设置),渲染器可跳过部分状态检查获得小幅加速。仅 WebGPURenderer 相关
animationsArray<AnimationClip>对象持有的动画片段数组,AnimationMixer常用
userDataObject存放自定义业务数据;不要放函数引用,克隆时不会保留
up(0,1,0)对象的上方向,影响lookAt姿态;全局默认值即Object3D.DEFAULT_UP

渲染回调钩子(默认空实现,子类或实例可覆盖):

  • onBeforeRender( renderer, object, camera, geometry, material, group ):对象渲染前回调;
  • onAfterRender( renderer, object, camera, geometry, material, group ):渲染后回调;
  • onBeforeShadow( renderer, object, camera, shadowCamera, geometry, depthMaterial, group ):写入阴影贴图前回调;
  • onAfterShadow( ... ):写入阴影贴图后回调。

典型用途:RTT 特效、在渲染单对象前切换纹理、逐对象调试。注意它们只对被实际渲染的对象触发。

十、遍历:traverse / traverseVisible / traverseAncestors

  • traverse( callback ):对对象自身及所有后代逐个执行回调;
  • traverseVisible( callback ):仅对visible === true的对象执行回调,且不可见对象的后代整棵子树直接跳过(src/core/Object3D.js#L1103-L1117);
  • traverseAncestors( callback ):反向,只沿parent链向上执行。

三个实现均为递归且简单直观。文档与源码注释一致强调:不推荐在回调中修改场景图(增删子对象会改变正在遍历的children数组)。

十一、克隆与序列化

clone / copy

  • clone( recursive = true ) : Object3D:返回new this.constructor().copy( this, recursive )——注意它用当前构造函数实例化,因此Group.clone()得到的是Group
  • copy( source, recursive = true ) : Object3D:从source拷贝 name、up、position、rotation.order、quaternion、scale、pivot(克隆或置 null)、matrix/matrixWorld、两个 auto-update 标志、matrixWorldNeedsUpdate、layers.mask、visible、castShadow/receiveShadow、frustumCulled、renderOrder、static、animations(浅拷贝数组),并用JSON.parse(JSON.stringify(...))深拷贝userDatarecursivetrue时对每个子对象clone()后重新add(src/core/Object3D.js#L1605-L1654)。

dispose()用于释放 GPU 相关资源并触发dispose事件;几何体、材质、纹理可能被共享,需要分别释放。

toJSON( meta )

toJSON( meta = undefined ) : Object将对象序列化为 JSON(src/core/Object3D.js#L1284-L1584)。机制要点:

  • metaundefined或字符串(JSON.stringify调用时即字符串)时视为根对象:初始化meta = { geometries, materials, textures, images, shapes, skeletons, animations, nodes }去重缓存,并写入metadata = { version: 4.7, type: 'Object', generator: 'Object3D.toJSON' }
  • 标准字段包括 uuid、type、name、castShadow、receiveShadow、visible、frustumCulled、renderOrder、static、matrixAutoUpdate、layers.mask、matrix(16 元素数组)、up、pivot(非 null 时)、非空时的 userData;
  • 子类扩展分支:InstancedMesh输出 count/instanceMatrix/instanceColor;isScene输出 background/environment;Mesh/Line/Points 通过serialize()缓存 geometry;SkinnedMesh输出 bindMode/bindMatrix/skeleton uuid;材质(单个或数组)同样按 uuid 入缓存;children 递归输出。

反序列化端对应 docs/pages/ObjectLoader.html.md 中的ObjectLoader#parse,两者共同构成 three.js 的对象 JSON 生态。

十二、事件

Object3D继承EventDispatcher,除通用的addEventListener/removeEventListener/dispatchEvent外,层级操作触发以下四类事件(事件对象为模块级单例,复用内存):

事件触发者说明
added被添加的对象自身对象被加入其父对象后触发
childadded父对象新的子对象被加入时触发,事件携带child属性
removed被移除的对象自身对象从父对象中移除后触发
childremoved父对象子对象被移除时触发,事件携带child

典型应用:在父对象上监听childadded/childremoved维护拾取列表或 LOD 注册表。

十二、实战要点小结

  1. 改完变换要等世界矩阵position修改后matrixWorld在下一次updateMatrixWorld前是旧的;getWorldPosition等 API 已自动处理,但手写渲染循环取matrixWorld前需自行保证更新;
  2. 批量静态场景:对不移动的对象可设matrixAutoUpdate = false并在初始化时手动updateMatrix(),减少每帧 compose 开销;
  3. 跨父级移动对象attach而非手动改parent,但需确认链路无非均匀缩放;
  4. 拾取前过滤visible控制渲染、layers控制渲染与 Raycaster 双重过滤、frustumCulled只影响渲染剔除,三者职责不同;
  5. 序列化toJSONmeta参数在递归序列化多个根对象时可手动传入以共享几何/材质缓存,避免重复输出。

本文全部内容以当前仓库 src/core/Object3D.js 的实现、test/unit/src/core/Object3D.tests.js 的测试断言及 examples/jsm 中的真实调用为证据;文档与源码行为存在差异时,以源码为准。

【免费下载链接】three.jsJavaScript 3D Library.项目地址: https://gitcode.com/GitHub_Trending/th/three.js

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询