16.Unity性能优化实践项目总结

  1. 16.总结
    1. 16.1 知识点
      1. 总结主要内容
      2. 主要学习内容
      3. 更多拓展
      4. 举一反三
      5. 总结
    2. 16.2 核心要点速览
      1. 压测基线与资源准备
      2. 从动画机到 VAT 像素
        1. 枚举 Clip 与帧数
        2. 定帧取顶点
        3. 归一化进纹理
        4. 写 PNG 与导入设置
      3. Shader 实例化、顶点 ID 与 MPB
        1. 启用 Instancing
        2. 逐实例数据与 VAT
        3. MaterialPropertyBlock 实际干什么
      4. 编辑器 VAT 工具入口
      5. VAT 纹理排布与 ScriptableObject 配置
      6. 烘焙收尾与资源关联
      7. VAT Shader 采样与运行时驱帧
        1. Shader 与材质参数分工
        2. 索引、采样与顶点输出
        3. 运行时网格与播放
    3. 16.3 面试题精选
      1. 进阶题
        1. 1. SampleAnimation 和 BakeMesh 的调用顺序与前提是什么?
          1. 题目
          2. 深入解析
          3. 答题示例
          4. 参考文章
        2. 2. VAT 用的 PNG 为什么要 Uncompressed、Point、关闭 sRGB?
          1. 题目
          2. 深入解析
          3. 答题示例
          4. 参考文章
        3. 3. GPU Instancing 相关宏在顶点和片元里怎么配合?
          1. 题目
          2. 深入解析
          3. 答题示例
          4. 参考文章
        4. 4. VAT 里 pixelIndex 和状态机动画顺序是什么关系?
          1. 题目
          2. 深入解析
          3. 答题示例
          4. 参考文章
        5. 5. 为什么 VAT + Instancing 要用 MeshRenderer 而不是 SkinnedMeshRenderer?
          1. 题目
          2. 深入解析
          3. 答题示例
          4. 参考文章
      2. 深度题
        1. 1. MaterialPropertyBlock 能单独降低 DrawCall 吗?和 Instancing 怎么配合?
          1. 题目
          2. 深入解析
          3. 答题示例
          4. 参考文章
        2. 2. 用 SV_VertexID 读 VAT 时,和烘焙阶段的数据要满足什么一致条件?
          1. 题目
          2. 深入解析
          3. 答题示例
          4. 参考文章
        3. 3. VAT 顶点着色器里 index 公式的每一项,在 C# 里谁负责传?
          1. 题目
          2. 深入解析
          3. 答题示例
          4. 参考文章
        4. 4. 课文里 VAT 方案的五条「遗留问题」,你会怎么跟策划或引擎组对齐?
          1. 题目
          2. 深入解析
          3. 答题示例
          4. 参考文章

16.总结


16.1 知识点

总结主要内容

主要学习内容

更多拓展

举一反三

总结


16.2 核心要点速览

压测基线与资源准备

整套对比建立在 Built-in 管线上:同一空工程里导入角色和动画,后面 VAT 和 GPU Instancing 的优化都针对「大量相同角色、动作同步播放」这种压力场景。中途换成 URP 或改场景结构,前面记的 Batches、Profiler 曲线就对不上了,所以基线阶段先把管线钉死。

  • 模型导入:Model 里该开的 Import BlendShapesImport Visibility 要开全,否则播动画时表情或显隐层级会丢。待机动画用的 AnimationClip 建议勾 Loop Time,Profiler 里看到的是稳定循环,而不是播完卡一下再从头。
  • 统计入口:Game 视图 StatsBatchesSetPass CallsTrianglesProfiler 里盯 GPURendering。这组数字是后面优化的参照,场景布局一变就要重新采一版。
做法 说明
控制器 Resources/Anim 下放 Animator Controller,默认状态指到待机动画 Resources 只是教程写法,项目里可换成 Addressables 等,只要批量实例读得到同一套控制器
预制体 场景里摆好 Animator、缩放和朝向,再拖到 Resources/Prefabs 后面 Instantiate 都从这个预制体来,避免手改场景对象和脚本生成的不一致
批量生成 CreateObject 用双层循环在 XZ 平面上铺实例,父节点挂在 ObjectFather 脚本里字段名叫 xy,实际表示 X 方向个数和 Z 方向个数,和 Vector3 的 y 轴不是一回事
看数据 同上 Stats + Profiler 改预制体、材质、实例数量之前先记一版,方便对比
// 外层 i 沿 X,内层 j 沿 Z;注意字段 y 是「Z 向数量」不是竖直高度
for (int i = 0; i < x; i++)
    for (int j = 0; j < y; j++)
        Instantiate(obj, father.transform).transform.position = new Vector3(i, 0, j);

从动画机到 VAT 像素

枚举 Clip 与帧数

运行时 Animator 上的 runtimeAnimatorController 强转成 AnimatorController,取 layers[0].stateMachine.states 得到 ChildAnimatorState[],再把每个 state.motion 转成 AnimationClip 才能进烘焙循环。motion 也可能是 BlendTree 或别的类型,教程正文只写了纯 AnimationClip 分支,真项目里要按类型拆开处理。

帧数用 Mathf.CeilToInt(clip.frameRate * clip.length) 估一个上限,后面按帧采样时不会少采。

定帧取顶点

对第 j 帧:SampleAnimation(gameObject, j / frameRate) 先把骨骼姿势写到层级上,再 mesh.Clear(false) 清掉上一帧残留,然后 SkinnedMeshRenderer.BakeMesh(mesh),从 mesh.verticesnormalstangents 读出这一帧的蒙皮结果。顺序不能反,BakeMesh 采到的是「当前姿势下」的网格。

角色身上不止一块蒙皮时,每个 SkinnedMeshRenderer 各自烘焙;VAT 里要么分区域写进同一张图,要么拆多张贴图,工具侧要约定好对应关系。

归一化进纹理

第一遍遍历所有 Clip、所有帧、所有顶点,用 Mathf.FloorToInt / CeilToInt 把世界空间分量收进一套全局 minmax。第二遍再对每个分量做 Mathf.InverseLerp,压到 0~1 后塞进 Color 写像素。某一轴 max == min 时要单独分支,避免 InverseLerp 除零。

写 PNG 与导入设置

步骤 要点
建纹理 RGBA32、关 mipmap;Texture2D 构造函数里线性空间那个参数用 true,别把数据当 sRGB 再弯一次
写盘 SetPixel 之后必须 Apply,再 EncodeToPNG + File.WriteAllBytes
导入 AssetDatabase.Refresh 后对贴图跑 TextureImporterUncompressedFilterMode.Point、关 mipmap、关 sRGBTexture,最后 SaveAndReimport
像素坐标 x = index % 宽度y = index / 宽度,注意别写出纹理边界

下面示例只把顶点位置塞进 RGB。法线、切线如果要跟变形走,一般要把 [-1,1] 再映射到 [0,1],而且经常要单独一张数据纹理,和位置 VAT 分开存。

API 或类型 干什么 容易踩的地方
AnimatorControllerChildAnimatorState 从运行时控制器反查编辑器里的状态机 控制器为空、motion 不是 AnimationClip 时要跳过
AnimationClip.SampleAnimation 按时间把姿势写到对象层级 骨骼层级、SkinnedMeshRenderer 要和 Clip 绑定关系一致
SkinnedMeshRenderer.BakeMesh 把当前帧蒙皮结果拷进目标 Mesh 必须出现在 SampleAnimation 之后
Mathf.InverseLerp 把分量归一化到 0~1 教程里三轴共用一对 min/max 是省事写法,精度和语义要不要分轴,按项目自己定
TextureImporter 那一套 保证读回的值就是写进去的数 压缩、双线性、伽马都会把数据弄脏;编辑器里看起来像彩噪点很正常
animationClip.SampleAnimation(targetGameObject, j / animationClip.frameRate);
mesh.Clear(false);
skinnedMeshRenderer.BakeMesh(mesh);
Vector3[] vertices = mesh.vertices;

texture2D.SetPixel(x, y, new Color(nx, ny, nz, 1f));
texture2D.Apply(false, false);
File.WriteAllBytes(path, texture2D.EncodeToPNG());

Shader 实例化、顶点 ID 与 MPB

启用 Instancing

#pragma multi_compile_instancing 打开之后,在 appdatav2f 里声明 UNITY_VERTEX_INPUT_INSTANCE_ID,顶点着色器一进来先 UNITY_SETUP_INSTANCE_ID。如果片元里还要读逐实例属性,就在 vertUNITY_TRANSFER_INSTANCE_IDfrag 开头再 UNITY_SETUP_INSTANCE_ID 一次。

物体空间到裁剪空间用 UnityObjectToClipPos,不要写老的 mul(UNITY_MATRIX_MVP, …),变体和新管线都对不上。

逐实例数据与 VAT

UNITY_INSTANCING_BUFFER_START / UNITY_DEFINE_INSTANCED_PROP / UNITY_ACCESS_INSTANCED_PROP 这一套,在 Shader 里声明的名字要和 C# 侧 MaterialPropertyBlock.SetVectorSetFloat 等对得上,否则实例化批次里参数是乱的。

VAT 这边在顶点输入上加 uint vid : SV_VertexID。它表示当前这一次 DrawCall 提交的顶点流里的序号,只有和烘焙时写纹理用的顶点顺序一致,后面用 vid 去算纹素位置才有意义。

MaterialPropertyBlock 实际干什么

MaterialPropertyBlock 解决的是「不想为每个物体 new Material,又要改这个 Renderer 上的参数」。单靠它并不能减少 DrawCall;要和开启了 GPU Instancing、并且在 UNITY_INSTANCING_BUFFER 里声明了逐实例字段的 Shader 配合,才能在同一材质、不同参数的情况下仍然走实例化那条路径。

宏或 API 放在哪 备注
UNITY_SETUP_INSTANCE_ID vert 开头必调;frag 要用实例属性时也要 没设好实例 ID,UNITY_ACCESS_INSTANCED_PROP 读出来没意义
UNITY_TRANSFER_INSTANCE_ID 片元需要逐实例数据时,在 vert 里传下去 顶点阶段算完就扔、片元不读,可以不传
SV_VertexID VAT 里算纹素偏移 和 Mesh 顶点顺序、SubMesh、合批方式强相关,换网格要重新对表
GetPropertyBlock / SetPropertyBlock 改 MPB 前先 Get 再改再 Set,避免把别的系统写的参数冲掉 属性 ID 用 Shader.PropertyToID 缓存

编辑器 VAT 工具入口

菜单 [MenuItem("VAT Tool/CreateVAT")] 里用 Selection.activeObject as GameObject 拿到当前选中的根节点,后面的烘焙逻辑都从这个根往下走。脚本放在 Editor 目录,引用 UnityEditor,否则会把编辑器 API 编进 Player,构建或运行阶段直接报错。

写文件和走导入器要用两套路径字符串对上:

用途 路径
File.WriteAllBytes Application.dataPath + "/Art/VAT/"
AssetImporter.GetAtPath Assets/Art/VAT/

目标目录要先建好。选中为空、没有 AnimatorruntimeAnimatorController 转不成 AnimatorController、下面找不到 SkinnedMeshRenderer,这类情况直接 LogError 然后 return,比烘出一半坏数据要好查得多。

检查点 为什么看它
选中对象 定死烘焙根,避免烘到场景里别的角色
Animator 没有它就拿不到 AnimatorController 和状态列表
SkinnedMeshRenderer BakeMesh 的数据来源

VAT 纹理排布与 ScriptableObject 配置

贴图里像素的排列顺序,和 AnimatorController 里状态机的遍历顺序一致:先把动画 A 的「每一帧 × 每一个顶点」连续写满,再紧挨着写 B、再写 C。宽度固定(教程里用 2048),按行优先填满。整张图占用的像素总数等于各段动画的 帧数 × 顶点数 之和。一张不够就拆多张 VAT,教程示例单张能装下就没展开。

类型 字段在记什么
AnimationInfo,一段动画 animationNameAnimatorState.name 对齐;frameCount;这段数据在 VAT 里从哪个 pixelIndex 开始;frameRate
AnimationInfos,整张 VAT vertexCount 一般取 sharedMesh.vertexCountvertexMin / vertexMax 用来在 Shader 里反映射;vat_texture 引用烘焙出来的贴图;allAnimationinfo 装所有段的元数据

每写完一段,游标前进 frameCount * vertexCount,和 SetPixelnowPixelIndex 往前走的长度一致,运行时 pixelIndex 才不会指到别人的数据上。

烘焙收尾与资源关联

遍次 内容
第一遍 只算全局 min / max,不写像素
第二遍 一边 SetPixel 一边把对应 AnimationInfopixelIndex、帧数、帧率填好,保证 .asset.png 是同一次烘焙出来的

PNG 导入设置还是前面那套数据贴图流程。用 AssetDatabase.LoadAssetAtPath<Texture2D> 赋给 ScriptableObjectvat_textureAssetDatabase.CreateAsset 写出 xxx_animationInfos.asset,刷新工程后材质球上直接拖引用即可。

VAT Shader 采样与运行时驱帧

Shader 与材质参数分工

示例 Shader 挂在 Unlit/VATShader 上,在已经能跑 Instancing 的版本里换成 UNITY_INSTANCING_BUFFER_START(VAT),里面 _FrameRate_OffsetIndex_PixelIndex_FrameCountMaterialPropertyBlock.SetInt 从 C# 灌进去,和每条 AnimationInfo 对上。_VertexCount_VertexMax_VertexMin 全模型共用,用 Material.SetInt 写一次就行。

索引、采样与顶点输出

顶点函数里拿 uint vid : SV_VertexID,用下面这段 HLSL 把当前帧、当前顶点映射到 VAT 上的 uvtex2Dlod 读出归一化位置,再 vertexPos * (max - min) + min 反映射回物体空间,写回 appdata.vertex,最后走 UnityObjectToClipPos

float index = pixelIndex
    + floor(fmod(_Time.y * frameRate + offsetIndex, frameCount)) * _VertexCount
    + vid;
float2 uv = float2(
    fmod(index, _VAT_Tex_TexelSize.z) * _VAT_Tex_TexelSize.x,
    (index * _VAT_Tex_TexelSize.x) * _VAT_Tex_TexelSize.y);
float3 vertexPos = tex2Dlod(_VAT_Tex, float4(uv, 0, 0));
vertexPos = vertexPos * (_VertexMax - _VertexMin) + _VertexMin;

运行时网格与播放

海量实例这条路上继续用 SkinnedMeshRenderer 播骨骼,和常见的 GPU Instancing 流程很难拧在一起。做法是换成 MeshRenderer + MeshFilter,网格拓扑和烘焙时 BakeMesh 的那份一致,材质勾选 GPU Instancing,顶点动画完全交给 VAT 在着色器里算。

Play(name, isRandom) 按名字在 AnimationInfos 里查到对应的 AnimationInfo。所有实例相同的量用 Material.SetInt 等写在材质上;每个 Renderer 独有的量走 MaterialPropertyBlock,例如给 _OffsetIndex 随机一个初值把动作相位错开,最后对该 Renderer 调用 SetPropertyBlock

说明
逐帧采样 没有插值,镜头贴脸会看到跳变;要平滑只能加帧率或 Shader 里做插值
没有 Animator 状态机 切动画、混合、过渡逻辑要自己写,不能像蒙皮那样拖状态机完事
VAT 体积 顶点多、动画长,单张 2048 不够就拆多张贴图或走数组,Shader 里索引方式要跟着改
法线、切线 本教程顶点阶段只动了位置,正确光照往往还要额外烘焙法线数据或单独方案
阴影 自定义 Shader 通常要自己补 ShadowCaster / DepthOnly 之类 Pass,否则投影或深度不对

16.3 面试题精选

进阶题

1. SampleAnimation 和 BakeMesh 的调用顺序与前提是什么?

题目

用 VAT 烘焙管线说明:为什么先 SampleAnimationBakeMesh?对场景里的对象结构有什么要求?

深入解析
  • SampleAnimation 把指定时间点的骨骼姿势写到 GameObject 子层级,SkinnedMeshRenderer 在该姿势下算出蒙皮后的顶点。
  • BakeMesh 只负责把「当前帧已经算好的蒙皮结果」拷进目标 Mesh;若不先采样,烘焙到的是上一帧或默认姿势。
  • 前提:targetGameObject 上挂的 Animator / 骨骼层级与 AnimationClip 一致,子层级找得到匹配的 SkinnedMeshRenderermotion 必须是能采样的 AnimationClipBlendTree 等需另写分支。
答题示例

先 SampleAnimation 定姿势,再 BakeMesh 把该姿势下的蒙皮顶点落到 Mesh 里。

骨骼层级和蒙皮网格要跟 Clip 对得上,否则采样无效或顶点不对。

参考文章
  • 5.补充知识-如何获取某个动画中所有帧的顶点信息

2. VAT 用的 PNG 为什么要 Uncompressed、Point、关闭 sRGB?

题目

TextureImporter 里这几项如果设错,VAT 会出什么问题?

深入解析
  • 压缩格式会丢失或量化通道值,顶点还原不再可靠。
  • Bilinear / Trilinear 会在采样时混合邻像素,破坏「一像素一份数据」的布局。
  • sRGB 打开会做伽马相关转换,把本来当线性数据存的 RGB 再弯一次,解码顶点时偏移。
  • mipmap 会生成缩小链,VAT 通常按固定分辨率逐像素索引,不需要 mip。
答题示例

这是数据纹理不是贴图,压缩和过滤都会改像素值,sRGB 还会多一次非线性变换。

所以关压缩、用 Point、关 sRGB、关 mipmap,保证读回的值就是写进去的归一化坐标。

参考文章
  • 7.补充知识-如何将颜色信息存储到纹理中

3. GPU Instancing 相关宏在顶点和片元里怎么配合?

题目

UNITY_SETUP_INSTANCE_IDUNITY_TRANSFER_INSTANCE_ID 分别在什么阶段必须调?片元里什么时候还要再调一次?

深入解析
  • #pragma multi_compile_instancing 生成变体后,实例 ID 由运行时注入;UNITY_SETUP_INSTANCE_ID 在着色器入口把当前实例上下文准备好,顶点阶段访问 instanced 属性前必须先执行。
  • 若片元着色器要读 UNITY_ACCESS_INSTANCED_PROP,需要把实例 ID 从顶点传到片元:UNITY_TRANSFER_INSTANCE_ID 写在 vert 里,并在 frag 开头再 UNITY_SETUP_INSTANCE_ID
  • 顶点里不用实例属性、只在片元用贴图采样时,仍要看是否触及 per-instance 数据决定传递链。
答题示例

vert 里最先 SETUP,需要把实例 ID 带到片元就 TRANSFER。

frag 里如果要用 per-instance 属性,再 SETUP 一次接上上下文。

参考文章
  • 8.补充知识-如何让自定义Shader支持GPUInstancing

4. VAT 里 pixelIndex 和状态机动画顺序是什么关系?

题目

AnimationInfo.pixelIndex 是怎么来的?换动画顺序不重新烘焙会怎样?

深入解析
  • 烘焙按 AnimatorStateMachine.states 的顺序依次写像素:每一段从当前 nowAnimationIndex(或等价游标)开始写 frameCount × vertexCount 个像素,并把该值记入 AnimationInfo.pixelIndex
  • 段末游标增加 frameCount * vertexCount,下一段紧接其后;因此状态机在控制器里的顺序就是纹理里的空间顺序。
  • 只改控制器里状态顺序而不重烘焙,会导致 pixelIndex、帧数与纹素内容错位,运行时采样到错误动画或乱顶点。
答题示例

pixelIndex 是这一段动画数据在 VAT 里的起始像素下标,按状态机遍历顺序连续排布。

改状态顺序不重烘焙,配置和纹理对不上,动画就废了。

参考文章
  • 12.具体实现-烘焙部分-动画信息配置
  • 13.具体实现-烘焙部分-生成配置信息

5. 为什么 VAT + Instancing 要用 MeshRenderer 而不是 SkinnedMeshRenderer?

题目

蒙皮角色本来用 SkinnedMeshRenderer,这一实践为什么换成静态网格 + VAT?

深入解析
  • 课文明确:骨骼蒙皮那条渲染路径与「海量实例 + GPU Instancing」的常见用法不匹配,目标是让顶点位置完全由 VAT 在着色器里驱动,CPU 不再逐实例更新骨骼。
  • MeshRenderer + MeshFilter 使用静态网格拓扑,顶点顺序与烘焙 BakeMesh 一致,便于 SV_VertexID 对齐纹素;Instancing 批量时材质与缓冲统一。
  • 代价是失去 Animator 状态机与骨骼插值,切动画、混合、IK 等要自己在逻辑或 Shader 里补。
答题示例

SkinnedMeshRenderer 走骨骼更新,不适合本课要的 Instancing 大批量套路。

换成 MeshRenderer 挂静态网格,顶点动画完全由 VAT 在 GPU 里算,才能大批量合批。

参考文章
  • 15.具体实现-渲染部分-CSharp传参与动画播放实现

深度题

1. MaterialPropertyBlock 能单独降低 DrawCall 吗?和 Instancing 怎么配合?

题目

有人说「用 MPB 就能批量渲染」,你怎么纠正?正确用法是什么?

深入解析
  • MPB 解决的是不克隆材质前提下覆写 Renderer 的参数,省材质实例和内存;每次 Set 的是「这个 Renderer 的材质属性覆盖层」,不自动合并 DrawCall。
  • 正文结论:要和 Shader 里 UNITY_INSTANCING_BUFFER 声明的 per-instance 属性对齐,由同一兼容 Instancing 的材质批量绘制,实例间差异走 instanced prop + MPB,才能在不打断批的前提下做「同材质不同参数」。
  • 若 Shader 未启用 Instancing 或未走相同 batch 条件,MPB 仍可能打断合批,需结合 Frame Debugger 验证。
答题示例

MPB 本身不减少 DrawCall,它避免为每个对象 new 材质。

和启用了 GPU Instancing 的 Shader、以及 instanced 属性字段配合,才能让不同参数仍算在同一类实例化绘制里。

参考文章
  • 10.补充知识-如何使用MaterialPropertyBlock
  • 8.补充知识-如何让自定义Shader支持GPUInstancing

2. 用 SV_VertexID 读 VAT 时,和烘焙阶段的数据要满足什么一致条件?

题目

VAT 贴图里按顶点顺序排像素,运行时 Shader 里用 SV_VertexID 取数,可能踩哪些坑?

深入解析
  • 烘焙时 mesh.vertices 的顺序与运行时提交给 GPU 的顶点流顺序一致时,vid 才能与纹素一一对应;LOD、动态合批、不同 Mesh 合并都会改顺序。
  • VAT 布局若按「帧 × 顶点」或「顶点 × 帧」展开,片元或顶点里还要乘上帧偏移、实例偏移,与 C# 侧 MaterialPropertyBlock 传的索引一致。
  • 多 SubMesh、多材质槽时,DrawCall 拆分后顶点流可能不是整张 VAT 假设的那一段,需要在工程里约定「只对单一 SubMesh、单一 Pass 的 VAT 网格」使用。
答题示例

VertexID 对应当前 Draw 的顶点序号,必须和烘焙时写纹理用的顶点顺序一致。

换 Mesh、合批、改 LOD 都可能让序号对不上,要在工具里固定拓扑和提交顺序。

参考文章
  • 9.补充知识-如何在Shader中区分不同顶点
  • 5.补充知识-如何获取某个动画中所有帧的顶点信息

3. VAT 顶点着色器里 index 公式的每一项,在 C# 里谁负责传?

题目

pixelIndex + floor(fmod(_Time.y * frameRate + offsetIndex, frameCount)) * _VertexCount + vid 中,哪些来自 AnimationInfos / AnimationInfo,哪些是 Unity 内置,哪些适合用 MPB?

深入解析
  • vid 由 GPU 根据当前 Draw 的顶点流给出,无需 C# 传。
  • _Time.y 为 Shader 内置时间;frameRateoffsetIndexpixelIndexframeCount 在课文中通过 MaterialPropertyBlock.SetInt 按实例写入,与 AnimationInfoframeRatepixelIndexframeCount 及随机相位 offsetIndex 对应。
  • _VertexCount_VertexMax_VertexMin 来自 AnimationInfos,课文用 material.SetInt 作为各实例共享数据。
  • 公式与 nowAnimationIndex 烘焙布局一致,才能保证 pixelIndex 与纹素对齐。
答题示例

vid 是 GPU 给的顶点序号;时间在 Shader 里。

帧率、起始像素、总帧、随机偏移用 MPB 从 AnimationInfo 填;顶点数和 min、max 用 Material 写共享的 AnimationInfos。

参考文章
  • 14.具体实现-渲染部分-Shader得到VAT数据并赋值
  • 15.具体实现-渲染部分-CSharp传参与动画播放实现

4. 课文里 VAT 方案的五条「遗留问题」,你会怎么跟策划或引擎组对齐?

题目

不流畅、切换麻烦、多纹理、法线、阴影 Pass——各用什么工程手段缓解?

深入解析
  • 不流畅:提高烘焙帧率、Shader 双帧插值、远景 VAT / 近景蒙皮分级。
  • 切换与循环:自管片段与首尾对齐,Play 关闭随机偏移从首帧播;需要过渡时自写短混合。
  • 多纹理:按容量拆多张 VAT 或纹理数组,SO 与 Shader 增加纹理索引与 UV 换算。
  • 法线切线:额外烘焙数据纹理或简化光照;主角单独高精度方案。
  • 阴影深度:为自定义 Shader 补 ShadowCaster / DepthOnly 等 Pass,或用 Layer 与渲染特征与蒙皮物体区分。
答题示例

近看跳就加插值或加采样率;切动画没有状态机就自己封一层逻辑。

数据装不下就拆多张 VAT;要正确光照就补法线烘焙;要投影就自己加阴影 Pass。

参考文章
  • 15.具体实现-渲染部分-CSharp传参与动画播放实现


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 785293209@qq.com

×

喜欢就点赞,疼爱就打赏