Unity性能优化利器:BakeShader原理、实现与实战应用

📅 2026/7/27 11:18:07
Unity性能优化利器:BakeShader原理、实现与实战应用
1. 项目概述为什么我们需要关注BakeShader在Unity项目开发的后期尤其是涉及到复杂光照、阴影和全局光照GI的场景性能优化往往是一个绕不开的坎。很多开发者都遇到过这样的场景在编辑器里跑得挺流畅一打包到移动端或者主机平台帧率就直线下降。这时候你打开Profiler大概率会发现GPU的瓶颈卡在了实时渲染那些复杂的光照计算上。对于静态或半静态的场景元素比如建筑、地形、大部分的道具它们的光照信息其实在运行时是基本不变的。那么一个很自然的想法就是能不能把这些昂贵的计算提前做好在运行时直接使用结果这就是“烘焙”Baking的核心价值。传统的Unity光照烘焙Lightmapping大家应该都不陌生它生成的是存储光照颜色信息的贴图Lightmap。这对于漫反射光照效果很好但对于一些需要高光、法线细节或者特殊着色效果的表面仅靠Lightmap就显得力不从心了。这时我们就需要更灵活、更强大的烘焙方案。BakeShader顾名思义就是一套专注于“烘焙”的Shader工具或框架。它允许你将任何自定义Shader的复杂渲染结果不仅仅是光照可以是任何计算预先渲染到纹理上从而在运行时用极其廉价的采样操作替代复杂的实时计算。这不仅仅是优化更是一种设计范式的转变让你能把美术效果和运行性能更好地解耦。简单来说BakeShader解决的核心痛点是在保证甚至提升视觉质量的前提下将GPU从繁重的每帧计算中解放出来特别适用于对性能有严苛要求的移动平台、VR/AR应用以及大型开放世界场景。无论是想实现电影级的静态场景细节还是为低端设备做深度优化深入理解并应用BakeShader都是一项极具价值的技术。2. BakeShader的核心原理与工作流程拆解要理解BakeShader我们得先抛开“Shader就是用来实时渲染的”这个固有观念。在这里Shader扮演的是一个“离线计算器”的角色。其核心原理可以概括为利用渲染管线将场景中特定物体在其最终显示状态下通过你指定的Shader程序所计算出的像素颜色或其他数据如法线、深度一次性渲染到一张或多张纹理即烘焙贴图上。2.1 与传统光照烘焙的异同很多人会把BakeShader和Unity的Lightmapping搞混它们有交集但目标和能力范围不同。Unity Lightmapping (光照贴图)目标主要烘焙静态物体的间接光照全局光照和直接光照的阴影。输出通常是RGB颜色贴图存储光照颜色和方向贴图存储主要光照方向用于细节法线。局限烘焙流程相对固定主要针对标准渲染管线或URP/HDRP的内置光照模型。对于自定义的高光、边缘光、程序化纹理等复杂着色效果它无法直接烘焙。BakeShader (着色器烘焙)目标烘焙任意Shader的完整输出结果。这可以是一个复杂的PBR材质包含漫反射、法线、高光、粗糙度、金属度等所有信息的合成颜色也可以是一个风格化的卡通着色效果甚至是一张存储了世界位置、顶点颜色等数据的“数据贴图”。输出完全由你定义的Shader决定。可以是一张RGBA的Albedo贴图一张存储了法线和高度的RGBA贴图或者多张不同用途的贴图集合。优势极度灵活。你可以为美术资产定制最华丽的Shader来追求极致效果然后通过烘焙将其“固化”为贴图运行时使用一个极其简单的、只做纹理采样的Unlit Shader来显示。性能开销降至最低。2.2 通用工作流程解析一个典型的BakeShader工作流包含以下几个关键步骤理解每一步的意图比记住具体操作更重要准备源模型与目标模型源模型 (High-poly / Source Mesh)这是拥有高细节、复杂材质的模型。它的UV可以不是最优的但需要包含所有我们想烘焙的视觉细节。目标模型 (Low-poly / Target Mesh)这是最终在游戏中使用的、面数较低的模型。它需要有一套干净、舒展且不重叠的UV通常称为“光照贴图UV”或第二套UV用于承载烘焙出来的贴图。烘焙的本质就是将高模的细节“投影”或“渲染”到低模的UV空间上。创建烘焙用Shader这不是一个运行时Shader而是一个专门用于烘焙过程的Shader。它通常会接收高模的各类输入顶点色、多套UV、切线空间等并按照你希望最终呈现的效果进行计算。例如如果你想烘焙一个包含法线贴图细节的完整颜色你的烘焙Shader就需要采样法线贴图并在Shader中进行光照计算使用一个固定的灯光方向最后输出颜色。配置烘焙渲染器与相机在代码中你需要创建一个临时的相机和渲染纹理RenderTexture。相机的渲染目标就是这个RenderTexture。关键操作是将目标模型的每个UV坐标映射为相机渲染空间中的一个像素。这通常通过将目标模型的顶点按照其UV坐标直接变换到NDC标准化设备坐标空间来实现使得模型恰好填满整个渲染视口。然后用这个相机、使用烘焙Shader去渲染源模型。这样渲染纹理上的每一个像素就精确对应了目标模型UV图上的一个点并且其颜色是源模型在该处被烘焙Shader渲染的结果。执行烘焙与后处理触发渲染。将渲染纹理中的像素数据读取出来保存为常见的图片格式如PNG、TGA。可能需要进行后处理如抗锯齿通过超级采样、色彩空间转换线性到sRGB或者将多张渲染纹理如颜色、法线、金属度打包成一张贴图集。在运行时使用烘焙结果为你的目标模型创建一个新的、极其简单的材质使用一个Unlit Shader或者标准Shader关闭实时光照。将烘焙好的贴图赋予这个材质。运行时GPU只需要进行一次纹理采样就能呈现出原本需要复杂计算才能得到的效果。注意市面上也有一些成熟的第三方工具或插件如TextureBaker、Bakery等封装了上述流程。但理解底层原理能帮助你在工具出错时进行排查也能让你有能力定制特殊的烘焙需求。3. 手动实现一个基础的BakeShader工具理解了原理我们不妨动手写一个简化版的烘焙工具核心代码。这将帮助你彻底吃透这个过程。我们将实现将一个带有法线贴图的高模细节烘焙到低模的漫反射贴图上。3.1 创建烘焙Shader首先我们需要一个用于烘焙的Shader。这个Shader在烘焙时运行模拟一次光照计算。// BakeShader.shader Shader Hidden/BakeDiffuseWithNormal { Properties { _MainTex (Albedo (RGB), 2D) white {} _BumpMap (Normal Map, 2D) bump {} _BakeLightDir (Bake Light Direction, Vector) (0.577, 0.577, 0.577, 0) // 归一化的光照方向 _BakeLightColor (Bake Light Color, Color) (1,1,1,1) } SubShader { Tags { RenderTypeOpaque } LOD 100 Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #include UnityCG.cginc struct appdata { float4 vertex : POSITION; float3 normal : NORMAL; float4 tangent : TANGENT; float2 uv : TEXCOORD0; }; struct v2f { float2 uv : TEXCOORD0; float3 worldNormal : TEXCOORD1; float3 worldTangent : TEXCOORD2; float3 worldBitangent : TEXCOORD3; float4 vertex : SV_POSITION; }; sampler2D _MainTex; float4 _MainTex_ST; sampler2D _BumpMap; float4 _BumpMap_ST; float3 _BakeLightDir; float4 _BakeLightColor; v2f vert (appdata v) { v2f o; o.vertex UnityObjectToClipPos(v.vertex); o.uv TRANSFORM_TEX(v.uv, _MainTex); // 计算世界空间的TBN矩阵用于法线贴图变换 float3 worldNormal UnityObjectToWorldNormal(v.normal); float3 worldTangent UnityObjectToWorldDir(v.tangent.xyz); float3 worldBitangent cross(worldNormal, worldTangent) * v.tangent.w; o.worldNormal worldNormal; o.worldTangent worldTangent; o.worldBitangent worldBitangent; return o; } fixed4 frag (v2f i) : SV_Target { // 采样基础颜色 fixed4 albedo tex2D(_MainTex, i.uv); // 采样法线贴图并解包到切线空间 float3 tangentNormal UnpackNormal(tex2D(_BumpMap, i.uv)); // 构建TBN矩阵将切线空间法线转换到世界空间 float3x3 TBN float3x3(normalize(i.worldTangent), normalize(i.worldBitangent), normalize(i.worldNormal)); float3 worldNormal mul(TBN, tangentNormal); worldNormal normalize(worldNormal); // 简单的兰伯特漫反射光照计算使用烘焙时设定的固定光源 float NdotL max(0, dot(worldNormal, _BakeLightDir)); fixed3 diffuse albedo.rgb * _BakeLightColor.rgb * NdotL; // 输出最终颜色可以加上环境光等这里简化 return fixed4(diffuse, albedo.a); } ENDCG } } }这个Shader的关键在于它不依赖于场景中的实时灯光而是使用_BakeLightDir和_BakeLightColor这两个属性来定义一个固定的光源方向进行光照计算。烘焙时我们会用这个Shader去渲染高模。3.2 编写C#烘焙脚本接下来是驱动烘焙过程的C#脚本。这个脚本的核心是创建一个临时的相机和RenderTexture并按照目标模型的UV进行渲染。// SimpleTextureBaker.cs using UnityEngine; using System.Collections; using System.IO; public class SimpleTextureBaker : MonoBehaviour { public GameObject sourceMeshObject; // 拥有高模和复杂材质的物体 public GameObject targetMeshObject; // 最终使用的低模物体 public string bakeShaderName Hidden/BakeDiffuseWithNormal; public int textureSize 1024; // 烘焙贴图尺寸 public string savePath Assets/BakedTextures/; // 保存路径 public string textureName BakedDiffuse; // 贴图名称 [Header(Bake Light Settings)] public Vector3 lightDirection new Vector3(0.577f, 0.577f, 0.577f).normalized; public Color lightColor Color.white; private Shader bakeShader; private Material bakeMaterial; private Camera bakeCamera; private RenderTexture renderTexture; [ContextMenu(Bake Texture)] public void Bake() { if (sourceMeshObject null || targetMeshObject null) { Debug.LogError(Source or Target Mesh Object is not assigned!); return; } // 1. 加载烘焙Shader并创建临时材质 bakeShader Shader.Find(bakeShaderName); if (bakeShader null) { Debug.LogError($Bake Shader {bakeShaderName} not found!); return; } bakeMaterial new Material(bakeShader); // 2. 设置烘焙光源参数到材质 bakeMaterial.SetVector(_BakeLightDir, lightDirection.normalized); bakeMaterial.SetColor(_BakeLightColor, lightColor); // 3. 将源物体的材质属性复制到烘焙材质这里简化假设只有一个材质 Renderer sourceRenderer sourceMeshObject.GetComponentRenderer(); if (sourceRenderer ! null sourceRenderer.sharedMaterial ! null) { Material sourceMat sourceRenderer.sharedMaterial; if (sourceMat.HasProperty(_MainTex)) bakeMaterial.SetTexture(_MainTex, sourceMat.GetTexture(_MainTex)); if (sourceMat.HasProperty(_BumpMap)) bakeMaterial.SetTexture(_BumpMap, sourceMat.GetTexture(_BumpMap)); // 复制其他需要的属性... } // 4. 创建临时相机和RenderTexture GameObject cameraGo new GameObject(BakeCamera); bakeCamera cameraGo.AddComponentCamera(); bakeCamera.enabled false; // 我们不希望它自动渲染 bakeCamera.clearFlags CameraClearFlags.SolidColor; bakeCamera.backgroundColor Color.black; // 背景设为黑色或透明色 renderTexture new RenderTexture(textureSize, textureSize, 24, RenderTextureFormat.ARGB32); renderTexture.Create(); bakeCamera.targetTexture renderTexture; // 5. 关键步骤设置相机投影矩阵使其渲染与目标UV 1:1对应 // 思路将目标模型的UV坐标0,1范围映射到相机的NDC-1,1范围。 // 一种常见方法是使用正交相机并使其渲染范围恰好覆盖UV的(0,0)到(1,1)。 bakeCamera.orthographic true; bakeCamera.orthographicSize 0.5f; // 正交大小设为0.5这样从中心到上下边界是0.5总高度是1。 bakeCamera.aspect 1.0f; // 宽高比设为1因为UV是正方形的。 // 设置相机位置和看向方向使其正对渲染平面这里简化处理实际可能需要根据目标模型包围盒调整 cameraGo.transform.position new Vector3(0.5f, 0.5f, -1); // 看向UV空间中心点(0.5,0.5) cameraGo.transform.rotation Quaternion.LookRotation(Vector3.forward); // 6. 创建一个临时物体来渲染源模型使用烘焙材质 GameObject tempRenderGo Instantiate(sourceMeshObject); Renderer tempRenderer tempRenderGo.GetComponentRenderer(); if (tempRenderer ! null) { tempRenderer.material bakeMaterial; // 临时替换为烘焙材质 } // 7. 执行渲染 bakeCamera.Render(); // 8. 从RenderTexture读取数据并保存为PNG SaveRenderTextureToPNG(renderTexture); // 9. 清理临时对象 DestroyImmediate(tempRenderGo); DestroyImmediate(cameraGo); if (Application.isEditor) { DestroyImmediate(bakeMaterial); } else { Destroy(bakeMaterial); } renderTexture.Release(); Debug.Log(Baking Complete! Texture saved to: savePath textureName .png); } void SaveRenderTextureToPNG(RenderTexture rt) { // 确保目录存在 if (!Directory.Exists(savePath)) { Directory.CreateDirectory(savePath); } RenderTexture.active rt; Texture2D tex2D new Texture2D(rt.width, rt.height, TextureFormat.ARGB32, false); tex2D.ReadPixels(new Rect(0, 0, rt.width, rt.height), 0, 0); tex2D.Apply(); RenderTexture.active null; byte[] bytes tex2D.EncodeToPNG(); string fullPath Path.Combine(savePath, textureName .png); File.WriteAllBytes(fullPath, bytes); // 在Unity编辑器中刷新资源数据库 #if UNITY_EDITOR UnityEditor.AssetDatabase.Refresh(); #endif DestroyImmediate(tex2D); } }3.3 使用步骤与参数解析场景准备在场景中放置你的高模sourceMeshObject和低模targetMeshObject。低模需要有良好的第二套UV。脚本挂载将SimpleTextureBaker脚本挂载到一个空物体上。参数配置Source Mesh Object拖入你的高模游戏物体。Target Mesh Object拖入你的低模游戏物体。Texture Size烘焙贴图的分辨率。根据低模的UV面积和所需精度来定通常为1024或2048。Bake Light Settings这里设置一个固定的光源方向和颜色用于烘焙时的光照计算。(0.577, 0.577, 0.577)是一个来自左上方的经典光源方向。执行烘焙在Inspector窗口点击脚本上的Bake Texture按钮[ContextMenu]特性生成。脚本会自动执行上述流程并在Assets/BakedTextures/文件夹下生成一张PNG贴图。应用烘焙结果将生成的贴图赋给低模模型的一个新材质使用Standard或Unlit Shader。在运行时这个低模就能呈现出接近高模带有法线光照细节的视觉效果而性能消耗极低。实操心得这个简易版本为了清晰省略了很多细节比如处理多子网格SubMesh、多材质球、抗锯齿、处理透明通道、更精确的UV到相机的映射等。在实际项目中你可能需要处理目标模型的UV包围盒动态计算正交相机的位置和大小以确保UV的每一个角落都被正确渲染。此外对于复杂的模型一次烘焙可能不够需要分块Chunk烘焙或使用更专业的UV展开工具。4. 进阶应用场景与方案选型掌握了基础原理后BakeShader的用武之地就非常广阔了。它不仅仅是为了优化更能开启一些独特的视觉效果实现方式。4.1 场景一复杂材质效果的移动端优化这是最经典的应用。你的美术制作了一个拥有多层混合、视差遮挡、动态苔藓等效果的顶级材质在PC上跑都吃力更别说手机了。方案在PC或开发机上使用一个复刻了所有复杂效果的烘焙Shader为这个模型烘焙出一套贴图。这套贴图可能包括AlbedoBaked包含所有颜色、纹理混合、光照信息的最终基础色。EmissiveBaked烘焙的自发光部分。可选PackedMap将粗糙度、金属度、环境光遮蔽AO等单通道信息打包到一张贴图的RGBA通道中。运行时移动端Shader只需采样1-2张贴图进行最简单的计算。视觉损失极小性能提升巨大。4.2 场景二程序化生成内容的预计算你在做一个无限地形系统地形材质由多种噪声图混合而成并随着高度、坡度变化。每帧实时混合这些噪声图开销很大。方案为地形区块Chunk预计算一张“材质索引图”或直接烘焙出最终的颜色/法线贴图。烘焙Shader的输入是世界的XZ坐标或基于此计算的噪声输出是混合后的材质权重或颜色。运行时地形Shader只需采样这张预烘焙的贴图无需进行复杂的噪声计算和混合。这对于开放世界游戏的地形渲染优化至关重要。4.3 场景三风格化渲染的“固化”卡通渲染Cel-shading通常需要实时计算光照的阶跃、描边等。在低端设备或大量角色同屏时这可能成为瓶颈。方案为角色模型烘焙多张“色阶贴图”。例如固定3个主光源方向前、左、后分别烘焙出亮部、中间调、暗部三张颜色贴图。运行时使用一个简化版的卡通Shader根据实时光源方向在这三张贴图之间进行插值或选择来代替实时的光照计算和阶跃判断。描边信息也可以烘焙到一张贴图的Alpha通道中。4.4 场景四光照探针与Lightmap的补充Unity的光照探针Light Probes为动态物体提供了间接光照但它是基于球谐函数的低频近似缺乏高频细节。对于需要精确镜面高光的动态物体效果不佳。方案将动态物体视为“静态”为其烘焙一张包含精确高光信息的“反射探针贴图”或“高光遮罩贴图”。烘焙时Shader可以采样场景的反射探针Reflection Probe和光照贴图计算出精确的高光颜色和强度。运行时动态物体的Shader采样这张烘焙贴图再结合实时直接光就能获得既有动态性又有高质量静态光照细节的效果。4.5 工具选型手动编码 vs. 第三方插件手动编码如上面的示例优点完全可控深度定制理解底层零成本。缺点开发周期长容易出错功能不完善如抗锯齿、UV展开、批量处理等。适用有特殊定制需求、项目规模较小或作为学习研究。第三方插件如TextureBaker, Bakery, Mesh Baker优点功能强大且全面支持多模型合并、UV自动展开、抗锯齿、AO烘焙等有图形界面GUI工作流成熟节省大量开发时间。缺点需要付费可能对项目管线有侵入性遇到极限情况可能受限于插件功能。适用中大型商业项目追求稳定高效的生产流水线。我的建议是对于严肃的商业项目尤其是美术资源量大的项目强烈建议使用成熟的第三方插件。它们解决了很多工程上的“脏活累活”稳定性更高。手动实现更适合作为技术储备用于解决插件无法覆盖的特定难题。5. 常见问题、性能陷阱与排查技巧在实际应用BakeShader的过程中你会遇到各种各样的问题。下面是一些典型问题及其解决思路很多都是我在项目中踩过的坑。5.1 烘焙贴图出现接缝、错位或拉伸这是最常见的问题根源几乎都出在UV上。问题诊断检查目标模型的UV确保用于烘焙的第二套UV通常叫UV1或Lightmap UV是完全展开、没有重叠、且边界留有足够padding通常2-4个像素的。UV islands之间不要靠得太近。检查UV是否在0-1范围内烘焙相机通常只渲染UV00到11这个区域。如果模型的UV坐标超出了这个范围超出的部分就不会被渲染或者会导致重复平铺如果Wrap Mode是Repeat。检查烘焙相机的投影设置在手动实现的工具中确保相机正交投影的orthographicSize和aspect设置正确并且相机对准了UV空间的中心0.5 0.5。一个快速的调试方法是用一个纯色的Shader烘焙看生成的贴图是否均匀地覆盖了整个UV空间。解决方案使用专业的3D软件如Maya, 3ds Max, Blender或Unity的模型导入设置中的“Generate Lightmap UVs”功能重新生成干净的第二套UV。在烘焙工具的代码中根据目标模型UV的实际边界mesh.uv.bounds来动态计算相机的投影矩阵和位置而不是假设UV总是(0,0)到(1,1)。增加烘焙贴图的分辨率并在Shader中使用tex2D时加上微小的偏移如半个纹素有时可以缓解因精度问题导致的接缝。5.2 烘焙结果与实时渲染效果不一致这通常是因为烘焙环境和运行时环境不一致。光照不一致烘焙Shader中使用的光源方向、颜色、强度是否与游戏场景中的主光源匹配环境光Ambient和天空盒Skybox是否考虑进去了确保烘焙时模拟了游戏中的光照条件。Shader不一致你的烘焙Shader是否完美复刻了运行时复杂Shader的所有计算包括纹理混合模式、Alpha测试/混合、顶点动画如果烘焙的是静态帧等。任何细微差别都会导致结果不同。材质属性不一致检查所有材质属性纹理、颜色、浮点数参数是否都正确地从源材质传递到了烘焙材质。在脚本中遍历并复制所有属性是一个好习惯。5.3 烘焙过程耗时过长或内存占用高烘焙尤其是高分辨率、多物体的烘焙是一个资源密集型操作。优化策略分块烘焙不要试图一次性烘焙整个巨大场景。将场景按区域或按物体类型分块分批烘焙。降低烘焙分辨率对于远景或小物体使用较低的贴图分辨率。可以制定一个分级标准如主角2048主要道具1024环境装饰512。使用渐进式烘焙或差值烘焙对于变化不大的部分可以只烘焙变化区域Dirty Region。一些高级插件支持此功能。管理临时资源在烘焙脚本中确保及时销毁临时创建的Camera、RenderTexture、Material和GameObject。使用DestroyImmediate编辑器下或Destroy并调用RenderTexture.Release()。异步操作对于需要烘焙大量资产的情况考虑将烘焙操作放在后台线程或协程中分帧进行避免主线程卡死。5.4 在移动设备上使用烘焙贴图后出现性能回退这听起来反直觉但确实可能发生。原因分析贴图尺寸过大一张4096x4096的贴图即使在内存中其采样带宽和缓存压力也可能超过进行一些简单实时计算的开销。特别是对于很多小物体每个都用大贴图是极大的浪费。过度绘制Overdraw如果烘焙贴图包含透明通道且渲染顺序设置不当可能导致严重的过度绘制。Shader复杂度转移虽然片段着色器简化了但顶点着色器或后处理可能引入了新的开销。或者为了使用烘焙贴图你启用了之前关闭的功能如雾效、动态合批打断等。解决方案严格管理贴图尺寸和格式使用ASTC、ETC2等移动端压缩格式。使用纹理图集Texture Atlas将多个小物体的烘焙贴图合并到一张大图上减少Draw Call和纹理切换。性能分析使用Unity Profiler和Frame Debugger。对比使用烘焙贴图前后的GPU耗时、内存占用、Draw Call和SetPass Call数量。找到真正的瓶颈。简化运行时Shader确保用于显示烘焙贴图的Shader是真正的“轻量级”。避免不必要的计算、纹理采样和分支。5.5 烘焙贴图在特定角度或光照下“发平”或“变暗”这通常是因为烘焙的光照信息是“固化”的无法响应运行时光照的变化。问题本质你烘焙的是“光照结果”而不是“材质属性”。例如你烘焙了一个在左上角光源下的漫反射颜色。当游戏中的光源移动到右侧时模型本该变亮的部分右侧却因为烘焙了暗部的颜色而显得暗。解决方案烘焙“光照无关”的信息不要烘焙包含直接光照漫反射的颜色。改为烘焙Albedo反照率、法线、粗糙度、金属度、环境光遮蔽AO等材质属性。然后在运行时使用标准PBR光照模型结合这些属性和实时灯光进行计算。这样模型就能正确响应动态光照了。这才是更现代、更灵活的用法。使用光照贴图Lightmap处理静态间接光将动态直接光和静态间接光分离。静态间接光包括天空光、反射光、全局光照用Unity的Lightmap烘焙。动态物体则使用实时直接光烘焙的材质属性Light Probe。这样既能保证动态性又能有丰富的间接光照细节。BakeShader是一个强大的“时间换空间”策略。它要求我们在开发管线中投入更多的前期准备和烘焙时间来换取运行时极致的性能红利。它不是一个“银弹”需要根据项目具体需求目标平台、美术风格、场景复杂度来谨慎设计和实施。当你被实时渲染的性能压得喘不过气时回头看看BakeShader它很可能就是那把帮你打开瓶颈的钥匙。从我个人的经验来看在移动端项目的中后期系统地引入BakeShader对静态和半静态资产进行优化往往是性能提升最显著、性价比最高的手段之一。关键在于要建立一套可靠、自动化的烘焙流水线并让美术同学从一开始就在资源制作规范中考虑烘焙的需求比如准备好低模和干净的UV。