news 2026/8/14 7:17:18

GPU ECS AnimationBaker 烘焙动画方案原理

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
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/
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/14 7:17:00

标准网站建设合同到底长啥样?老站长掏心窝子教你避坑指南

咱们今天不聊那些虚头巴脑的大道理,也不去扯什么数字化转型的宏大叙事。就聊点最实在、最接地气,但又最容易让无数创业者、中小企业老板,甚至是刚入行的设计朋友掉进坑里的东西——签合同。对,你没听错,就是那几张薄薄的纸,或者是电子文档里的条款,它能决定你几个月的汗…

作者头像 李华
网站建设 2026/8/14 7:16:07

揭秘福建漳州网站建设费用:从几百到几万到底差在哪?老板们必看避坑指南

做老板的,尤其是咱们福建漳州这一带的老板,最近是不是经常被问到一个问题:“老张/老李,你那个网站搞了多少银子?” 这句话听着轻松,其实背后水深得能把人淹死。很多老板听到报价单的时候,第一反应往往是懵的:“怎么这个做个网站要好几万?我隔壁王老板好像几千块就搞定…

作者头像 李华
网站建设 2026/8/14 7:16:03

LangGraph实战:基于StateGraph构建带记忆的ReAct智能体工作流

1. 项目概述:为什么我们需要 LangGraph?如果你已经用 LangChain 构建过一些简单的 AI 应用,比如一个问答机器人,你可能会发现一个痛点:当对话稍微复杂一点,需要多步推理、调用工具、或者记住之前的上下文时…

作者头像 李华
网站建设 2026/8/14 7:15:53

零基础入门Weakpass:从哈希识别到密码生成的完整工作流

零基础入门Weakpass:从哈希识别到密码生成的完整工作流 【免费下载链接】weakpass Weakpass collection of tools for bruteforce and hashcracking 项目地址: https://gitcode.com/gh_mirrors/we/weakpass Weakpass是一款功能强大的密码与哈希破解工具集&am…

作者头像 李华
网站建设 2026/8/14 7:15:47

从入门到精通2024年企业级网站建设实战指南及核心建站知识全解析

在这个数字化浪潮席卷全球的今天,任何一家稍有野心的企业或创业团队,如果还抱着“酒香不怕巷子深”的老黄历不放,那基本上是在自断前程。说实话,我以前也觉得建站这事儿挺高深莫测的,好像只有那些穿着格子衫、戴着眼镜、对着满屏代码敲敲打打的技术大牛才能搞定。但真当你…

作者头像 李华
网站建设 2026/8/14 7:14:30

从网球策略到数学建模:美赛C题决策优化与MDP实战解析

1. 从“网球策略”到“数学建模”:2024美赛C题的核心是什么?每年美赛的C题,总给人一种“接地气”的感觉。它不像A题那样需要处理海量数据,也不像B题那样涉及复杂的连续系统动力学。C题往往聚焦于一个具体的、生活化的场景&#xf…

作者头像 李华