Babylon.js 8.x 中文文档整理——Node篇 (上)

前言

由于 Node 涵盖了整个 Babylon.js 的节点体系,既包含场景树与空间变换,也包含各种网格对象及其几何结构,因此将 Node 系列共分为两篇:

  • 上篇:节点体系,从整体角度介绍 Node 的继承关系、场景节点体系、Mesh 派生类、空间变换(TRS)、坐标空间以及 BoundingInfo 等基础概念,帮助建立完整的节点知识体系;

  • 下篇:几何体系,围绕 Geometry 展开,介绍顶点数据、索引数据、VertexDataSubMesh 等核心内容,并结合几何处理相关能力,进一步理解 Mesh 的组成与工作原理;

Part 1 Node介绍

1. Node概述

Node基类是Babylon最重要、最核心的类,它是场景中所有节点对象的基础类,也是整个场景树的核心组成部分。

无论是可见的 MeshGroundMesh,还是不可见的 TransformNodeCameraLight等对象,本质上都继承自 Node,共享统一的节点体系和生命周期管理机制。

bash 复制代码
Node
├── TransformNode
│   ├── AbstractMesh
│   │   ├── Mesh
│   │   │   ├── GroundMesh
│   │   │   ├── InstancedMesh
│   │   │   ├── LinesMesh
│   │   │   └── ...
│   │   └── ...
│   ├── Camera
│   └── ...
├── Light
├── Bone
└── ...

正因为所有场景对象都继承自 Node,因此它们拥有一致的节点管理能力,例如父子关系Parent/Children、世界矩阵World Matrix、启用状态Enabled、动画系统Animation,这也是 Babylon 能够统一管理整个场景对象的重要基础。

Node 描述场景对象本身,它定义了节点的名称、唯一标识、父子层级、启用状态、元数据、动画、生命周期等通用能力,为整个场景建立了一套统一的对象管理模型。
需要注意的是,Node 本身并不包含任何几何数据,也不具备直接渲染能力。顶点、索引、法线、UV、材质、纹理等真正参与 GPU 渲染的数据,所以实际使用中,几乎不会直接创建 Node 实例,而是使用其派生类完成具体功能。

2. Node
bash 复制代码
new Node(
	name: string,	//必填项
    scene?: Scene | null,	//可选
    isPure?: boolean	//可选,是否具备变换能力的标记
)

Node实例成员包括:

| 属性/方法名称 | 参数值/返回值类型 | 说明 |
|----------------------------------------------------------------------------------------------------------------------------|----------------------------------|----------------------------|----------------------|
| name | string | 节点名称 |
| id | string | 节点标识符 |
| uniqueId | number | 场景内自动分配的唯一整数 ID |
| state | string | 节点状态字符串 |
| metadata | any | 用户自定义元数据 |
| reservedDataStore | any | Babylon预留的数据存储对象 |
| inspectableCustomProperties | IInspectable[] | Inspector 中显示的自定义可检查属性列表 |
| accessibilityTag | `IAccessibilityTag | null` | 无障碍标签信息 |
| doNotSerialize | boolean | 是否禁止该节点参与场景序列化 |
| animations | Animation[] | 当前节点关联的动画集合 |
| parent | `Node | null` | 当前节点的父节点 |
| animationPropertiesOverride | `AnimationPropertiesOverride | null` | 动画播放属性覆盖配置,可统一控制动画行为 |
| getClassName() | string | 返回当前对象的运行时类名 |
| getScene() | Scene | 获取当前节点所属场景 |
| getEngine() | AbstractEngine | 获取当前场景关联的渲染引擎 |
| addBehavior(behavior: Behavior<Node>, attachImmediately?: boolean) | Node | 为节点添加一个 Behavior 行为组件 |
| removeBehavior(behavior: Behavior<Node>) | Node | 移除指定 Behavior |
| behaviors | Behavior<Node>[] | 获取当前节点绑定的所有 Behavior |
| getBehaviorByName(name: string) | `Behavior | null` | 根据名称获取指定 Behavior |
| getWorldMatrix() | Matrix | 获取当前节点的世界变换矩阵 |
| worldMatrixFromCache | Matrix | 获取缓存中的世界矩阵,不会重新计算 |
| updateCache(force?: boolean) | void | 更新节点缓存数据 |
| isSynchronizedWithParent() | boolean | 判断当前节点是否与父节点保持同步 |
| isSynchronized() | boolean | 判断当前节点缓存是否为最新状态 |
| isReady(_completeCheck?: boolean) | boolean | 判断节点是否已准备完成,可参与渲染 |
| markAsDirty(_property?: string) | Node | 标记节点指定属性已修改,触发重新计算 |
| isEnabled(checkAncestors?: boolean) | boolean | 判断节点是否处于启用状态,可同时检查父节点 |
| setEnabled(value: boolean) | void | 设置节点启用或禁用状态 |
| isDescendantOf(ancestor: Node) | boolean | 判断当前节点是否为指定节点的后代 |
| getDescendants(directDescendantsOnly?: boolean, predicate?: (node: Node) => boolean) | Node[] | 获取当前节点的所有后代节点 |
| getChildMeshes(directDescendantsOnly?: boolean, predicate?: (node: Node) => boolean) | AbstractMesh[] | 获取当前节点下所有 Mesh 类型子节点 |
| getChildren(predicate?: (node: Node) => boolean, directDescendantsOnly?: boolean) | Node[] | 获取当前节点的子节点 |
| getAnimationByName(name: string) | `Animation | null` | 根据名称获取节点上的动画 |
| createAnimationRange(name: string, from: number, to: number) | void | 创建动画区间 |
| deleteAnimationRange(name: string, deleteFrames?: boolean) | void | 删除指定动画区间 |
| getAnimationRange(name: string) | `AnimationRange | null` | 获取指定动画区间 |
| clone(name: string, newParent: Nullable<Node>, doNotCloneChildren?: boolean) | `Node | null` | 克隆当前节点,可指定新的父节点 |
| getAnimationRanges() | `(AnimationRange | null)\[\]` | 获取节点上的所有动画区间 |
| beginAnimation(name: string, loop?: boolean, speedRatio?: number, onAnimationEnd?: () => void) | `Animatable | null` | 播放指定动画区间 |
| serializeAnimationRanges() | any | 序列化所有动画区间信息 |
| computeWorldMatrix(_force?: boolean) | Matrix | 重新计算并返回节点世界矩阵 |
| dispose(doNotRecurse?: boolean, disposeMaterialAndTextures?: boolean) | void | 销毁当前节点,可选择递归销毁子节点及材质资源 |
| getHierarchyBoundingVectors(includeDescendants?: boolean, predicate?: Nullable<(abstractMesh: AbstractMesh) => boolean>) | { min: Vector3; max: Vector3;} | 计算当前节点(及其子节点)的整体包围盒范围 |
| isDisposed() | boolean | 判断当前节点是否已被销毁 |
| onReady | `(node: Node) => void | null` | 节点准备完成后的回调函数 |
| onDisposeObservable | Observable<Node> | 节点销毁时触发的观察者对象 |
| onDispose(callback: () => void) | void | 注册节点销毁回调 |
| onEnabledStateChangedObservable | Observable<boolean> | 节点启用状态发生变化时触发 |
| onClonedObservable | Observable<Node> | 节点克隆完成时触发 |

3. Node生命周期

Node作为整个场景对象体系的基础类,为场景中每一节点从创建到销毁都定义了一套统一的生命周期。

整个过程可以概括为:

bash 复制代码
创建(Create)
    ↓
加入场景(Scene)
    ↓
建立层级(Parent / Children)
    ↓
状态更新(Dirty → World Matrix)
    ↓
启用 / 禁用(Enabled)
    ↓
动画与行为(Animation / Behavior)
    ↓
克隆(Clone)
    ↓
销毁(Dispose)

Part 2Node派生类

1. Node派生体系概览

Node基类的派生体系大致如下:

Scene已在单独章节中介绍,它不属于Node派生体系。

CameraLight已在单独章节中介绍,我们本篇重点关注整个Mesh对象体系。

bash 复制代码
Engine
    │
    ▼
Scene
    │
    ├── Node
    │   ├── Camera
    │   ├── Light
    │   ├── Bone
    │   └── TransformNode
    │        └── AbstractMesh
    │             ├── Mesh
    │             └── InstancedMesh
    └──...
2. TransformNode

TransformNodeNode 的直接派生类,它在Node基类的基础上增加了完整的空间变换Transform能力。

从继承关系来看,TransformNode 位于 AbstractMeshCamera 等类型的上层,是所有具备空间变换能力对象的基础。

可以理解为:Node描述节点对象是什么,而TransformNode则进一步描述节点对象位于哪里、朝向哪里、如何缩放。
需要强调的是,TransformNode自身并不包含任何几何数据,也不参与渲染。

TransformNode更多用于组织场景层级,作为父节点控制多个节点对象的统一变换。

bash 复制代码
new TransformNode(
    name: string,	//必填项
    scene?: Scene | null,	//可选
    isPure?: boolean	//可选,是否具备变换能力的标记
)

TransformNode实例成员包括:

| 属性/方法名称 | 参数值/返回值类型 | 说明 |
|--------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------|------------------------------|-----------------------------|-------------|
| billboardMode | number | 设置公告板模式,使节点始终朝向相机 |
| scalingDeterminant | number | 缩放行列式系数,用于控制缩放方向及镜像变换 |
| infiniteDistance | boolean | 是否始终保持与相机固定距离,常用于天空盒等对象 |
| ignoreNonUniformScaling | boolean | 是否忽略非均匀缩放对矩阵计算的影响 |
| reIntegrateRotationIntoRotationQuaternion | boolean | 是否将欧拉角旋转重新合并到四元数旋转中 |
| position | Vector3 | 节点局部位置 |
| rotation | Vector3 | 节点局部欧拉角旋转 |
| scaling | Vector3 | 节点局部缩放 |
| rotationQuaternion | `Quaternion | null` | 节点四元数旋转,设置后通常优先于 rotation |
| customMarkAsDirty | () => void | 自定义节点脏标记处理函数 |
| forward | Vector3 | 获取节点当前前方向向量 |
| up | Vector3 | 获取节点当前上方向向量 |
| right | Vector3 | 获取节点当前右方向向量 |
| absolutePosition | Vector3 | 获取节点世界坐标位置 |
| absoluteScaling | Vector3 | 获取节点世界缩放 |
| absoluteRotationQuaternion | Quaternion | 获取节点世界旋转四元数 |
| isWorldMatrixFrozen | boolean | 当前世界矩阵是否已被冻结 |
| nonUniformScaling | boolean | 是否存在非均匀缩放 |
| getClassName() | string | 返回当前对象运行时类名 |
| isUsingPivotMatrix() | boolean | 判断是否启用了 Pivot Matrix |
| isUsingPostMultiplyPivotMatrix() | boolean | 判断 Pivot Matrix 是否采用后乘方式计算 |
| updatePoseMatrix(matrix: Matrix) | TransformNode | 更新节点姿态矩阵 |
| getPoseMatrix() | Matrix | 获取当前姿态矩阵 |
| setPreTransformMatrix(matrix: Matrix) | TransformNode | 设置预变换矩阵 |
| setPivotMatrix(matrix: DeepImmutable<Matrix>, postMultiplyPivotMatrix?: boolean) | TransformNode | 设置 Pivot Matrix |
| getPivotMatrix() | Matrix | 获取当前 Pivot Matrix |
| `instantiateHierarchy(newParent?: Nullable, options?: { doNotInstantiate: boolean | ((node: TransformNode) => boolean); }, onNewNodeCreated?: (source: TransformNode, clone: TransformNode) => void)` | `TransformNode | null` | 实例化整个节点层级结构 |
| freezeWorldMatrix(newWorldMatrix?: Nullable<Matrix>, decompose?: boolean) | TransformNode | 冻结世界矩阵,避免重复计算 |
| unfreezeWorldMatrix() | TransformNode | 解除世界矩阵冻结 |
| getAbsolutePosition() | Vector3 | 获取节点世界坐标位置 |
| setAbsolutePosition(absolutePosition: Vector3) | TransformNode | 设置节点世界坐标位置 |
| setPositionWithLocalVector(vector3: Vector3) | TransformNode | 使用局部坐标设置节点位置 |
| getPositionExpressedInLocalSpace() | Vector3 | 获取节点在局部坐标系中的位置 |
| locallyTranslate(vector3: Vector3) | TransformNode | 按局部坐标系移动节点 |
| lookAt(targetPoint: Vector3, yawCor?: number, pitchCor?: number, rollCor?: number, space?: Space) | TransformNode | 使节点朝向指定目标点 |
| getDirection(localAxis: Vector3) | Vector3 | 获取指定局部方向对应的世界方向 |
| getDirectionToRef(localAxis: Vector3, result: Vector3) | TransformNode | 将方向计算结果写入指定对象 |
| setDirection(localAxis: Vector3, yawCor?: number, pitchCor?: number, rollCor?: number) | TransformNode | 设置节点朝向指定方向 |
| setPivotPoint(point: Vector3, space?: Space) | TransformNode | 设置旋转与缩放中心点 |
| getPivotPoint() | Vector3 | 获取局部 Pivot 点 |
| getPivotPointToRef(result: Vector3) | TransformNode | 将局部 Pivot 点写入指定对象 |
| getAbsolutePivotPoint() | Vector3 | 获取世界坐标系下的 Pivot 点 |
| getAbsolutePivotPointToRef(result: Vector3) | TransformNode | 将世界 Pivot 点写入指定对象 |
| markAsDirty(property?: string) | Node | 标记节点属性已修改,触发矩阵重新计算 |
| setParent(node: Nullable<Node>, preserveScalingSign?: boolean, updatePivot?: boolean) | TransformNode | 设置父节点 |
| addChild(mesh: TransformNode, preserveScalingSign?: boolean) | TransformNode | 添加子节点 |
| removeChild(mesh: TransformNode, preserveScalingSign?: boolean) | TransformNode | 移除子节点 |
| attachToBone(bone: Bone, affectedTransformNode: TransformNode) | TransformNode | 将节点绑定到指定骨骼 |
| detachFromBone(resetToPreviousParent?: boolean) | TransformNode | 解除节点与骨骼的绑定 |
| rotate(axis: Vector3, amount: number, space?: Space) | TransformNode | 围绕指定轴旋转节点 |
| rotateAround(point: Vector3, axis: Vector3, amount: number) | TransformNode | 围绕指定点进行旋转 |
| translate(axis: Vector3, distance: number, space?: Space) | TransformNode | 沿指定方向平移节点 |
| addRotation(x: number, y: number, z: number) | TransformNode | 增量叠加欧拉角旋转 |
| isWorldMatrixCameraDependent() | boolean | 判断世界矩阵是否依赖相机计算 |
| computeWorldMatrix(force?: boolean, camera?: Nullable<Camera>) | Matrix | 计算并返回世界矩阵 |
| resetLocalMatrix(independentOfChildren?: boolean) | void | 重置局部变换矩阵 |
| registerAfterWorldMatrixUpdate(func: (mesh: TransformNode) => void) | TransformNode | 注册世界矩阵更新完成后的回调 |
| unregisterAfterWorldMatrixUpdate(func: (mesh: TransformNode) => void) | TransformNode | 注销世界矩阵更新回调 |
| getPositionInCameraSpace(camera?: Nullable<Camera>) | Vector3 | 获取节点在相机坐标系中的位置 |
| getDistanceToCamera(camera?: Nullable<Camera>) | number | 获取节点到相机的距离 |
| clone(name: string, newParent: Nullable<Node>, doNotCloneChildren?: boolean) | `TransformNode | null` | 克隆当前节点 |
| serialize(currentSerializationObject?: any) | any | 序列化当前节点 |
| getChildTransformNodes(directDescendantsOnly?: boolean, predicate?: (node: Node) => boolean) | TransformNode[] | 获取所有子 TransformNode 节点 |
| dispose(doNotRecurse?: boolean, disposeMaterialAndTextures?: boolean) | void | 销毁当前节点 |
| normalizeToUnitCube(includeDescendants?: boolean, ignoreRotation?: boolean, predicate?: Nullable<(node: AbstractMesh) => boolean>) | TransformNode | 将节点(及可选子节点)缩放至单位立方体尺寸 |
| onAfterWorldMatrixUpdateObservable | Observable<TransformNode> | 世界矩阵更新完成后触发的观察者 |

注意事项:

  • TransformNode 不参与渲染,优先用于层级管理;

  • 当节点设置了 rotationQuaternion 后,rotation通常将不再参与计算,两者不建议同时使用;

  • 调用 freezeWorldMatrix() 后,更新变换前需在合理时机调用unfreezeWorldMatrix()

3. AbstractMesh

AbstractMesh是所有可渲染三维对象的抽象基类,它继承自TransformNode

它在继承 TransformNode 空间变换能力的基础上,进一步增加了渲染、拾取、碰撞、包围盒、LOD、骨骼动画、材质关联等能力。

AbstractMesh通常不会直接实例化,而是对可渲染三维对象的公共能力进行抽象,实际使用时创建其子类实现功能。
AbstractMesh实例成员包括:

| 属性/方法名称 | 参数值/返回值类型 | 说明 |
|------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------|-----------------------------|----------------------------|
| cullingStrategy | number | 设置包围体视锥裁剪策略 |
| facetNb | number | 获取网格包含的面数量 |
| partitioningSubdivisions | number | 设置面分区划分数量 |
| partitioningBBoxRatio | number | 设置面分区包围盒扩展比例 |
| mustDepthSortFacets | boolean | 是否启用面深度排序 |
| facetDepthSortFrom | Vector3 | 设置面深度排序参考点 |
| collisionRetryCount | number | 碰撞检测失败后的最大重试次数 |
| isFacetDataEnabled | boolean | 是否已启用面数据 |
| morphTargetManager | `MorphTargetManager | null` | 当前网格关联的 Morph Target 管理器 |
| bakedVertexAnimationManager | `IBakedVertexAnimationManager | null` | 当前网格关联的顶点烘焙动画管理器 |
| rawBoundingInfo | `BoundingInfo | null` | 原始包围信息对象 |
| onCollide | (collidedMesh?: AbstractMesh) => void | 碰撞发生时触发的回调函数 |
| onCollisionPositionChange | () => void | 碰撞导致位置变化时触发的回调 |
| definedFacingForward | boolean | 是否定义模型正前方向 |
| visibility | number | 设置网格整体可见度0~1 |
| alphaIndex | number | 设置透明对象渲染排序优先级 |
| inheritVisibility | boolean | 是否继承父节点的可见状态 |
| isVisible | boolean | 是否参与渲染 |
| isPickable | boolean | 是否允许射线拾取 |
| isNearPickable | boolean | 是否支持近距离拾取 |
| isNearGrabbable | boolean | 是否支持近距离抓取 |
| showSubMeshesBoundingBox | boolean | 是否显示所有 SubMesh 的包围盒 |
| isBlocker | boolean | 是否作为导航或遮挡阻挡对象 |
| enablePointerMoveEvents | boolean | 是否响应 Pointer Move 事件 |
| pointerOverDisableMeshTesting | boolean | PointerOver 时是否跳过 Mesh 检测 |
| renderingGroupId | number | 所属渲染组编号 |
| material | `Material | null` | 当前网格关联的材质对象 |
| receiveShadows | boolean | 是否接收阴影 |
| outlineColor | Color3 | 外轮廓颜色 |
| outlineWidth | number | 外轮廓宽度 |
| overlayColor | Color3 | Overlay 覆盖层颜色 |
| overlayAlpha | number | Overlay 覆盖层透明度 |
| hasVertexAlpha | boolean | 顶点颜色是否包含 Alpha 通道 |
| useVertexColors | boolean | 是否使用顶点颜色进行渲染 |
| computeBonesUsingShaders | boolean | 是否使用 GPU Shader 计算骨骼动画 |
| numBoneInfluencers | number | 每个顶点允许的骨骼影响数量 |
| applyFog | boolean | 是否受场景雾效影响 |
| enableDistantPicking | boolean | 是否启用远距离拾取 |
| useOctreeForRenderingSelection | boolean | 是否使用八叉树进行渲染筛选 |
| useOctreeForPicking | boolean | 是否使用八叉树进行拾取检测 |
| useOctreeForCollisions | boolean | 是否使用八叉树进行碰撞检测 |
| layerMask | number | 图层掩码,用于控制相机可见性 |
| alwaysSelectAsActiveMesh | boolean | 是否始终作为活动网格参与渲染 |
| doNotSyncBoundingInfo | boolean | 是否禁止自动同步包围信息 |
| actionManager | `AbstractActionManager | null` | 当前网格关联的动作管理器 |
| ellipsoid | Vector3 | 碰撞检测使用的椭球体尺寸 |
| ellipsoidOffset | Vector3 | 碰撞椭球体相对于网格的位置偏移 |
| collisionMask | number | 碰撞掩码,用于过滤碰撞对象 |
| collisionResponse | boolean | 是否响应碰撞结果 |
| collisionGroup | number | 所属碰撞分组 |
| surroundingMeshes | `AbstractMesh\[\] | null` | 指定参与碰撞检测的周围网格集合 |
| edgesWidth | number | 边缘渲染线宽 |
| edgesColor | Color4 | 边缘渲染颜色 |
| subMeshes | SubMesh[] | 当前网格包含的 SubMesh 集合 |
| lightSources | Light[] | 当前影响该网格的光源集合 |
| skeleton | `Skeleton | null` | 当前关联的骨骼对象 |
| isBlocked | boolean | 是否被阻止参与当前渲染 |
| hasBoundingInfo | boolean | 是否已创建包围信息 |
| useBones | boolean | 当前网格是否启用骨骼动画 |
| isAnInstance | boolean | 是否为实例网格 |
| hasInstances | boolean | 是否存在实例网格 |
| hasThinInstances | boolean | 是否包含 Thin Instance 实例 |
| checkCollisions | boolean | 是否参与碰撞检测 |
| collider | `Collider | null` | 当前网格关联的碰撞器对象 |
| getMaterialForRenderPass(renderPassId: number) | `Material | undefined` | 获取指定 Render Pass 使用的材质 |
| setMaterialForRenderPass(renderPassId: number, material?: Material) | void | 设置指定 Render Pass 使用的材质 |
| transferToEffect(world: Matrix) | void | 将网格数据传递至当前 Shader Effect |
| getMeshUniformBuffer() | UniformBuffer | 获取网格对应的 Uniform Buffer |
| getClassName() | string | 返回当前对象运行时类名 |
| toString(fullDetails?: boolean) | string | 返回当前对象的字符串描述 |
| markAsDirty(property?: string) | AbstractMesh | 标记指定属性已修改,触发相关数据更新 |
| resetDrawCache(passId?: number, immediate?: boolean) | void | 重置当前网格绘制缓存 |
| getLOD(camera: Camera) | `AbstractMesh | null` | 根据相机获取当前应使用的 LOD 网格 |
| getTotalVertices() | number | 获取顶点总数 |
| getTotalIndices() | number | 获取索引总数 |
| getIndices() | `IndicesArray | null` | 获取索引数据 |
| getVerticesData(kind: string) | `FloatArray | null` | 获取指定类型的顶点数据 |
| setVerticesData(kind: string, data: FloatArray, updatable?: boolean, stride?: number) | AbstractMesh | 设置指定类型的顶点数据 |
| updateVerticesData(kind: string, data: FloatArray, updateExtends?: boolean, makeItUnique?: boolean) | AbstractMesh | 更新已有顶点数据 |
| setIndices(indices: IndicesArray, totalVertices: Nullable<number>) | AbstractMesh | 设置索引数据 |
| isVerticesDataPresent(kind: string) | boolean | 判断指定类型的顶点数据是否存在 |
| getBoundingInfo() | BoundingInfo | 获取当前包围信息 |
| getRawBoundingInfo() | BoundingInfo | 获取原始包围信息 |
| setBoundingInfo(boundingInfo: BoundingInfo) | AbstractMesh | 设置包围信息 |
| buildBoundingInfo(minimum: DeepImmutable<Vector3>, maximum: DeepImmutable<Vector3>, worldMatrix?: DeepImmutable<Matrix>) | BoundingInfo | 根据边界计算包围信息 |
| normalizeToUnitCube(includeDescendants?: boolean, ignoreRotation?: boolean, predicate?: Nullable<(node: AbstractMesh) => boolean>) | AbstractMesh | 将网格缩放至单位立方体尺寸 |
| movePOV(amountRight: number, amountUp: number, amountForward: number) | AbstractMesh | 按自身坐标系移动网格 |
| calcMovePOV(amountRight: number, amountUp: number, amountForward: number) | Vector3 | 计算按自身坐标系移动后的位移向量 |
| rotatePOV(flipBack: number, twirlClockwise: number, tiltRight: number) | AbstractMesh | 按自身坐标系旋转网格 |
| calcRotatePOV(flipBack: number, twirlClockwise: number, tiltRight: number) | Vector3 | 计算按自身坐标系旋转后的角度 |
| getNormalsData(applySkeleton?: boolean, applyMorph?: boolean) | `FloatArray | null` | 获取法线数据 |
| getPositionData(applySkeleton?: boolean, applyMorph?: boolean, data?: Nullable<FloatArray>) | `FloatArray | null` | 获取顶点坐标数据 |
| isInFrustum(frustumPlanes: Plane[]) | boolean | 判断网格是否位于视锥体内 |
| isCompletelyInFrustum(frustumPlanes: Plane[]) | boolean | 判断网格是否完全位于视锥体内 |
| `intersectsMesh(mesh: AbstractMesh | SolidParticle, precise?: boolean, includeDescendants?: boolean)` | boolean | 判断是否与指定网格相交 |
| intersectsPoint(point: Vector3) | boolean | 判断是否包含指定点 |
| moveWithCollisions(displacement: Vector3, slideOnCollide?: boolean) | AbstractMesh | 带碰撞检测移动网格 |
| intersects(ray: Ray, fastCheck?: boolean, trianglePredicate?: TrianglePickingPredicate, onlyBoundingInfo?: boolean, worldToUse?: Matrix, skipBoundingInfo?: boolean) | PickingInfo | 使用射线检测当前网格 |
| clone(name: string, newParent: Nullable<Node>, doNotCloneChildren?: boolean) | `AbstractMesh | null` | 克隆当前网格 |
| releaseSubMeshes(immediate?: boolean) | AbstractMesh | 释放所有 SubMesh |
| dispose(doNotRecurse?: boolean, disposeMaterialAndTextures?: boolean) | void | 销毁当前网格 |
| updateFacetData() | AbstractMesh | 更新面数据 |
| getFacetLocalNormals() | Vector3[] | 获取所有面的局部法线 |
| getFacetLocalPositions() | Vector3[] | 获取所有面的局部中心位置 |
| getFacetLocalPartitioning() | number[][] | 获取面的空间分区数据 |
| getFacetPosition(i: number) | Vector3 | 获取指定面的世界坐标中心 |
| getFacetPositionToRef(i: number, ref: Vector3) | AbstractMesh | 将指定面的中心位置写入目标对象 |
| getFacetNormal(i: number) | Vector3 | 获取指定面的法线方向 |
| getFacetNormalToRef(i: number, ref: Vector3) | AbstractMesh | 将指定面的法线写入目标对象 |
| getFacetsAtLocalCoordinates(x: number, y: number, z: number) | `number\[\] | null` | 获取指定局部坐标所在的所有面索引 |
| getClosestFacetAtCoordinates(x: number, y: number, z: number, projected?: Vector3, checkFace?: boolean, facing?: boolean) | `number | null` | 获取距离指定世界坐标最近的面 |
| getClosestFacetAtLocalCoordinates(x: number, y: number, z: number, projected?: Vector3, checkFace?: boolean, facing?: boolean) | `number | null` | 获取距离指定局部坐标最近的面 |
| getFacetDataParameters() | any | 获取当前面数据相关配置参数 |
| disableFacetData() | AbstractMesh | 禁用面数据功能 |
| updateIndices(indices: IndicesArray, offset?: number, gpuMemoryOnly?: boolean) | AbstractMesh | 更新索引数据 |
| createNormals(updatable: boolean) | AbstractMesh | 根据几何数据重新生成法线 |
| optimizeIndicesAsync() | Promise<AbstractMesh> | 异步优化索引缓存,提高渲染效率 |
| alignWithNormal(normal: Vector3, upDirection?: Vector3) | AbstractMesh | 使网格朝向指定法线方向 |
| disableEdgesRendering() | AbstractMesh | 关闭边缘渲染 |
| enableEdgesRendering(epsilon?: number, checkVerticesInsteadOfIndices?: boolean, options?: IEdgesRendererOptions) | AbstractMesh | 开启边缘渲染 |
| getConnectedParticleSystems() | IParticleSystem[] | 获取关联的粒子系统 |
| onCollideObservable | Observable<AbstractMesh> | 碰撞发生时触发的观察者 |
| onCollisionPositionChangeObservable | Observable<Vector3> | 碰撞位置发生变化时触发的观察者 |
| onMaterialChangedObservable | Observable<AbstractMesh> | 材质发生变化时触发的观察者 |
| onRebuildObservable | Observable<AbstractMesh> | 网格重建完成时触发的观察者 |

注意事项:

  • AbstractMesh 不是具体几何体,通常不会直接实例化;

  • AbstractMesh集成了碰撞、拾取、LOD、遮挡、边缘渲染、骨骼动画等众多功能,但并非所有能力都必须启用;

4. Mesh

MeshBabylon中最常见的可渲染节点对象,它继承自AbstractMesh类。

Mesh除了不仅是一个继承自AbstractMesh的可渲染节点对象,它同时还拥有独立几何数据Geometry和材质Material

Babylon中,大部分我们看到的三维物体最终都会落到 Mesh 或它的子类体系中。

bash 复制代码
//新版构造函数,推荐使用
new Mesh(
	name: string,	//必填项
    scene?: Scene | null,	//可选
    options?: MeshCreationOptions	//可选
)

interface MeshCreationOptions {
    source?: Mesh | null;	//指定一个来源 Mesh,新 Mesh 会基于 source 创建
    parent?: Node | null;	//指定父节点
    doNotCloneChildren?: boolean;	//是否忽略 source 的子节点
    clonePhysicsImpostor?: boolean;	//是否复制物理碰撞体
    cloneThinInstances?: boolean;	//是否复制 Thin Instance 数据
}

//旧版构造函数,不推荐
new Mesh(
    name: string, //必填项
    scene?: Scene | null, //可选
    parent?: Node | null, //可选
    source?: Mesh | null, //可选
    doNotCloneChildren?: boolean, //可选
    clonePhysicsImpostor?: boolean	//可选
)

Mesh实例成员包括:

| 属性/方法名称 | 参数值/返回值类型 | 说明 |
|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------|---------------------------------------------------|-------------------------------------|----------------------|
| computeBonesUsingShaders | boolean | 是否使用 GPU Shader 进行骨骼动画计算 |
| onBeforeDraw | () => void | Mesh 绘制前执行的回调函数 |
| hasInstances | boolean | 当前 Mesh 是否存在 InstancedMesh 实例 |
| hasThinInstances | boolean | 当前 Mesh 是否存在 Thin Instance |
| delayLoadState | number | 当前延迟加载状态 |
| instances | InstancedMesh[] | 当前 Mesh 创建的所有实例对象 |
| delayLoadingFile | string | 延迟加载的模型文件路径 |
| onLODLevelSelection | (distance: number, mesh: Mesh, selectedLevel: Nullable<Mesh>) => void | LOD 切换时触发的回调 |
| forcedInstanceCount | number | 强制指定实例数量,用于实例化渲染优化 |
| sideOrientation | number | Mesh 默认的面朝向 |
| overrideMaterialSideOrientation | number | 覆盖材质的面朝向设置 |
| overrideRenderingFillMode | `number | null` | 覆盖材质的渲染填充模式 |
| material | `Material | null` | 当前 Mesh 使用的材质 |
| ignoreCameraMaxZ | boolean | 是否忽略相机 maxZ 裁剪距离 |
| source | `Mesh | null` | 当前 Mesh 的源 Mesh |
| cloneMeshMap | `{id: string: Mesh | undefined } | null` | 克隆过程中维护的新旧 Mesh 映射 |
| isUnIndexed | boolean | 当前 Mesh 是否采用非索引几何 |
| worldMatrixInstancedBuffer | Float32Array | 存储实例世界矩阵的数据缓冲区 |
| previousWorldMatrixInstancedBuffer | Float32Array | 存储上一帧实例世界矩阵的数据缓冲区 |
| manualUpdateOfWorldMatrixInstancedBuffer | boolean | 是否手动更新实例世界矩阵缓冲区 |
| manualUpdateOfPreviousWorldMatrixInstancedBuffer | boolean | 是否手动更新上一帧实例矩阵缓冲区 |
| forceWorldMatrixInstancedBufferUpdate | boolean | 强制刷新实例世界矩阵缓冲区 |
| hasLODLevels | boolean | 是否配置了 LOD 层级 |
| geometry | `Geometry | null` | 当前 Mesh 持有的几何数据对象 |
| isBlocked | boolean | 当前 Mesh 是否被阻止参与渲染 |
| areNormalsFrozen | boolean | 法线数据是否被冻结 |
| overridenInstanceCount | number | 当前覆盖后的实例数量 |
| getClassName() | string | 获取类名 |
| toString(fullDetails?: boolean) | string | 返回 Mesh 的字符串描述 |
| getLODLevels() | MeshLODLevel[] | 获取所有 LOD 层级 |
| addLODLevel(distanceOrScreenCoverage: number, mesh: Nullable<Mesh>) | Mesh | 添加一个 LOD 层级 |
| getLODLevelAtDistance(distance: number) | `Mesh | null` | 获取指定距离对应的 LOD |
| removeLODLevel(mesh: Nullable<Mesh>) | Mesh | 移除指定 LOD |
| getLOD(camera: Camera, boundingSphere?: BoundingSphere) | `AbstractMesh | null` | 获取当前应使用的 LOD 对象 |
| getTotalVertices() | number | 获取顶点总数 |
| getVerticesData(kind: string, copyWhenShared?: boolean, forceCopy?: boolean, bypassInstanceData?: boolean) | `FloatArray | null` | 获取指定类型的顶点数据 |
| copyVerticesData(kind: string, vertexData: { [kind: string]: Float32Array; }) | void | 复制指定类型的顶点数据 |
| getVertexBuffer(kind: string, bypassInstanceData?: boolean) | `VertexBuffer | null` | 获取指定顶点缓冲区 |
| isVerticesDataPresent(kind: string, bypassInstanceData?: boolean) | boolean | 判断是否存在指定顶点数据 |
| isVertexBufferUpdatable(kind: string, bypassInstanceData?: boolean) | boolean | 判断指定顶点缓冲区是否可更新 |
| getVerticesDataKinds(bypassInstanceData?: boolean) | string[] | 获取所有顶点数据类型 |
| getTotalIndices() | number | 获取索引总数 |
| getIndices(copyWhenShared?: boolean, forceCopy?: boolean) | `IndicesArray | null` | 获取索引数组 |
| isReady(completeCheck?: boolean, forceInstanceSupport?: boolean) | boolean | 判断 Mesh 是否已完成渲染准备 |
| freezeNormals() | Mesh | 冻结法线数据 |
| unfreezeNormals() | Mesh | 解除法线冻结 |
| `refreshBoundingInfo(applySkeletonOrOptions?: boolean | IMeshDataOptions, applyMorph?: boolean)` | Mesh | 重新计算包围盒信息 |
| subdivide(count: number) | void | 将 Mesh 划分为多个 SubMesh |
| setVerticesData(kind: string, data: FloatArray, updatable?: boolean, stride?: number | AbstractMesh | 设置指定类型的顶点数据 |
| removeVerticesData(kind: string) | void | 删除指定类型的顶点数据 |
| markVerticesDataAsUpdatable(kind: string, updatable?: boolean) | void | 设置顶点数据是否可更新 |
| setVerticesBuffer(buffer: VertexBuffer, disposeExistingBuffer?: boolean) | Mesh | 设置顶点缓冲区 |
| updateVerticesData(kind: string, data: FloatArray, updateExtends?: boolean, makeItUnique?: boolean) | AbstractMesh | 更新顶点数据 |
| updateMeshPositions(positionFunction: (data: FloatArray) => void, computeNormals?: boolean) | Mesh | 更新顶点坐标,可选重算法线 |
| makeGeometryUnique() | Mesh | 将共享 Geometry 转为独立 Geometry |
| setIndexBuffer(indexBuffer: DataBuffer, totalVertices: number, totalIndices: number, is32Bits?: Nullable<boolean>) | void | 设置索引缓冲区 |
| setIndices(indices: IndicesArray, totalVertices?: Nullable<number>, updatable?: boolean, dontForceSubMeshRecreation?: boolean) | AbstractMesh | 设置索引数据 |
| updateIndices(indices: IndicesArray, offset?: number, gpuMemoryOnly?: boolean) | AbstractMesh | 更新索引数据 |
| toLeftHanded() | Mesh | 转换为左手坐标系 |
| registerBeforeRender(func: (mesh: AbstractMesh) => void) | Mesh | 注册渲染前回调 |
| unregisterBeforeRender(func: (mesh: AbstractMesh) => void) | Mesh | 注销渲染前回调 |
| registerAfterRender(func: (mesh: AbstractMesh) => void) | Mesh | 注册渲染后回调 |
| unregisterAfterRender(func: (mesh: AbstractMesh) => void) | Mesh | 注销渲染后回调 |
| renderWithRenderPassId(renderPassId?: number, enableAlphaMode?: boolean, effectiveMeshReplacement?: AbstractMesh, subMesh?: SubMesh, checkFrustumCulling?: boolean) | Mesh | 使用指定 Render Pass 渲染 |
| directRender() | Mesh | 直接执行渲染 |
| render(subMesh: SubMesh, enableAlphaMode: boolean, effectiveMeshReplacement?: AbstractMesh) | Mesh | 渲染指定 SubMesh |
| cleanMatrixWeights() | void | 清理骨骼权重数据 |
| validateSkinning() | { skinned: boolean;valid: boolean; report: string; } | 校验骨骼蒙皮数据是否合法 |
| isInFrustum(frustumPlanes: Plane[]) | boolean | 判断是否位于视锥体内 |
| setMaterialById(id: string) | Mesh | 根据材质 ID 设置材质 |
| getAnimatables() | IAnimatable[] | 获取所有可动画对象 |
| bakeTransformIntoVertices(transform: DeepImmutable<Matrix>) | Mesh | 将指定变换烘焙到顶点数据 |
| bakeCurrentTransformIntoVertices(bakeIndependentlyOfChildren?: boolean, forceUnique?: boolean) | Mesh | 将当前变换烘焙到顶点数据 |
| `clone(name?: string, newParent?: Nullable | MeshCloneOptions, doNotCloneChildren?: boolean, clonePhysicsImpostor?: boolean)` | Mesh | 克隆当前 Mesh |
| dispose(doNotRecurse?: boolean, disposeMaterialAndTextures?: boolean) | void | 释放 Mesh 及相关资源 |
| applyDisplacementMap(url: string, minHeight: number, maxHeight: number, onSuccess?: (mesh: Mesh) => void, uvOffset?: Vector2, uvScale?: Vector2, forceUpdate?: boolean, onError?: (message?: string, exception?: any) => void) | Mesh | 根据位移贴图修改顶点高度 |
| applyDisplacementMapFromBuffer(buffer: Uint8Array, heightMapWidth: number, heightMapHeight: number, minHeight: number, maxHeight: number, uvOffset?: Vector2, uvScale?: Vector2, forceUpdate?: boolean) | Mesh | 根据位移图缓冲区修改顶点高度 |
| convertToFlatShadedMesh() | Mesh | 转换为平面着色 Mesh |
| convertToUnIndexedMesh() | Mesh | 转换为非索引 Mesh |
| flipFaces(flipNormals?: boolean) | Mesh | 翻转三角面朝向 |
| increaseVertices(numberPerEdge?: number) | void | 增加 Mesh 顶点数量 |
| forceSharedVertices() | void | 强制共享重复顶点 |
| createInstance(name: string) | InstancedMesh | 创建一个实例化 Mesh |
| synchronizeInstances() | Mesh | 同步所有实例数据 |
| optimizeIndices(successCallback?: (mesh?: Mesh) => void) | Mesh | 优化索引数据,提高缓存命中率 |
| serialize(serializationObject?: any) | any | 序列化 Mesh |
| setPositionsForCPUSkinning() | `Float32Array | null` | 为 CPU 骨骼动画生成位置缓存 |
| setNormalsForCPUSkinning() | `Float32Array | null` | 为 CPU 骨骼动画生成法线缓存 |
| applySkeleton(skeleton: Skeleton) | Mesh | 将骨骼应用到当前 Mesh |
| addInstance(instance: InstancedMesh) | void | 添加一个实例对象 |
| removeInstance(instance: InstancedMesh) | void | 移除一个实例对象 |
| onMeshReadyObservable | Observable<Mesh> | Mesh 完成加载时触发 |
| onBeforeRenderObservable | Observable<Mesh> | Mesh 渲染前触发 |
| onAfterRenderObservable | Observable<Mesh> | Mesh 渲染后触发 |
| onBetweenPassObservable | Observable<SubMesh> | 多 Pass 渲染过程中触发 |
| thinInstanceEnablePicking | boolean | 是否启用 Thin Instance 的拾取功能 |
| thinInstanceAllowAutomaticStaticBufferRecreation | boolean | 是否允许静态实例缓冲区在数据更新时自动重新创建 |
| thinInstanceCount | number | 当前 Mesh 包含的 Thin Instance 实例数量 |
| `thinInstanceAdd(matrix: DeepImmutableObject | Array<DeepImmutableObject>, refresh?: boolean)` | number | 添加一个或多个 Thin Instance,并返回新实例的起始索引 |
| thinInstanceAddSelf(refresh?: boolean) | number | 将当前 Mesh 自身作为一个 Thin Instance 添加到实例列表,并返回实例索引 |
| thinInstanceRegisterAttribute(kind: string, stride: number) | void | 注册自定义 Thin Instance 属性,用于实例级 Shader 数据传递 |
| thinInstanceSetMatrixAt(index: number, matrix: DeepImmutableObject<Matrix>, refresh?: boolean) | void | 设置指定实例的世界变换矩阵 |
| thinInstanceSetAttributeAt(kind: string, index: number, value: Array<number>, refresh?: boolean) | void | 设置指定实例的自定义属性值 |
| thinInstanceSetBuffer(kind: string, buffer: Nullable<Float32Array>, stride?: number, staticBuffer?: boolean) | void | 设置或更新指定类型的 Thin Instance GPU 缓冲区 |
| thinInstanceGetWorldMatrices() | Matrix[] | 获取所有 Thin Instance 的世界变换矩阵 |
| thinInstanceBufferUpdated(kind: string) | void | 通知引擎指定实例缓冲区已更新,并同步至 GPU |
| thinInstancePartialBufferUpdate(kind: string, data: Float32Array, offset: number) | void | 更新指定实例缓冲区的局部数据,避免整体重新上传 |
| thinInstanceRefreshBoundingInfo(forceRefreshParentInfo?: boolean, applySkeleton?: boolean, applyMorph?: boolean) | void | 重新计算所有 Thin Instance 的包围盒信息 |

注意事项:

  • Geometry独立于Mesh,因此一个或多个Mesh可以独享或共享同一个Geometry实例,但Geometry的生命周期独立于Mesh的生命周期;

  • Material独立于Mesh,因此一个或多个Mesh可以独享或共享同一个Material实例,但Material的生命周期也独立于Mesh的生命周期;

  • 顶点数据修改后,需要同步更新包围信息,否则可能影响Picking准确性、BoundingBox 错误等;

  • Mesh必须主动配置addLODLevel,否则它会始终渲染最高精度模型;

  • Mesh不可见,不等于被销毁,只有dispose是才会释放资源;

  • ThinInstanceMesh并不是一个独立的对象,可以把它理解为Mesh的一种批量渲染的能力,且它的维护也只能通过Mesh

5. InstancedMesh

InstancedMesh是轻量级的Mesh对象,它也是AbstractMesh的直接派生类,多个InstancedMesh可以共享源MeshGeometryMaterial

InstancedMesh 不会重复创建顶点缓冲区、索引缓冲区和材质,是实现大量重复物体渲染的重要方式。

InstancedMesh必须依赖一个源Mesh作为source,它不能独立进行创建。

bash 复制代码
new InstancedMesh(
    name: string, //必填项
    source: Mesh	//必填项,源mesh
)

InstancedMesh实例成员包括:

| 属性/方法名称 | 参数值/返回值类型 | 说明 |
|-------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------|------------------------------|------------------|------------------------|
| lightSources | Light[] | 当前实例受影响的光源列表 |
| receiveShadows | boolean | 是否接收阴影 |
| material | `Material | null` | 当前实例使用的材质源Mesh |
| visibility | number | 当前实例的可见性系数0~1 |
| skeleton | `Skeleton | null` | 当前实例关联的骨骼对象 |
| renderingGroupId | number | 当前实例所属的渲染组编号 |
| sourceMesh | Mesh | 创建该实例所依赖的源 Mesh |
| geometry | `Geometry | null` | 当前实例使用的几何数据 |
| isAnInstance | boolean | 是否为实例化对象 |
| createInstance(name: string) | InstancedMesh | 基于当前实例继续创建新的实例 |
| getClassName() | string | 获取类名 |
| getTotalVertices() | number | 获取几何体顶点总数 |
| getTotalIndices() | number | 获取几何体索引总数 |
| isReady(completeCheck?: boolean) | boolean | 判断当前实例是否已完成渲染准备 |
| getVerticesData(kind: string, copyWhenShared?: boolean, forceCopy?: boolean) | `FloatArray | null` | 获取指定类型的顶点数据 |
| copyVerticesData(kind: string, vertexData: {[kind: string]: Float32Array; }) | void | 复制指定类型的顶点数据 |
| setVerticesData(kind: string, data: FloatArray, updatable?: boolean, stride?: number) | AbstractMesh | 设置指定类型的顶点数据 |
| updateVerticesData(kind: string, data: FloatArray, updateExtends?: boolean, makeItUnique?: boolean) | Mesh | 更新指定类型的顶点数据 |
| setIndices(indices: IndicesArray, totalVertices?: Nullable<number>) | Mesh | 设置几何体索引数据 |
| isVerticesDataPresent(kind: string) | boolean | 判断是否存在指定类型的顶点数据 |
| getIndices() | `IndicesArray | null` | 获取几何体索引数据 |
| `refreshBoundingInfo(applySkeletonOrOptions?: boolean | IMeshDataOptions, applyMorph?: boolean)` | InstancedMesh | 重新计算当前实例的包围盒信息 |
| getWorldMatrix() | Matrix | 获取当前实例的世界变换矩阵 |
| getLOD(camera: Camera) | AbstractMesh | 根据相机位置获取当前应使用的 LOD 对象 |
| clone(name: string, newParent?: Nullable<Node>, doNotCloneChildren?: boolean, newSourceMesh?: Mesh) | InstancedMesh | 克隆当前实例,并返回新的 InstancedMesh |
| dispose(doNotRecurse?: boolean, disposeMaterialAndTextures?: boolean) | void | 销毁当前实例,不影响源 Mesh 及共享资源 |
| `instantiateHierarchy(newParent?: Nullable, options?: { doNotInstantiate: boolean | ((node: TransformNode) => boolean); newSourcedMesh?: Mesh; }, onNewNodeCreated?: (source: TransformNode, clone: TransformNode) => void)` | `TransformNode | null` | 实例化当前节点及其层级结构,并返回新的根节点 |

注意事项:

  • InstancedMesh 必须依赖源 Mesh,且当源 Mesh 被释放后,所有实例也会随之失效;

  • InstancedMesh共享源MeshGeometryMaterial,当被修改时会影响所有依赖于该源MeshInstancedMesh

  • 虽然共享 Geometry,但每个可以独立拾取和参与碰撞;

6.小结
类型 继承关系 核心作用
Node 场景节点基类,定义了场景节点的基础能力
TransformNode extends Node 不参与渲染,一般作为层级节点,在 Node 基础上添加了空间变换TRS能力
AbstractMesh extends TransformNode 抽象类一般不直接实例化,在TransformNode基础上,定义了三维场景中可渲染的三维对象基础能力,包括拾取、碰撞、包围盒、LOD、阴影等
Mesh extends AbstractMesh 最常用的可渲染网格对象,可独立拥有GeometryMaterialThinInstance是其独特的批量渲染能力
InstancedMesh extends Mesh 简化版Mesh,必须依赖源Mesh,并共享源MeshGeometryMaterial,但可独立进行空间变换TRS

Part 3 Mesh派生类

1. Mesh派生体系概述

为了满足不同类型网格的应用需求,BabylonMesh 的基础上进一步派生出多个专用网格类。

每个派生类都针对特定场景进行了扩展,在继承 Mesh 全部能力的同时,增加了各自独有的功能。

bash 复制代码
Mesh
    ├── GroundMesh
    ├── GoldbergMesh
    |------ TrailMesh
    ├── LinesMesh
    ├── GreasedLineBaseMesh
    │      ├── GreasedLineMesh
    │      └── GreasedLineRibbonMesh
    └── GaussianSplattingMesh
2. GroundMesh

GroundMesh是专用于表示地面的Mesh网格对象,它在Mesh的基础上,进一步增加了高度查询、法线计算、地形坐标映射等与地形相关的功能。

GroundMesh适用于地形、地图等需要进行地面计算的场景。

bash 复制代码
new GroundMesh(
	name: string, 	//必填项
	scene?: Scene	//可选
)

GroundMesh实例成员包括:

属性/方法名称 参数值/返回值类型 说明
generateOctree boolean 是否在优化时生成地面八叉树
subdivisions number 地面在 X/Z 方向上的总细分数量
subdivisionsY number 地面在 Y 方向上的细分数量
getClassName() string 获取当前类名
optimize(chunksCount: number, octreeBlocksSize?: number) void 对地面进行分块优化,并可生成八叉树以提升查询和渲染效率
getHeightAtCoordinates(x: number, z: number) number 获取指定世界坐标 (x, z) 对应的地面高度
getNormalAtCoordinates(x: number, z: number) Vector3 获取指定世界坐标 (x, z) 处地面的法线向量
getNormalAtCoordinatesToRef(x: number, z: number, ref: Vector3) GroundMesh 将指定坐标处的地面法线写入目标 Vector3,避免创建新对象
updateCoordinateHeights() GroundMesh 更新地面高度缓存,使高度查询结果与当前地形保持一致
serialize(serializationObject: any) void 序列化当前GroundMesh

注意事项:

  • updateCoordinateHeights不是重新计算顶点,而是更新 GroundMesh 内部用于 getHeightAtCoordinates() 等查询的高度缓存;
3. goldbergMesh

goldbergMesh是一种基于Goldberg多面体生成的特殊 Mesh 网格对象,它主要用于生成球面均匀分布的多边形拓扑结构。

它的核心特点是将球面划分为大量五边形和六边形区域,实现类似于足球、地球网格、六边形蜂窝结构等特殊几何效果。
GoldbergMesh 没有公开构造函数,通常通过 MeshBuilder创建。
GoldbergMesh实例成员包括:

bash 复制代码
type GoldbergData = {
	faceColors: Color4[];	//每一个 Goldberg 面的颜色
    faceCenters: Vector3[];	//每个 Goldberg 面的中心点坐标
    faceZaxis: Vector3[];	//每个面的 Z 方向轴
    faceXaxis: Vector3[];	//每个面的局部 X 轴
    faceYaxis: Vector3[];	//每个面的局部 Y 轴
    nbSharedFaces: number;	//共享面的数量
    nbUnsharedFaces: number;	//非共享面的数量
    nbFaces: number;	//总面数量
    nbFacesAtPole: number;	//极点附近面的数量
    adjacentFaces: number[][];	//表示每一个面相邻的其他面编号
}

| 属性/方法名称 | 参数值/返回值类型 | 说明 |
|----------------------------------------------------------------------|---------------------|----------------------------------|-----------------------|
| goldbergData | GoldbergData | 保存当前 Goldberg 网格的拓扑数据 |
| relatedGoldbergFace(poleOrShared: number, fromPole?: number) | number | 根据极点信息获取对应的 Goldberg 面编号 |
| `setGoldbergFaceColors(colorRange: (number | Color4)\[\]\[\])` | void | 设置 Goldberg 面片颜色 |
| `updateGoldbergFaceColors(colorRange: (number | Color4)\[\]\[\])` | void | 更新 Goldberg 面片颜色 |
| `setGoldbergFaceUVs(uvRange: (number | Vector2)\[\]\[\])` | void | 设置 Goldberg 面片 UV |
| `updateGoldbergFaceUVs(uvRange: (number | Vector2)\[\]\[\])` | void | 更新 Goldberg 面片 UV |
| placeOnGoldbergFaceAt(mesh: Mesh, face: number, position: Vector3) | void | 将指定 Mesh 放置到 Goldberg 指定面片位置 |
| serialize(serializationObject: any) | void | 序列化当前 GoldbergMesh |

注意事项:

  • GoldbergMesh生成的是特殊拓扑结构,并不是普通的三角网格,因此不适合作为传统角色模型使用;

  • Goldberg 网格通常包含12个五边形面、大量六边形面,这是由球面拓扑结构决定的,球面无法完全由规则六边形铺满,因此必须存在五边形区域进行闭合;

4. TrailMesh

TrailMesh 是一种用于生成运动轨迹效果的特殊 Mesh 网格对象。

它通过记录目标对象,并沿移动路径动态生成连续带状几何体,从而实现类似飞行尾迹、子弹轨迹等效果。

bash 复制代码
new TrailMesh(
	name: string,	//必填项
	generator: TransformNode,	//必填项,被跟踪的目标 Mesh
    scene?: Scene, 	//可选
    diameter?: number, 	//可选,轨迹宽度
    length?: number, 	//可选,轨迹长度
    autoStart?: boolean	//可选,是否自动开始记录轨迹
)

TrailMesh实例成员包括:

| 属性/方法名称 | 参数值/返回值类型 | 说明 |
|---------------------------------------|-------------------------------------------|------------------|-----------------|
| diameter | number | 轨迹宽度 |
| getClassName() | string | 获取当前类名 |
| start() | void | 开始记录目标运动轨迹 |
| stop() | void | 停止更新轨迹 |
| update() | void | 更新轨迹 |
| reset() | void | 重置轨迹 |
| `clone(name: string | undefined, newGenerator: TransformNode)` | TrailMesh | 克隆当前TrailMesh |
| serialize(serializationObject: any) | void | 序列化当前TrailMesh |

  • TrailMesh依赖目标源Mesh对象移动;

  • TrailMesh不是永久历史记录,它根据length的配置进行历史点数据记录;

5. LinesMesh

LinesMesh 是专门用于表示线段类型几何体的 Mesh 网格对象,它不包含面和法线计算,而是直接通过顶点之间的连接关系绘制线段。

它主要用于辅助线、坐标轴;

bash 复制代码
new LinesMesh(
	name: string,	//必填项
	scene?: Scene | null, 	//可选
	parent?: Node | null, 	//可选,指定父节点
	source?: LinesMesh | null, 	//可选,指定一个来源 LinesMesh,新 LinesMesh 会基于 source 创建
	doNotCloneChildren?: boolean, 	//可选,是否忽略 source 的子节点
    useVertexColor?: boolean | undefined, 	//可选,是否启用顶点颜色
    useVertexAlpha?: boolean | undefined,	//可选,是否启用顶点透明度
    material?: Material	//可选,指定材质
)

LinesMesh实例成员包括:

| 属性/方法名称 | 参数值/返回值类型 | 说明 |
|---------------------------------------------------------------------------------------------------------|------------------------------------------------------|-------------------------------------------|-----------------|
| color | Color3 | 线段整体颜色 |
| alpha | number | 线段整体透明度 |
| intersectionThreshold | number | 线段拾取检测阈值 |
| material | `Material | null` | 当前线段使用的材质 |
| checkCollisions | boolean | 是否参与碰撞检测 |
| getClassName() | string | 获取类名 |
| dispose(doNotRecurse?: boolean, disposeMaterialAndTextures?: boolean, doNotDisposeMaterial?: boolean) | void | 销毁当前 LinesMesh |
| `clone(name: string, newParent?: Nullable | MeshCreationOptions, doNotCloneChildren?: boolean)` | LinesMesh | 克隆当前LinesMesh |
| createInstance(name: string) | InstancedLinesMesh | 创建 LinesMesh 的实例对象,共享原始线段 Geometry 数据 |
| serialize(serializationObject: any) | void | 序列化当前LinesMesh |

注意事项:

  • WebGL环境中,对于LinesMesh线宽支持有限,通常只能接近1px,如果需要粗线则不要使用它;
6. GreasedLineBaseMesh

GreasedLine 系列是为了解决传统 LinesMesh 线宽限制而提供的新一代线渲染方案。

GreasedLine 通过将线段转换为实际 Mesh 面片的方式实现任意宽度、渐变、纹理和动画效果。

  • GreasedLineBaseMesh抽象基类,定义了 GreasedLine基础能力;

  • GreasedLineRibbonMesh类是GreasedLineBaseMesh的派生类,它具备Ribbon 形式的线带生成能力,本质是一种宽线面片;

  • GreasedLineMesh类是GreasedLineBaseMesh的派生类,是最常用的创建结果;

bash 复制代码
new GreasedLineRibbonMesh(
	name: string, 	//必填项
	scene: Scene,	//必填项
    _options: GreasedLineMeshOptions,	//必填项
    _pathOptions?: {	//可选
        options: GreasedLineMeshOptions;
        pathCount: number;
    }[]
)

new GreasedLineMesh(
	name: string, 	//必填项
	scene: Scene,	//必填项
    _options: GreasedLineMeshOptions,	//必填项
)

interface GreasedLineMeshOptions {
    points: GreasedLinePoints;	//定义线的路径点
    widths?: number[];	//定义每个路径点对应的线宽
    instance?: GreasedLineBaseMesh;	//基于已有 GreasedLine 实例创建新的实例
    colorPointers?: number[];	//定义颜色索引映射
    uvs?: FloatArray;	//指定 UV 坐标
    updatable?: boolean;	//是否允许更新 Geometry
    lazy?: boolean;	//是否延迟创建 Geometry
    ribbonOptions?: GreasedLineRibbonOptions;	//控制 Ribbon 面生成方式
    pointsOptions?: GreasedLinePointsOptions;	//控制点数据处理方式
}

type GreasedLinePoints = Vector3[] | Vector3[][] | Float32Array | Float32Array[] | number[][] | number[];

interface GreasedLineRibbonOptions {
    pointsMode?: 0 | 1;	// 控制路径点的处理模式,例如单条路径、多条路径等,可选值0 | 1
    directions?: Vector3[] | Vector3;	//指定 Ribbon 面片扩展方向,用于控制线宽展开方向
    directionsAutoMode?: GreasedLineRibbonAutoDirectionMode;	//自动计算 Ribbon 扩展方向的模式
    width?: number;	//设置默认线宽,当未指定 widths 时使用该宽度
    facesMode?: 0  | 1 | 2;	//控制 Ribbon 面片的生成方式,例如单面、双面等
    closePath?: boolean;	//是否闭合路径,使首尾连接形成闭合线带
    smoothShading?: boolean;	//是否启用平滑着色,使 Ribbon 表面法线平滑过渡
}

interface GreasedLinePointsOptions {
    floatArrayStride?: number	//指定点数据在 FloatArray 中的步长,用于自定义点数据格式解析
}

GreasedLineBaseMesh实例成员包括:

| 属性/方法名称 | 参数值/返回值类型 | 说明 |
|--------------------------------------------------------------------------|------------------------|---------------------------------------------------|------------------------------------------|
| uvs | FloatArray | 当前 GreasedLineUV 坐标数据 |
| offsets | number[] | 线段顶点偏移数据,用于计算 Ribbon 面片两侧顶点的位置 |
| widths | number[] | 每个路径点对应的线宽数据,用于实现不同位置不同宽度的线效果 |
| colorPointers | number[] | 顶点颜色索引映射,用于指定路径点对应的颜色数据,实现渐变或分段颜色效果 |
| greasedLineMaterial | `IGreasedLineMaterial | undefined` | GreasedLine 专用材质接口,包含线宽、颜色、透明度等线渲染相关配置 |
| points | number[][] | 当前线的路径点数据,以数组形式保存顶点坐标信息 |
| getClassName() | string | 获取类名 |
| updateLazy() | void | 更新延迟创建的 GreasedLine 数据,当开启 lazy 模式后,需要主动调用该方法 |
| addPoints(points: number[][], options?: GreasedLineMeshOptions) | void | 向当前 GreasedLine 追加新的路径点,可同时传入更新配置 |
| dispose(doNotRecurse?: boolean, disposeMaterialAndTextures?: boolean) | void | 销毁当前 GreasedLine |
| isLazy() | boolean | 判断当前 GreasedLine 是否启用了延迟创建模式 |
| setPoints(points: GreasedLinePoints, options?: GreasedLineMeshOptions) | void | 重新设置路径点数据,并根据配置重新生成线 Geometry |
| serialize(serializationObject: any) | void | 序列化当前 GreasedLine |

GreasedLineRibbonMesh继承自GreasedLineBaseMesh基类,它拥有GreasedLineBaseMesh基类的所有实例成员,它特有的实例成员包括:

属性/方法名称 参数值/返回值类型 说明
isFlatLine boolean 判断当前线是否为平面线模式,开启后线条会按照固定平面方向生成
slopes number[] 保存路径点对应的斜率数据,用于计算 Ribbon 面片展开方向,使弯曲路径保持正确宽度
addPoints(points: number[][], options: GreasedLineMeshOptions, hasPathOptions?: boolean) void Ribbon 线追加路径点,并根据路径配置重新生成 Ribbon Geometry
getClassName() string 获取类名
clone(name?: string, newParent?: Nullable<Node>) GreasedLineRibbonMesh 克隆当前RibbonMesh
serialize(serializationObject: any) void 序列化当前RibbonMesh

GreasedLineRibbonMesh继承自GreasedLineBaseMesh基类,它拥有GreasedLineBaseMesh基类的所有实例成员,它特有的实例成员包括:

| 属性/方法名称 | 参数值/返回值类型 | 说明 |
|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------|-------------------------|---------------------------------------|
| intersectionThreshold | number | 线段拾取检测阈值 |
| getClassName() | string | 获取类名 |
| clone(name?: string, newParent?: Nullable<Node>) | GreasedLineRibbonMesh | 克隆当前 GreasedLineMesh |
| serialize(serializationObject: any) | void | 序列化当前 GreasedLineMesh |
| intersects(ray: Ray, fastCheck?: boolean, trianglePredicate?: TrianglePickingPredicate, onlyBoundingInfo?: boolean, worldToUse?: Matrix, skipBoundingInfo?: boolean) | PickingInfo | 使用射线检测当前线是否被命中,返回拾取结果信息 |
| findAllIntersections(ray: Ray, _fastCheck?: boolean, _trianglePredicate?: TrianglePickingPredicate, onlyBoundingInfo?: boolean, _worldToUse?: Matrix, skipBoundingInfo?: boolean, firstOnly?: boolean) | `{distance: number; point: Vector3; }\[\] | undefined` | 查找射线与 GreasedLine 的所有交点,返回交点距离和坐标信息 |

注意事项:

  • GreasedLine 系列本质是 Mesh,不是原生 Line

  • 宽线效果依赖 Geometry 重建,如果需要实时变化建议updatable:true更新;

  • 纹理流动效果依赖 UV

  • 线宽一般很小,拾取需要调整 intersectionThreshold扩大检测范围;

7. GaussianSplattingMesh

GaussianSplattingMeshBabylon中用于渲染 3D Gaussian Splatting 3DGS数据 的特殊 Mesh 类型。

GaussianSplattingMesh不依赖Vertex + Index 三角面Geometry SurfaceMaterial光照计算,而是通过大量 三维高斯分布点来表达真实世界中的空间信息。

它主要用于实景三维重建、室内空间采集、无人机建模等场景。

bash 复制代码
new GaussianSplattingMesh(
	name: string, 	//必填项
	url?: string | null, 	//可选,Gaussian Splat 数据文件地址
	scene?: Scene | null, 	//可选
	keepInRam?: boolean	//可选,是否保留CPU端数据,用于重新加载或更新
)

GaussianSplattingMesh实例成员包括:

| 属性/方法名称 | 参数值/返回值类型 | 说明 |
|-----------------------------------------------------------------------------------------------|-------------------------|-----------------------------------------------------------------|----------------------------------------------------------------------|
| viewDirectionFactor | Vector3 | 视角方向影响因子,用于控制 Gaussian Splat 在不同观察方向下的显示效果 |
| shDegree | number | 球谐函数阶数,用于表示 Gaussian Splat 的视角相关颜色信息 |
| splatsData | `ArrayBuffer | null` | 原始 Gaussian Splat 数据缓存,保存位置、缩放、旋转、颜色、不透明度等核心数据 |
| covariancesATexture | `BaseTexture | null` | Gaussian 协方差矩阵 A 部分纹理,用于 GPU Shader 中计算 Gaussian 的空间扩散范围和形状 |
| covariancesBTexture | `BaseTexture | null` | Gaussian 协方差矩阵 B 部分纹理,与 covariancesATexture 配合描述 Gaussian 椭球形状 |
| centersTexture | `BaseTexture | null` | Gaussian 中心点位置纹理,存储每个 Splat 的三维空间坐标信息 |
| colorsTexture | `BaseTexture | null` | Gaussian 颜色数据纹理,存储每个 Splat 的颜色和透明度信息 |
| shTextures | `BaseTexture\[\] | null` | 球谐函数数据纹理数组,用于存储视角相关颜色信息,实现不同观察方向下的颜色变化 |
| material | `Material | null` | 当前 Gaussian Splat 使用的材质 |
| getClassName() | string | 获取类名 |
| getTotalVertices() | number | 获取当前 Gaussian Splat 数量 |
| isReady(completeCheck?: boolean) | boolean | 判断 Gaussian Splat 是否已经准备完成,包括数据加载、Texture 创建、Shader 编译等状态 |
| render(subMesh: SubMesh, enableAlphaMode: boolean, effectiveMeshReplacement?: AbstractMesh) | Mesh | 重写 Mesh 渲染流程,使用 Gaussian Splat 专用渲染逻辑,而不是传统三角面渲染 |
| loadDataAsync(data: ArrayBuffer) | Promise<void> | 从二进制数据加载 Gaussian Splat 数据,并创建对应 GPU 资源 |
| loadFileAsync(url: string) | Promise<void> | 根据文件地址异步加载 Gaussian Splat 文件,例如 .splat.ply 等格式 |
| dispose(doNotRecurse?: boolean) | void | 销毁 Gaussian Splat 相关资源 |
| clone(name?: string) | GaussianSplattingMesh | 克隆当前 Gaussian Splat 对象 |
| updateDataAsync(data: ArrayBuffer, sh?: Uint8Array[]) | Promise<void> | 异步更新 Gaussian 数据,可同时更新球谐颜色数据,适用于实时更新场景 |
| updateData(data: ArrayBuffer, sh?: Uint8Array[]) | void | 同步更新 Gaussian 数据,直接替换当前 Splat 数据 |
| refreshBoundingInfo() | Mesh | 根据当前 Gaussian 数据重新计算包围盒,用于视锥裁剪、拾取范围判断等 |

注意事项:

  • Gaussian Splat 不参与传统光照,不要期待light改变 Gaussian

  • 性能主要取决Gaussian数量,它影响GPU显存、排序以及Texture大小;

  • Gaussian 更像拍摄结果,而不是模型资产,不适合频繁修改;

8. 小结
类型 继承关系 核心作用
InstancedMesh extends Mesh 依赖源 Mesh 创建实例,共享 GeometryMaterial,仅保留独立的空间变换能力
GroundMesh extends Mesh 地形网格,针对高度查询、法线获取、地面拾取等场景进行了优化
LinesMesh extends Mesh 线框网格,用于绘制线段、路径、辅助坐标轴等,不参与常规三角形渲染
TrailMesh extends Mesh 拖尾网格,可根据节点运动轨迹实时生成拖尾效果
GoldbergMesh extends Mesh Goldberg 多面体网格,对二十面体进行细分形成近似球形结构
GreasedLineMesh extends Mesh 高性能粗线渲染,相比 LinesMesh 支持线宽、渐变、纹理等效果
GreasedLineRibbonMesh extends GreasedLineBaseMesh 基于 Ribbon 的粗线实现,可生成带状连续曲面
GaussianSplattingMesh extends Mesh 基于 Gaussian Splatting 数据进行实时渲染,不依赖传统三角形网格
Lattice extends TransformNode 晶格变形节点,通过控制点对关联 Mesh 进行整体形变,不属于普通几何网格

Part 4 Mesh Builder

直接通过 new Mesh() 创建网格对象,不包含任何几何数据,需要手动创建顶点、索引、法线、UV。

Babylon提供了MeshBuilder,它是一个几何体创建工具集合,用于快速创建一个已经包含GeometryMesh网格对象:

方法 配置项说明(options 作用
MeshBuilder.CreateBox(name, options?, scene?) size?: number``width?: number``height?: number``depth?: number``faceUV?: Vector4[]``faceColors?: Color4[]``sideOrientation?: number``frontUVs?: Vector4``backUVs?: Vector4``wrap?: boolean``topBaseAt?: number``bottomBaseAt?: number``updatable?: boolean 创建立方体/长方体
MeshBuilder.CreateTiledBox(name, options?, scene?) pattern?: number``tileSize?: number``tileWidth?: number``tileHeight?: number``width?: number``height?: number``depth?: number``alignHorizontal?: number``alignVertical?: number``faceUV?: Vector4[]``faceColors?: Color4[]``sideOrientation?: number``updatable?: boolean 创建支持平铺纹理的立方体
MeshBuilder.CreateSphere(name, options?, scene?) diameter?: number``diameterX?: number``diameterY?: number``diameterZ?: number``segments?: number``arc?: number``slice?: number``sideOrientation?: number``frontUVs?: Vector4``backUVs?: Vector4``updatable?: boolean 创建球体
MeshBuilder.CreateDisc(name, options?, scene?) radius?: number``tessellation?: number``arc?: number``sideOrientation?: number``frontUVs?: Vector4``backUVs?: Vector4``updatable?: boolean 创建二维圆盘
MeshBuilder.CreateIcoSphere(name, options?, scene?) radius?: number``radiusX?: number``radiusY?: number``radiusZ?: number``subdivisions?: number``flat?: boolean``sideOrientation?: number``frontUVs?: Vector4``backUVs?: Vector4``updatable?: boolean 创建正二十面体球coSphere
MeshBuilder.CreateRibbon(name, options?, scene?) pathArray: Vector3[][]``closeArray?: boolean``closePath?: boolean``offset?: number``invertUV?: boolean``sideOrientation?: number``frontUVs?: Vector4``backUVs?: Vector4``updatable?: boolean``instance?: Mesh 根据路径数组生成带状网格
MeshBuilder.CreateCylinder(name, options?, scene?) height?: number``diameter?: number``diameterTop?: number``diameterBottom?: number``tessellation?: number``subdivisions?: number``arc?: number``enclose?: boolean``faceUV?: Vector4[]``faceColors?: Color4[]``hasRings?: boolean``sideOrientation?: number``frontUVs?: Vector4``backUVs?: Vector4``updatable?: boolean 创建圆柱、圆锥等柱体
MeshBuilder.CreateTorus(name, options?, scene?) diameter?: number``thickness?: number``tessellation?: number``sideOrientation?: number``frontUVs?: Vector4``backUVs?: Vector4``updatable?: boolean 创建圆环
MeshBuilder.CreateTorusKnot(name, options?, scene?) radius?: number``tube?: number``radialSegments?: number``tubularSegments?: number``p?: number``q?: number``sideOrientation?: number``updatable?: boolean 创建环面结Torus Knot
MeshBuilder.CreateLineSystem(name, options?, scene?) lines: Vector3[][]``colors?: Color4[][]``updatable?: boolean``instance?: LinesMesh 创建多条线段系统
MeshBuilder.CreateLines(name, options?, scene?) points: Vector3[]``colors?: Color4[]``alpha?: number``useVertexAlpha?: boolean``updatable?: boolean``instance?: LinesMesh 创建折线
MeshBuilder.CreateDashedLines(name, options?, scene?) points: Vector3[]``dashSize?: number``gapSize?: number``dashNb?: number``updatable?: boolean``instance?: LinesMesh 创建虚线
MeshBuilder.ExtrudeShape(name, options?, scene?) shape: Vector3[]``path: Vector3[]``scale?: number``rotation?: number``cap?: number``sideOrientation?: number``frontUVs?: Vector4``backUVs?: Vector4``updatable?: boolean``instance?: Mesh 沿路径拉伸二维截面
MeshBuilder.ExtrudeShapeCustom(name, options?, scene?) shape: Vector3[]``path: Vector3[]``scaleFunction?: (i,d)=>number``rotationFunction?: (i,d)=>number``ribbonCloseArray?: boolean``ribbonClosePath?: boolean``cap?: number``sideOrientation?: number``updatable?: boolean``instance?: Mesh 自定义拉伸
MeshBuilder.CreateLathe(name, options?, scene?) shape: Vector3[]``radius?: number``tessellation?: number``arc?: number``closed?: boolean``cap?: number``sideOrientation?: number``updatable?: boolean 旋转生成实体
MeshBuilder.CreateTiledPlane(name, options?, scene?) width?: number``height?: number``tileSize?: number``tileWidth?: number``tileHeight?: number``pattern?: number``alignHorizontal?: number``alignVertical?: number``sideOrientation?: number``updatable?: boolean 创建平铺平面
MeshBuilder.CreatePlane(name, options?, scene?) size?: number``width?: number``height?: number``sourcePlane?: Plane``sideOrientation?: number``frontUVs?: Vector4``backUVs?: Vector4``updatable?: boolean 创建平面
MeshBuilder.CreateGround(name, options?, scene?) width?: number``height?: number``subdivisions?: number``subdivisionsX?: number``subdivisionsY?: number``updatable?: boolean 创建地面
MeshBuilder.CreateTiledGround(name, options?, scene?) xmin:number``zmin:number``xmax:number``zmax:number``subdivisions:{w:number,h:number}``precision:{w:number,h:number}``updatable?: boolean 创建分块地面
MeshBuilder.CreateGroundFromHeightMap(name, url, options?, scene?) width?: number``height?: number``subdivisions?: number``minHeight?: number``maxHeight?: number``colorFilter?: Color3``alphaFilter?: number``onReady?: (mesh)=>void``updatable?: boolean 根据高度图生成地形
MeshBuilder.CreatePolygon(name, options?, scene?) shape: Vector3[]``holes?: Vector3[][]``depth?: number``sideOrientation?: number``frontUVs?: Vector4``backUVs?: Vector4``updatable?: boolean 创建二维多边形
MeshBuilder.ExtrudePolygon(name, options?, scene?) shape: Vector3[]``holes?: Vector3[][]``depth?: number``faceUV?: Vector4[]``faceColors?: Color4[]``sideOrientation?: number``updatable?: boolean 拉伸二维多边形生成三维模型
MeshBuilder.CreateTube(name, options?, scene?) path: Vector3[]``radius?: number``radiusFunction?: Function``tessellation?: number``arc?: number``cap?: number``sideOrientation?: number``updatable?: boolean``instance?: Mesh 沿路径生成管道
MeshBuilder.CreatePolyhedron(name, options?, scene?) type?: number``size?: number``sizeX?: number``sizeY?: number``sizeZ?: number``custom?: PolyhedronData``faceUV?: Vector4[]``faceColors?: Color4[]``flat?: boolean``sideOrientation?: number``updatable?: boolean 创建多面体
MeshBuilder.CreateGeodesic(name, options?, scene?) m:number``n:number``size?: number``sideOrientation?: number``updatable?: boolean 创建测地球Geodesic
MeshBuilder.CreateGoldberg(name, options?, scene?) m:number``n:number``size?: number``faceColors?: Color4[]``faceUV?: Vector4[]``sideOrientation?: number``updatable?: boolean 创建 Goldberg 多面体
MeshBuilder.CreateDecal(name, sourceMesh, options?) position?: Vector3``normal?: Vector3``size?: Vector3``angle?: number 在已有网格表面生成贴花Decal
MeshBuilder.CreateCapsule(name, options?, scene?) height?: number``radius?: number``tessellation?: number``subdivisions?: number``capSubdivisions?: number``sideOrientation?: number``frontUVs?: Vector4``backUVs?: Vector4``updatable?: boolean 创建胶囊体
MeshBuilder.CreateText(name, text, fontData, options?, scene?) size?: number``resolution?: number``depth?: number``faceUV?: Vector4[]``faceColors?: Color4[]``sideOrientation?: number``updatable?: boolean 创建三维文字

Part 5 Spatial Transform

1. TRSMatrix

TransformNode是最基础的拥有完整空间变化能力的节点对象,它负责描述一个节点在三维空间中的变换信息。

TRSTranslation平移、Rotation旋转、Scale缩放。

  • Translation表示改变物体的位置,而不会改变物体本身的朝向和大小,它对应position属性;

  • Rotation表示物体绕自身坐标轴旋转,它对应rotation/rotationQuaternion属性;

  • Scaling表示改变物体尺寸,它对应scaling属性;

在进行实时渲染时,节点的 positionrotationscaling 并不会直接发送给 GPUBabylon 会先会把三者组合成生成当前节点的 Local Matrix

Babylon提供一些常用的TRS访问包括:

属性/方法名称 参数值/返回值类型 说明
position Vector3 节点局部位置
rotation Vector3 节点局部欧拉角旋转
scaling Vector3 节点局部缩放
absolutePosition Vector3 获取节点世界坐标位置
absoluteScaling Vector3 获取节点世界缩放
absoluteRotationQuaternion Quaternion 获取节点世界旋转四元数
freezeWorldMatrix(newWorldMatrix?: Nullable<Matrix>, decompose?: boolean) TransformNode 冻结世界矩阵,避免重复计算
unfreezeWorldMatrix() TransformNode 解除世界矩阵冻结
getAbsolutePosition() Vector3 获取节点世界坐标位置
setAbsolutePosition(absolutePosition: Vector3) TransformNode 设置节点世界坐标位置
computeWorldMatrix(force?: boolean, camera?: Nullable<Camera>) Matrix 计算并返回世界矩阵
resetLocalMatrix(independentOfChildren?: boolean) void 重置局部变换矩阵
rotate(axis: Vector3, amount: number, space?: Space) TransformNode 围绕指定轴旋转节点
rotateAround(point: Vector3, axis: Vector3, amount: number) TransformNode 围绕指定点进行旋转
translate(axis: Vector3, distance: number, space?: Space) TransformNode 沿指定方向平移节点
addRotation(x: number, y: number, z: number) TransformNode 增量叠加欧拉角旋转
2.WorldLocal

World MatrixLocal Matrix是最长用且最容易混淆的两套坐标矩阵。

  • Local Matrix描述节点相对于父节点坐标系的空间变换,比如position = (0,1,0),它表示距离与父节点的距离;

  • World Matrix是整个Scene共用的一套坐标体系,比如absolutePosition = (0,1,0),它是相对世界原点的距离;

所有的网格对象最终都会从Local Space转换到World SpaceGPU 也是利用World Matrix进行绘制,矩阵是在图形渲染中最常见的数据形式。

实际使用时,我们需要根据我们使用目的去判断使用World Matrix还是Local Matrix

一个Mesh网格对象从加载到绘制的过程大致如下:

bash 复制代码
Mesh(position / rotation / scaling)
    │
    ▼
Local Space
    │
    │ Local Matrix
    ▼
Parent Transform
    │
    │ Parent World × Local Matrix
    ▼
World Space
    │
    │ View Matrix
    ▼
View Space
    │
    │ Projection Matrix
    ▼
Clip Space
    │
    ▼
Screen Space (Canvas)
3. Pivot中心

默认情况下,节点的旋转与缩放都围绕自身的局部原点Origin进行,因此模型原点的位置会直接影响旋转和缩放效果。

Babylon支持对物体原点进行设置,它不会修改物体的Geometry,也不会改变顶点数据,它只是改变在变换时旋转与缩放所参考的基准点。

Pivot实际属于一种变换行为,而不是几何编辑。

常用的功能包括:

属性/方法名称 参数值/返回值类型 说明
setPivotMatrix(matrix: DeepImmutable<Matrix>, postMultiplyPivotMatrix?: boolean) TransformNode 设置 Pivot Matrix
getPivotMatrix() Matrix 获取当前 Pivot Matrix
setPivotPoint(point: Vector3, space?: Space) TransformNode 设置旋转与缩放中心点
getPivotPoint() Vector3 获取局部 Pivot
isUsingPivotMatrix() boolean 判断是否启用了 Pivot Matrix
isUsingPostMultiplyPivotMatrix() boolean 判断 Pivot Matrix 是否采用后乘方式计算

Part 6 BoundingInfo

1. BoundingInfo概述

我们在Scene中篇大致描述过BoundingInfo的概念------为了减少大量顶点参与计算,Babylon 会自动为每个 Mesh 计算一份 包围盒信息BoundingInfo

AbstractMesh则是最基础的可渲染三维对象,因为它拥有几何边界,因此才具备一系列空间能力,对拾取、碰撞等提供基础支持。

BoundingInfo 并不是一种单独的几何体,而是同时维护了两种不同的包围结构:

  • 包围盒BoundingBox,使用一个长方体完全包裹模型,它记录了模型在三个坐标轴上的最小值Min和最大值Max

  • 包围球BoundingSphere,是一种使用球体描述模型空间范围的数据结构,它只包含球心和半径两个数据,计算速度快;

类型 描述 特点 常见用途
BoundingSphere 使用球心和半径描述模型空间范围 计算最快,但包裹通常不够精确 视锥体裁剪、距离检测、LOD、碰撞粗检测
BoundingBox 使用最小点(Min)和最大点(Max)组成长方体描述模型范围 精度更高,但计算略复杂 Picking、碰撞检测、空间计算、包围盒显示
2. BoundingInfo 更新

Mesh 创建完成后,会根据 Geometry 生成一份 Local BoundingInfo,其中保存了Local Space下的 BoundingBoxBoundingSphere

虽然BoundingInfo初始来自于Geometry,但当节点发生 TRS 变换时,Geometry实际不会发生变化,而是通过 World Matrix 进行维护。

BoundingInfo 并不会在每一帧渲染时都进行重复计算,它同样具有缓存机制,只有当节点的 World Matrix 被重新计算后,包围体才会同步更新。

常用与BoundingInfo相关功能包括:

| 属性/方法 | 类型 | 说明 |
|----------------------------------------------------------------------------------------------------------------------------|----------------------------------|-------------------------------------------|-----------------|
| getBoundingInfo() | BoundingInfo | 获取当前包围信息 |
| getRawBoundingInfo() | BoundingInfo | 获取原始包围信息 |
| setBoundingInfo(boundingInfo: BoundingInfo) | AbstractMesh | 设置包围信息 |
| buildBoundingInfo(minimum: DeepImmutable<Vector3>, maximum: DeepImmutable<Vector3>, worldMatrix?: DeepImmutable<Matrix>) | BoundingInfo | 根据边界计算包围信息 |
| rawBoundingInfo | `BoundingInfo | null` | 原始包围信息对象 |
| doNotSyncBoundingInfo | boolean | 是否禁止自动同步包围信息 |
| surroundingMeshes | `AbstractMesh\[\] | null` | 指定参与碰撞检测的周围网格集合 |
| refreshBoundingInfo(applySkeleton?: boolean, applyMorph?: boolean) | AbstractMesh | 根据当前 Geometry 重新生成 Local BoundingInfo |
| getHierarchyBoundingVectors(includeDescendants?: boolean, predicate?) | { min: Vector3; max: Vector3 } | 获取整个节点层级的包围范围 |
| showSubMeshesBoundingBox | boolean | 显示每个 SubMesh 的 BoundingBox |
| getBoundingInfo().boundingBox | BoundingBox | 获取包围盒 |
| getBoundingInfo().boundingSphere | BoundingSphere | 获取包围球 |

  • computeWorldMatrix() 更新的是节点的世界矩阵,而 BoundingInfo 会在需要时根据新的 World Matrix 自动更新,因此大多数情况下无需手动更新包围盒;

  • 一般在修改了Geometry时,才需要调用 refreshBoundingInfo() 重新生成 Local BoundingInfo

总结

Node 体系虽然不像 EngineScene 那样直接参与渲染,但它贯穿了整个 Babylon.js 三维对象的组织方式。从节点层级、空间变换到网格继承关系,再到包围体等基础能力,几乎所有三维对象都建立在这一套体系之上。理解 Node 的设计思想,不仅能够更好地理解各种对象之间的关系,也能够帮助我们在实际项目中更加合理地组织场景结构与管理三维对象。

NodeBabylon.js 整个场景树的基础,也是所有三维对象共同的起点。无论是普通网格、灯光、相机,还是各种特殊对象,本质上都建立在 Node 所提供的层级管理能力之上。

本篇主要围绕 Node 体系进行了系统梳理,从 Node → TransformNode → AbstractMesh → Mesh 的继承关系开始,介绍了各层所承担的职责与能力;随后结合 Mesh 的各种派生类型,说明了不同网格对象之间的设计思想与适用场景;最后又介绍了空间变换(TRS)、局部空间与世界空间(Local / World)、Pivot 原点以及 BoundingInfo 包围体等三维开发中最核心的基础概念。

相关推荐
柒和远方3 小时前
V058:前端路由的第一性原理:从 hashchange 手写路由,到 React Router 的嵌套与懒加载
前端·javascript·react.js
计算机魔术师3 小时前
阿里云上线 One Key MCP 服务:兼容 Qoder、Codex 等,可一键调用多家 MCP 服务
前端
Darling噜啦啦3 小时前
React Router 全家桶实战:从路由懒加载到嵌套路由的 6 大核心玩法
前端·react.js
Goodbye3 小时前
组件详解:从起源到未来的全方位解读
前端
无糖可可果3 小时前
从前端路由的起源到 React Router 实战
前端
亿元程序员3 小时前
竹知了很火?于是我用Cocos做了一个
前端
八号当铺3 小时前
我做了一个多端基金收益助手:从养基宝数据到 Web、桌面端、浏览器插件和 IDE 插件
前端·人工智能·github
用户852495071843 小时前
从多页到 SPA:用 50 行代码理解前端路由的本质
前端
Zldaisy3d3 小时前
连续纤维增材制造的机翼已飞上天,复材打印在低空飞行器上还需翻过几道坎?
java·前端·数据库