
目录方案概述烘焙阶段离线骨骼矩阵纹理布局CPU 运行时工作数据如何传给 GPUGPU 运行时工作加权蒙皮数学原理挂载道具剑/枪项目实例Zombunny1. 方案概述核心思想将传统Animator SkinnedMeshRenderer的骨骼动画离线烘焙为纹理数据运行时CPU只计算当前该查纹理的哪一行、帧间插值多少、多路动画如何混合约 64 字节/角色/帧GPU查纹理拿骨骼矩阵在 Vertex Shader 里完成顶点蒙皮三阶段分工阶段执行时机做什么数据量烘焙Editor 一次性采样动画 → 算蒙皮矩阵 → 写纹理 UV 权重大MB 级静态CPU每帧推进时间 → 算frameIndex 混合参数小~64B/实例/帧GPU每顶点并行查纹理 → LBS 蒙皮 → 输出变形顶点并行与本项目的关系ShaderAssets/Res/Shader/EnemyGpu.shadergraph、PlayerGpu.shadergraph烘焙资源Assets/Res/Prefabs/EntityAnimG/BakedAssets_*Gpu/运行时 SystemGpuEcsAnimatorSystem、GpuEcsAnimatedMeshSystemECS 控制GpuEcsAnimatorAspect.RunAnimation()2. 烘焙阶段离线2.1 烘焙产出物产出Shader 属性 / 存储位置说明骨骼矩阵纹理_AnimatedBoneMatrices所有动画 × 所有帧 × 所有骨骼的 4×4 蒙皮矩阵骨骼权重Mesh UV1 / UV2 / UV3每顶点最多 6 根骨骼的(boneIndex, weight)动画元数据GpuEcsAnimatorBehaviour/ ECS Buffer每段动画的startFrameIndex、帧数、是否循环挂点数据可选GpuEcsAttachmentAnchorData挂点 Transform 每帧的 4×4 矩阵Material绑定矩阵纹理 Shader运行时不变关键代码入口GpuEcsAnimationBakerServices.GenerateAnimationObjectFromModel2.2 骨骼矩阵如何计算不是从骨骼上直接读取现成矩阵而是逐帧采样 Animator 后计算// GpuEcsAnimationBakeServices.cs - BakeAnimationMatricesTexturefor每一帧:Animator.Play(stateName,-1,progressRatio);Animator.Update(0);// 只更新骨骼 Transformfor每一根骨骼:matrixinv(网格世界矩阵)× bone.localToWorldMatrix × bindpose[boneIndex]写入纹理4像素/矩阵公式含义matrix inv(SkinnedMeshRenderer.transform) × boneTransform.localToWorldMatrix × bindpose与 Unity SkinnedMeshRenderer 标准 GPU 蒙皮矩阵一致将顶点从绑定姿态模型空间变换到当前动画姿态模型空间。2.3 采样参数采样帧率30 FPSGlobalConstants.SampleFrameRate每段动画帧数(int)(clip.length × 30) 1支持SingleClip和DualClipBlendBlend Tree 预采样2.4 烘焙时顶点是否参与不参与。逐帧循环只更新骨骼并计算矩阵顶点权重在独立步骤BakeBoneWeightsIntoMesh中只做一次静态写入 UV与动画时间无关。3. 骨骼矩阵纹理布局3.1 尺寸公式维度公式含义宽度骨骼数 × 4每根骨骼占 4 列像素4×4 矩阵的 4 列高度totalNbrOfFrames所有动画帧数累加格式RGBAFloat每像素 16 字节float4体积估算宽 × 高 × 16字节3.2 像素与矩阵1 像素 矩阵的 1 列4 个 float4 像素 完整 4×4 矩阵 64 字节存储方式按列写入float4(m[0][i], m[1][i], m[2][i], m[3][i])3.3 查表坐标Shader 中Load坐标// GpuEcsAnimator.cginc animatedBoneMatrices.Load(int3((boneIndex * 4) col, frameIndex, 0))XboneIndex × 4 colcol 0, 1, 2, 3YframeIndex3.4 布局示意[B0×4列] [B1×4列] [B2×4列] ... ← 宽 骨骼数 × 4 frame 0 │ M0 │ M1 │ M2 │ frame 1 │ M0 │ M1 │ M2 │ ... │ ... │ ... │ ... │ frame N │ M0 │ M1 │ M2 │ ← 高 totalNbrOfFrames4. CPU 运行时工作4.1 核心原则CPU不算蒙皮、不传骨骼矩阵只算查表参数。4.2 数据流游戏 SystemRunAnimation ↓ 写 animationID / speedFactor / blendFactor GpuEcsAnimatorSystemBurst Job ↓ 推进 normalizedTime算 frameIndex、帧间插值、多路混合 ↓ 写入 GpuEcsAnimatorShaderDataComponent.shaderData GpuEcsAnimatedMeshSystem ↓ 拷贝到 GpuEcsMaterialAnimationState.Value Entities Graphics [MaterialProperty] ↓ 渲染时自动上传 GPU4.3 每帧传给 GPU 的实例数据Shader 属性ECS 组件类型含义_AnimationStateGpuEcsMaterialAnimationStatefloat4x4查哪一行、插多少、怎么混_EnableAnimationGpuEcsMaterialEnableAnimationfloat0跳过蒙皮1启用_AnimationState每行最多 4 行分量含义[0]blendFactor该路动画混合权重[1]transitionToNextFrame当前帧 → 下一帧插值比例0~1[2]frameIndex纹理行索引整数帧[3]保留正常播放用 1~2 行动画切换过渡时 4 行全用旧动画 新动画 crossfade。4.4 游戏逻辑如何驱动// GpuEcsAnimatorAspectgpuEcsAnimatorAspect.RunAnimation((int)AnimationIdsEnemyGpu.Death);写入GpuEcsAnimatorControlComponentanimationID、speedFactor、blendFactor等。5. 数据如何传给 GPU5.1 不是什么方式本方案是否使用ComputeBuffer / StructuredBuffer否MaterialPropertyBlock否每帧 SetTexture 传矩阵否本项目血条、阴影等系统才用ComputeBufferGPUECSAnimationBaker不用。5.2 实际机制ECS MaterialProperty DOTS Instancing[MaterialProperty(_AnimationState)]publicstructGpuEcsMaterialAnimationState:IComponentData{publicfloat4x4Value;}业务代码只写 ECS 组件gpuEcsMaterialAnimationState.Value shaderDataUnityEntities Graphics在渲染时自动收集实例数据写入GPU Per-Instance BufferDOTS InstancingMaterial 需启用 Instancingm_EnableInstancingVariants: 15.3 两类数据的传法数据传法更新频率_AnimationState、_EnableAnimation[MaterialProperty]→ Instance Buffer每帧_AnimatedBoneMatrices纹理Material 绑定烘焙时SetTexture静态Mesh UV1/2/3 权重Mesh 顶点缓冲静态LocalTransform世界矩阵ECS 标准渲染每帧6. GPU 运行时工作全部在Vertex ShaderGpuEcsAnimator.cgincEnemyGpu.shadergraph子图完成。6.1 流程总览读 _AnimationState → 解析 frameIndex ↓ 读 UV1/2/3 → 解析 (boneIndex, weight) ↓ Load 纹理 → 拼 4×4 矩阵 ↓ 矩阵变换 加权累加LBS ↓ 帧间插值 → 多路动画混合 ↓ 输出 Position / Normal / Tangent ↓ Branch(_EnableAnimation) ? 蒙皮结果 : 原始顶点 ↓ LocalToWorld → MVP → 光照片元着色6.2 读矩阵并拼接float4 m0 loadBoneMatrixTexture(..., frameIndex, boneIndex, 0); float4 m1 loadBoneMatrixTexture(..., frameIndex, boneIndex, 1); float4 m2 loadBoneMatrixTexture(..., frameIndex, boneIndex, 2); float4 m3 loadBoneMatrixTexture(..., frameIndex, boneIndex, 3); float4x4 animatedBoneMatrix float4x4(m0, m1, m2, m3);6.3 单帧多骨骼加权对每个影响顶点骨骼最多 6 根positionOut boneWeight * mul(animatedBoneMatrix, float4(position, 1)).xyz; normalOut boneWeight * mul(rotationMatrix, normal); // 只用旋转部分 tangentOut boneWeight * mul(rotationMatrix, tangent);6.4 帧间插值pos (1 - t) * pos_frameN t * pos_frameN1t来自_AnimationState[i][1]frameIndex来自_AnimationState[i][2]。6.5 多路混合最多 4 路动画过渡、DualClipBlend每路乘blendFactor累加。6.6 蒙皮之后对象位移/旋转角色在场景中移动→ 常规 ECSLocalTransformLocalToWorld骨骼变形挥刀、跑步→ GPU 蒙皮模型空间两者分离先模型空间蒙皮再世界变换7. 加权蒙皮数学原理7.1 权重存储烘焙进 UVUV 通道内容UV1(bone0索引, weight0, bone1索引, weight1)UV2(bone2索引, weight2, bone3索引, weight3)UV3(bone4索引, weight4, bone5索引, weight5)超出maxNumberOfBonesPerVertex的骨骼会被截断并重新归一化保证 Σweight 1。7.2 Linear Blend SkinningLBS单帧公式p Σ (w_i × M_i × p) n Σ (w_i × R(M_i) × n)M_i完整 4×4 蒙皮矩阵含位移R(M_i)提取的纯旋转矩阵去掉缩放避免法线错误7.3 法线为何不用完整矩阵蒙皮矩阵含非均匀缩放时直接变换法线会出错。extractRotationMatrix对前三列归一化只保留旋转。8. 挂载道具剑/枪烘焙方案没有运行时骨骼 Transform 层级挂道具需额外处理。8.1 方案一绑骨骼蒙皮GPU推荐纯视觉道具原理剑/枪作为 SkinnedMesh顶点 100% 绑在手骨与身体共用 AnimatorGPU 蒙皮自动跟随。本项目 Player 的 Gun 即如此PlayerGpu_GpuEcsAnimator ├── Player ← GpuEcsAnimatedMeshBehaviour └── Gun ← GpuEcsAnimatedMeshBehaviour绑手骨优点缺点实现简单与身体完全一致不易做独立实体拾取、碰撞GPU 并行无额外 CPU需重新烘焙8.2 方案二Attachment Anchor 挂点CPU推荐可交互道具原理烘焙时额外记录手部挂点 Transform 每帧矩阵运行时 CPU 插值后写入剑实体的LocalTransformGPU当普通 Mesh 渲染不做 Shader 蒙皮。烘焙// 配置 AttachmentAnchor指向骨骼下 WeaponSocket 空节点anchorTransforms[frameIndex]anchorTransform.localToWorldMatrix;运行时GpuEcsAnimatorSystem → 插值挂点矩阵 → GpuEcsCurrentAttachmentAnchorComponent GpuEcsAttachmentSystem → 剑.LocalTransform 挂点矩阵 GPU → 常规 LocalToWorld × 顶点整物体变换非逐顶点蒙皮优点缺点剑是独立 ECS 实体可挂逻辑/碰撞额外 CPU SystemGPU 开销低标准 MVP需烘焙挂点数据注意方案二的 GPU不会在 Shader 里读挂点矩阵做逐顶点计算挂点矩阵在 CPU 侧写入实体 Transform。9. 项目实例Zombunny项值骨骼数35纹理尺寸140 × 383140 35×4总体积≈ 838 KB140×383×16 字节总帧数383动画分段AnimationIdsEnemyGpu动画startFrame帧数累计循环Move03838是Idle38301339否Death33944383否每帧矩阵数据35 × 64 字节 ≈2.2 KB/帧仅矩阵纹理部分。10. 关键点速查烘焙贴图烘焙的是蒙皮矩阵不是顶点位置矩阵 inv(网格矩阵) × 骨骼世界矩阵 × bindpose计算得来非直接提取逐帧采样只动骨骼顶点不参与宽 骨骼数×4高 总帧数每矩阵 4 像素RGBAFloatCPU 工作每帧只传_AnimationStatefloat4x4_EnableAnimation传的是 frameIndex、插值 t、混合权重不传骨骼矩阵通过[MaterialProperty] Entities Graphics不是 ComputeBufferGPU 工作Load 纹理 → 拼矩阵 → LBS 加权 → 帧间 lerp → 多路 blend法线用旋转矩阵位置用完整矩阵蒙皮在模型空间世界位移走LocalTransform挂道具绑骨骼GPU 蒙皮与身体同流程挂点CPU 写 TransformGPU 普通 Mesh 渲染优缺点优点缺点CPU 开销极低适合大量同屏内存随帧数线性增长DOTS Instancing 友好动画需离线烘焙不能运行时改实现相对简单LBS 动画混合可能有体积塌陷与 SkinnedMeshRenderer 对比SkinnedMeshRendererGPUECSAnimationBaker骨骼矩阵CPU 每帧计算烘焙进纹理CPU 每帧更新所有骨骼只传 ~64B 查表参数蒙皮位置CPU 或 GPU引擎内部GPU Vertex Shader 查纹理Animator运行时完整状态机烘焙固定动画集相关文件索引类型路径烘焙逻辑Assets/Thirds/GPUECSAnimationBaker/Engine/Baker/GpuEcsAnimationBakeServices.csCPU 动画 SystemAssets/Thirds/GPUECSAnimationBaker/Engine/AnimatorSystem/GpuEcsAnimatorSystem.csMaterial 同步 SystemAssets/Thirds/GPUECSAnimationBaker/Engine/AnimatorSystem/GpuEcsAnimatedMeshSystem.csMaterialProperty 组件Assets/Thirds/GPUECSAnimationBaker/Engine/AnimatorSystem/GpuEcsAnimatedMeshComponents.csGPU 蒙皮 HLSLAssets/Thirds/GPUECSAnimationBaker/Engine/Shader/GpuEcsAnimator.cginc挂点 SystemAssets/Thirds/GPUECSAnimationBaker/Engine/AnimatorSystem/GpuEcsAttachmentSystem.cs敌人 ShaderAssets/Res/Shader/EnemyGpu.shadergraph烘焙资源示例Assets/Res/Prefabs/EntityAnimG/BakedAssets_ZombunnyGpu/