当前位置: 首页 > news >正文

GPU ECS AnimationBaker 烘焙动画方案原理

目录

  1. 方案概述
  2. 烘焙阶段(离线)
  3. 骨骼矩阵纹理布局
  4. CPU 运行时工作
  5. 数据如何传给 GPU
  6. GPU 运行时工作
  7. 加权蒙皮数学原理
  8. 挂载道具(剑/枪)
  9. 项目实例:Zombunny

1. 方案概述

核心思想

将传统Animator + SkinnedMeshRenderer的骨骼动画离线烘焙为纹理数据,运行时:

  • CPU:只计算当前该查纹理的哪一行、帧间插值多少、多路动画如何混合(约 64 字节/角色/帧)
  • GPU:查纹理拿骨骼矩阵,在 Vertex Shader 里完成顶点蒙皮

三阶段分工

阶段执行时机做什么数据量
烘焙Editor 一次性采样动画 → 算蒙皮矩阵 → 写纹理 + UV 权重大(MB 级,静态)
CPU每帧推进时间 → 算frameIndex+ 混合参数小(~64B/实例/帧)
GPU每顶点并行查纹理 → LBS 蒙皮 → 输出变形顶点并行

与本项目的关系

  • Shader:Assets/Res/Shader/EnemyGpu.shadergraphPlayerGpu.shadergraph
  • 烘焙资源:Assets/Res/Prefabs/EntityAnimG/BakedAssets_*Gpu/
  • 运行时 System:GpuEcsAnimatorSystemGpuEcsAnimatedMeshSystem
  • ECS 控制: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.GenerateAnimationObjectFromModel

2.2 骨骼矩阵如何计算

不是从骨骼上直接读取现成矩阵,而是逐帧采样 Animator 后计算

// GpuEcsAnimationBakeServices.cs - BakeAnimationMatricesTexturefor每一帧:Animator.Play(stateName,-1,progressRatio);Animator.Update(0);// 只更新骨骼 Transformfor每一根骨骼:matrix=inv(网格世界矩阵)× 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
  • 支持SingleClipDualClipBlend(Blend Tree 预采样)

2.4 烘焙时顶点是否参与

不参与。逐帧循环只更新骨骼并计算矩阵;顶点权重在独立步骤BakeBoneWeightsIntoMesh只做一次静态写入 UV,与动画时间无关。


3. 骨骼矩阵纹理布局

3.1 尺寸公式

维度公式含义
宽度骨骼数 × 4每根骨骼占 4 列像素(4×4 矩阵的 4 列)
高度totalNbrOfFrames所有动画帧数累加
格式RGBAFloat每像素 16 字节(float4)

体积估算宽 × 高 × 16字节

3.2 像素与矩阵

  • 1 像素= 矩阵的 1 列(4 个 float)
  • 4 像素= 完整 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))
  • X=boneIndex × 4 + col(col = 0, 1, 2, 3)
  • Y=frameIndex

3.4 布局示意

[B0×4列] [B1×4列] [B2×4列] ... ← 宽 = 骨骼数 × 4 frame 0 │ M0 │ M1 │ M2 │ frame 1 │ M0 │ M1 │ M2 │ ... │ ... │ ... │ ... │ frame N │ M0 │ M1 │ M2 │ ← 高 = totalNbrOfFrames

4. CPU 运行时工作

4.1 核心原则

CPU不算蒙皮、不传骨骼矩阵,只算查表参数

4.2 数据流

游戏 System(RunAnimation) ↓ 写 animationID / speedFactor / blendFactor GpuEcsAnimatorSystem(Burst Job) ↓ 推进 normalizedTime,算 frameIndex、帧间插值、多路混合 ↓ 写入 GpuEcsAnimatorShaderDataComponent.shaderData GpuEcsAnimatedMeshSystem ↓ 拷贝到 GpuEcsMaterialAnimationState.Value Entities Graphics [MaterialProperty] ↓ 渲染时自动上传 GPU

4.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);

写入GpuEcsAnimatorControlComponentanimationIDspeedFactorblendFactor等。


5. 数据如何传给 GPU

5.1 不是什么

方式本方案是否使用
ComputeBuffer / StructuredBuffer
MaterialPropertyBlock
每帧 SetTexture 传矩阵

本项目血条、阴影等系统才用ComputeBuffer;GPUECSAnimationBaker不用

5.2 实际机制:ECS MaterialProperty + DOTS Instancing

[MaterialProperty("_AnimationState")]publicstructGpuEcsMaterialAnimationState:IComponentData{publicfloat4x4Value;}
  • 业务代码只写 ECS 组件:gpuEcsMaterialAnimationState.Value = shaderData
  • UnityEntities Graphics在渲染时自动收集实例数据,写入GPU Per-Instance Buffer(DOTS Instancing)
  • Material 需启用 Instancing:m_EnableInstancingVariants: 1

5.3 两类数据的传法

数据传法更新频率
_AnimationState_EnableAnimation[MaterialProperty]→ Instance Buffer每帧
_AnimatedBoneMatrices纹理Material 绑定(烘焙时SetTexture静态
Mesh UV1/2/3 权重Mesh 顶点缓冲静态
LocalTransform世界矩阵ECS 标准渲染每帧

6. GPU 运行时工作

全部在Vertex ShaderGpuEcsAnimator.cginc+EnemyGpu.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_frameN+1

t来自_AnimationState[i][1]frameIndex来自_AnimationState[i][2]

6.5 多路混合

最多 4 路(动画过渡、DualClipBlend),每路乘blendFactor累加。

6.6 蒙皮之后

  • 对象位移/旋转(角色在场景中移动)→ 常规 ECSLocalTransformLocalToWorld
  • 骨骼变形(挥刀、跑步)→ GPU 蒙皮(模型空间)
  • 两者分离:先模型空间蒙皮,再世界变换

7. 加权蒙皮数学原理

7.1 权重存储(烘焙进 UV)

UV 通道内容
UV1(bone0索引, weight0, bone1索引, weight1)
UV2(bone2索引, weight2, bone3索引, weight3)
UV3(bone4索引, weight4, bone5索引, weight5)

超出maxNumberOfBonesPerVertex的骨骼会被截断并重新归一化,保证 Σweight = 1。

7.2 Linear Blend Skinning(LBS)

单帧公式:

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% 绑在手骨,与身体共用 Animator,GPU 蒙皮自动跟随。

本项目 Player 的 Gun 即如此

PlayerGpu_GpuEcsAnimator ├── Player ← GpuEcsAnimatedMeshBehaviour └── Gun ← GpuEcsAnimatedMeshBehaviour(绑手骨)
优点缺点
实现简单,与身体完全一致不易做独立实体(拾取、碰撞)
GPU 并行,无额外 CPU需重新烘焙

8.2 方案二:Attachment Anchor 挂点(CPU,推荐可交互道具)

原理:烘焙时额外记录手部挂点 Transform 每帧矩阵;运行时 CPU 插值后写入剑实体的LocalTransform;GPU当普通 Mesh 渲染(不做 Shader 蒙皮)。

烘焙

// 配置 AttachmentAnchor,指向骨骼下 WeaponSocket 空节点anchorTransforms[frameIndex]=anchorTransform.localToWorldMatrix;

运行时

GpuEcsAnimatorSystem → 插值挂点矩阵 → GpuEcsCurrentAttachmentAnchorComponent GpuEcsAttachmentSystem → 剑.LocalTransform = 挂点矩阵 GPU → 常规 LocalToWorld × 顶点(整物体变换,非逐顶点蒙皮)
优点缺点
剑是独立 ECS 实体,可挂逻辑/碰撞额外 CPU System
GPU 开销低(标准 MVP)需烘焙挂点数据

注意:方案二的 GPU不会在 Shader 里读挂点矩阵做逐顶点计算;挂点矩阵在 CPU 侧写入实体 Transform。


9. 项目实例:Zombunny

骨骼数35
纹理尺寸140 × 383(140 = 35×4)
总体积≈ 838 KB(140×383×16 字节)
总帧数383

动画分段(AnimationIdsEnemyGpu):

动画startFrame帧数累计循环
Move03838
Idle38301339
Death33944383

每帧矩阵数据:35 × 64 字节 ≈2.2 KB/帧(仅矩阵纹理部分)。


10. 关键点速查

烘焙贴图

  • 烘焙的是蒙皮矩阵,不是顶点位置
  • 矩阵 =inv(网格矩阵) × 骨骼世界矩阵 × bindpose计算得来非直接提取
  • 逐帧采样只动骨骼,顶点不参与
  • 宽 = 骨骼数×4,高 = 总帧数,每矩阵 4 像素(RGBAFloat)

CPU 工作

  • 每帧只传_AnimationState(float4x4)+_EnableAnimation
  • 传的是 frameIndex、插值 t、混合权重,不传骨骼矩阵
  • 通过[MaterialProperty]+ Entities Graphics,不是 ComputeBuffer

GPU 工作

  • Load 纹理 → 拼矩阵 → LBS 加权 → 帧间 lerp → 多路 blend
  • 法线用旋转矩阵,位置用完整矩阵
  • 蒙皮在模型空间,世界位移走LocalTransform

挂道具

  • 绑骨骼:GPU 蒙皮,与身体同流程
  • 挂点:CPU 写 Transform,GPU 普通 Mesh 渲染

优缺点

优点缺点
CPU 开销极低,适合大量同屏内存随帧数线性增长
DOTS Instancing 友好动画需离线烘焙,不能运行时改
实现相对简单LBS 动画混合可能有体积塌陷

与 SkinnedMeshRenderer 对比

SkinnedMeshRendererGPUECSAnimationBaker
骨骼矩阵CPU 每帧计算烘焙进纹理
CPU 每帧更新所有骨骼只传 ~64B 查表参数
蒙皮位置CPU 或 GPU(引擎内部)GPU Vertex Shader 查纹理
Animator运行时完整状态机烘焙固定动画集

相关文件索引

类型路径
烘焙逻辑Assets/Thirds/GPUECSAnimationBaker/Engine/Baker/GpuEcsAnimationBakeServices.cs
CPU 动画 SystemAssets/Thirds/GPUECSAnimationBaker/Engine/AnimatorSystem/GpuEcsAnimatorSystem.cs
Material 同步 SystemAssets/Thirds/GPUECSAnimationBaker/Engine/AnimatorSystem/GpuEcsAnimatedMeshSystem.cs
MaterialProperty 组件Assets/Thirds/GPUECSAnimationBaker/Engine/AnimatorSystem/GpuEcsAnimatedMeshComponents.cs
GPU 蒙皮 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/
http://www.cnnetsun.cn/news/4016665.html

相关文章:

  • 标准网站建设合同到底长啥样?老站长掏心窝子教你避坑指南
  • 揭秘福建漳州网站建设费用:从几百到几万到底差在哪?老板们必看避坑指南
  • LangGraph实战:基于StateGraph构建带记忆的ReAct智能体工作流
  • 零基础入门Weakpass:从哈希识别到密码生成的完整工作流
  • 从入门到精通2024年企业级网站建设实战指南及核心建站知识全解析
  • 从网球策略到数学建模:美赛C题决策优化与MDP实战解析
  • WinDynamicDesktop自定义动态桌面主题:从原理到实战制作全指南
  • 深度解析门户网站建设重要性及未来趋势对品牌数字化生存的关键影响
  • 揭秘北京东直门网站建设:为何本地企业需要打造专业且懂业务的数字门面
  • 数学建模实战:从货量预测到人员排班的优化模型构建与求解
  • 揭秘中国建设教育网站背后的真相:它如何重塑行业人才标准并影响你的职业未来?
  • 网站建设的域名什么意思?老鸟掏心窝子:域名注册,其实就是一场关于互联网的“房产证”保卫战
  • Postman环境与全局变量详解:提升API测试效率与协作规范
  • 广州励网网站建设网络公司如何从底层逻辑重塑你的数字化竞争力与品牌溢价?
  • 从AI工具人到决策依赖:如何避免被AI绑架并构建健康人机协作
  • 深度解析建设银行积分网站的使用技巧与价值最大化指南,助您轻松实现积分翻倍
  • 吉林省城乡建设厅网站全面解读:如何高效获取最新政策与办事指南
  • 揭秘贵州省建设监理协会网站是什么以及它如何成为行业发展的核心枢纽
  • 深耕东莞网站建设与建筑工程技术支持打造数字化时代的专业服务高地
  • 大兴智能网站建设哪家好?避开坑位后的真心话与实操指南
  • UI.Vision RPA自动化从零到一实战指南:把重复劳动交给免费开源工具
  • 北京大兴企业网站建设哪家好:深度解析本地服务商的选择逻辑与避坑指南,助你打造高转化数字名片
  • PhotoGIMP免费补丁实测:3分钟让GIMP变身Photoshop界面的终极方案
  • 深度解析国家建设局网站功能与权威信息发布价值:如何利用国家建设局网站获取最新建筑行业政策及资质查询指南
  • Shapiq在树模型解释中的应用:LightGBM/XGBoost实例教程
  • 服装网站建设目标解析:从流量转化到品牌塑造的实战指南
  • 厦门橄榄网站建设:在流量为王的时代,如何为企业打造一套真正能落地的数字门面
  • Git私有仓库上传全攻略:从SSH配置到安全推送实践
  • 揭秘万基城市建设有限公司网站背后的匠心故事与行业未来展望
  • 房地产集团网站建设方案:打造数字化转型核心引擎与品牌信赖基石全方位指南