
1. 项目概述为什么Unity Shader有三种写法刚接触Unity Shader开发的朋友可能都经历过一个困惑期网上找的教程有的代码写在Surface Shader里有的写在Vertex Fragment Shader里还有的直接用Shader Graph拖节点。到底该学哪个哪种才是“正统”这其实不是一个选择题而是一个“在什么场景下用什么工具”的工程思维问题。今天我们就来彻底拆解Unity中编写Shader的三种核心方式表面着色器Surface Shader、顶点片元着色器Vertex Fragment Shader以及可视化着色器Shader Graph。理解它们各自的定位、底层原理和适用场景不仅能帮你快速上手更能让你在未来的项目里面对任何渲染需求都能游刃有余地选择最合适的工具而不是被工具限制住思路。简单来说这三种方式代表了Unity为不同层级的开发者、不同复杂度的需求提供的三种“接口”。Surface Shader是Unity封装好的“高级API”让你用最少的代码实现基于物理的光照Vertex Fragment Shader是更底层的“汇编语言”给你完全的控制权去实现任何天马行空的效果而Shader Graph则是“可视化脚本”让美术和策划也能参与到Shader创作中极大地提升了原型验证和迭代的效率。接下来我会结合我踩过的无数个坑带你从原理到实操彻底搞懂这三种方式。2. 核心思路与方案选型三种方式的本质区别在深入代码之前我们必须先建立一个核心认知这三种方式不是三种不同的Shader语言而是三种不同的Shader编写框架或工作流。它们的底层最终都会被Unity编译成平台相关的着色器代码如HLSL for DirectX, GLSL for OpenGL等。选择哪种方式取决于你的目标是追求开发效率、追求极致控制还是追求团队协作的便捷性。2.1 表面着色器Surface Shader面向光照的快速原型工具Surface Shader是Unity自己创造的一个抽象层。它的设计哲学是“你只需要告诉我物体表面的属性颜色、法线、光滑度等我来帮你处理复杂的光照计算。” 这听起来非常诱人也确实如此。当你需要实现一个标准的、带光照的材质比如石头、金属、皮肤时Surface Shader能让你在二三十行代码内就得到一个看起来不错的效果。它的工作原理是你编写一个surf函数输出一个SurfaceOutput结构体包含Albedo、Normal、Emission等。Unity的预处理工具会根据你启用的光照模型如Standard, Lambert, BlinnPhong自动为你生成完整的顶点-片元着色器代码其中包括了处理多光源、阴影、光照贴图等繁琐但必需的步骤。注意很多新手误以为Surface Shader是一种独立的语言或性能更差。其实它只是一个代码生成器。你写的Surface Shader代码在导入Unity时会被展开成数百行的Vertex Fragment Shader代码。因此它的性能开销在于生成的代码可能不如手写的精简但对于大多数标准光照场景其性能是完全可接受的而它带来的开发效率提升是巨大的。2.2 顶点片元着色器Vertex Fragment Shader手握渲染的终极控制权这是最传统、最接近图形API原生的编写方式。在这里你需要完整地定义两个函数vert顶点着色器和frag片元着色器。vert函数负责处理每个顶点的位置、法线、UV等数据frag函数则决定屏幕上每个像素更准确说是片元最终输出的颜色。选择这种方式通常意味着你需要实现非真实感渲染NPR效果比如卡通着色、水墨风格、像素化等。这些效果的光照计算往往与物理规律不同Surface Shader的固定光照模型无法满足。你需要对顶点数据进行特殊处理比如顶点动画草地的摆动、旗帜的飘扬、曲面细分、或者自定义的顶点变形效果。你对性能有极致的苛求你可以手动优化每一个计算步骤移除任何不需要的特性如某些光照分支写出最紧凑高效的代码。你在学习图形学原理这是理解渲染管线最直接的途径。它的缺点也很明显你需要自己处理多光源、阴影等复杂特性代码量会大很多。2.3 可视化着色器Shader Graph团队协作与快速迭代的革命Shader Graph是Unity官方推出的可视化着色器编辑工具。它完全摒弃了代码采用节点连接的方式构建Shader逻辑。每个节点代表一个操作如纹理采样、数学运算、向量计算通过连线来定义数据流。它的核心优势在于直观可视效果实时反馈参数调整立竿见影非常适合美术人员独立创作材质。降低门槛无需记忆语法和API通过连接节点就能理解Shader的数据流向学习曲线平缓。便于协作与复用可以将复杂的节点网络封装成Sub Graph子图像函数一样在不同Shader中复用极大提升了资产的模块化程度。与渲染管线深度集成无论是内置渲染管线、URPUniversal Render Pipeline还是HDRPHigh Definition Render Pipeline都有对应的Shader Graph版本确保了效果的一致性。但它并非万能。对于极其复杂、需要精细算法控制或大量动态分支的逻辑节点图可能会变得异常庞大和难以维护此时代码反而更清晰。3. 核心细节解析与实操要点理解了宏观区别我们深入到每一种方式的核心细节和实操中必然会遇到的“坑”。3.1 Surface Shader 的骨架与“魔法”属性一个最简单的Surface Shader模板如下Shader Custom/MySurfaceShader { Properties { _MainTex (Albedo (RGB), 2D) white {} _Glossiness (Smoothness, Range(0,1)) 0.5 _Metallic (Metallic, Range(0,1)) 0.0 } SubShader { Tags { RenderTypeOpaque } CGPROGRAM // 表面着色器编译指令使用Standard光照模型 #pragma surface surf Standard fullforwardshadows // 声明Properties中定义的变量 sampler2D _MainTex; float _Glossiness; float _Metallic; // 输入结构体由Unity自动填充 struct Input { float2 uv_MainTex; }; // 核心的surf函数 void surf (Input IN, inout SurfaceOutputStandard o) { // 采样纹理作为漫反射颜色 fixed4 c tex2D (_MainTex, IN.uv_MainTex); o.Albedo c.rgb; // 设置金属感和光滑度 o.Metallic _Metallic; o.Smoothness _Glossiness; o.Alpha c.a; } ENDCG } FallBack Diffuse }关键点解析#pragma surface指令这是Surface Shader的“灵魂”。它告诉Unity如何生成代码。surf是你的函数名Standard是光照模型fullforwardshadows表示包含完整的正向渲染阴影支持。你可以在这里添加很多选项比如addshadow为自定义顶点修改生成阴影投射Pass、vertex:vert指定自定义的顶点函数。Input结构体你需要什么数据就在这里声明。常见的成员有uv_MainTex纹理坐标、worldPos世界空间位置、worldNormal世界空间法线需配合INTERNAL_DATA使用。Unity会根据你的声明在生成的顶点着色器中为你计算好这些数据。SurfaceOutputStandard结构体这是面向基于物理渲染PBR的输出结构。如果你使用非PBR的光照模型如Lambert对应的输出结构是SurfaceOutput。实操心得性能提示在Input中声明不必要的变量如worldPos会增加顶点着色器的计算量。只声明你真正在surf函数中用到的。自定义光照模型你可以写自己的光照函数并在#pragma surface指令中指定lighting YourLightingModel。这是从Surface Shader进阶到高级光照的必经之路。透明度与混合处理透明物体时需要正确设置Tags中的QueueTransparent和RenderTypeTransparent并在#pragma指令中加入alpha选项如alpha:blend并在surf函数中正确输出o.Alpha。3.2 Vertex Fragment Shader 的完全控制这是一个标准模板Shader Custom/MyVertexFragmentShader { Properties { _MainTex (Texture, 2D) white {} _Color (Color, Color) (1,1,1,1) } SubShader { Tags { RenderTypeOpaque } Pass { // 一个Pass代表一次渲染绘制 CGPROGRAM #pragma vertex vert #pragma fragment frag #include UnityCG.cginc struct appdata { float4 vertex : POSITION; float2 uv : TEXCOORD0; }; struct v2f { float2 uv : TEXCOORD0; float4 vertex : SV_POSITION; }; sampler2D _MainTex; float4 _MainTex_ST; // 纹理的缩放偏移值 fixed4 _Color; v2f vert (appdata v) { v2f o; // 将顶点从模型空间转换到裁剪空间这是必须的一步 o.vertex UnityObjectToClipPos(v.vertex); // 应用纹理的缩放和偏移 o.uv TRANSFORM_TEX(v.uv, _MainTex); return o; } fixed4 frag (v2f i) : SV_Target { // 采样纹理并乘以颜色 fixed4 col tex2D(_MainTex, i.uv) * _Color; return col; } ENDCG } } }关键点解析语义SemanticsPOSITION,TEXCOORD0,SV_POSITION,SV_Target这些冒号后的标识就是语义。它们告诉GPU这个变量是做什么用的。例如顶点着色器必须输出一个带有SV_POSITION语义的变量表示裁剪空间坐标。坐标空间变换UnityObjectToClipPos(v.vertex)是核心中的核心。它一次性完成了从模型空间 - 世界空间 - 观察空间 - 裁剪空间的转换。新手常犯的错误就是自己用矩阵去乘结果往往不对直接使用这个内置函数最安全可靠。v2f结构体顶点着色器和片元着色器之间的桥梁。你在vert中计算好需要传递给frag的数据如UV、法线、光照信息通过这个结构体传递。实操心得多Pass渲染一个SubShader中可以包含多个Pass每个Pass都会执行一次渲染。常用于实现轮廓光、透明叠加等效果。但要注意多个Pass会显著增加Draw Call需谨慎使用。内置包含文件#include UnityCG.cginc引入了大量工具函数和宏如UnityObjectToClipPos,TRANSFORM_TEX。Lighting.cginc包含了光照相关函数。熟悉这些文件能极大提升编码效率。自定义数据结构appdata和v2f结构体你可以随意扩展加入切线、顶点色等数据只要在模型数据中存在且语义正确即可。3.3 Shader Graph 的节点化思维在Shader Graph中你不再编写代码而是与节点图交互。核心操作包括创建属性Property在Blackboard面板中创建这对应代码Shader中的Properties块。你可以定义颜色、纹理、浮点数等并设置默认值。使用节点Node从右键菜单搜索并添加节点。例如Sample Texture 2D节点用于纹理采样Multiply节点进行乘法运算。连接节点用线将节点的输出端口Output连接到另一个节点的输入端口Input。数据从左侧流向右侧最终连接到Master Stack主堆栈的相应输入口如Base Color,Normal。主堆栈Master Stack这是Shader Graph的最终输出目标它定义了着色器的表面类型不透明/透明、混合模式等并汇集了颜色、法线、金属度、光滑度、自发光等所有表面信息。实操心得善于使用Sub Graph如果你发现某一部分节点网络比如一个复杂的噪声图案生成器会在多个Shader中用到果断将其创建为Sub Graph。这就像编程中的函数能保持主图的整洁和项目的可维护性。理解数据流Shader Graph是强类型的。连接线有颜色如绿色是浮点蓝色是向量/颜色连接不匹配的类型会报错。这强迫你清晰地思考每一步操作的数据类型。与脚本交互Shader Graph中创建的属性可以通过脚本使用Material.SetFloat、Material.SetColor等方式进行动态修改实现运行时效果变化。针对管线选择模板创建Shader Graph时务必选择正确的渲染管线模板URP或HDRP。不同管线的Master Stack和可用节点有差异用错了模板会导致编译错误或效果异常。4. 三种方式的横向对比与选型指南光知道怎么用还不够我们必须知道什么时候该用哪个。下面这个表格是我根据多年项目经验总结的选型指南特性维度表面着色器 (Surface Shader)顶点片元着色器 (VF Shader)可视化着色器 (Shader Graph)学习曲线中等。需要理解CG/HLSL语法和Surface Shader特定结构。陡峭。需深入理解渲染管线、坐标空间、HLSL/CG语言。平缓。图形化界面直观无需编码但需理解节点化数据流思维。开发效率高。对于标准PBR材质代码极其精简。低。所有功能需从零实现包括光照和阴影如需。极高。实时预览参数调节直观节点复用方便。灵活性/控制力中。局限于Unity提供的光照模型框架自定义需编写光照函数。极高。完全控制顶点和像素级别的计算可实现任何效果。中高。受限于现有节点复杂逻辑可能节点图庞大但可通过自定义节点扩展。性能控制中。由Unity自动生成代码可能包含冗余但针对标准光照优化良好。极高。可进行极致的微优化写出性能最高的代码。中。生成的代码通常较高效但不如手工优化精细。节点复杂度直接影响性能。团队协作一般。代码需程序员维护美术调整参数需通过Material Inspector。差。高度依赖程序员美术几乎无法参与。优秀。美术可独立创作和调整大部分材质效果与程序员职责清晰分离。适用场景快速实现带复杂光照的实体物体材质角色、场景物件。自定义光照模型卡通、水墨、顶点动画、后处理效果、对性能有严苛要求的移动端特效。美术主导的材质创作、快速原型验证、团队内技术美术TA与美术的高效协作、URP/HDRP项目标准工作流。我的个人选型策略如果是个人学习或研究图形学原理从顶点片元着色器开始。它能帮你打下最坚实的基础理解每一个像素是如何被计算出来的。之后再去学Surface Shader你会恍然大悟明白它帮你做了什么。如果是中小型商业项目追求快速开发Shader Graph是首选尤其是URP项目。它能覆盖80%的材质需求并让美术发挥最大作用。对于Shader Graph难以实现的特殊效果再用顶点片元着色器作为补充。如果是特定风格化项目如纯卡通渲染可能需要以顶点片元着色器为主从头搭建一套自定义的光照和渲染框架。Surface Shader和Shader Graph可能反而不够灵活。如果是维护一个老旧项目使用内置渲染管线你可能会大量遇到表面着色器和顶点片元着色器。理解它们至关重要。5. 从零到一三种方式的完整实现案例理论说再多不如动手做一遍。我们用一个简单的“渐变纹理顶点波动”效果分别用三种方式来实现直观感受它们的差异。5.1 用 Surface Shader 实现带顶点动画的材质目标一个材质表面有从上到下的颜色渐变并且顶点会随着时间正弦波上下移动。Shader Custom/SurfaceGradientWave { Properties { _TopColor (Top Color, Color) (1,0,0,1) _BottomColor (Bottom Color, Color) (0,0,1,1) _WaveSpeed (Wave Speed, Float) 1.0 _WaveAmplitude (Wave Amplitude, Float) 0.1 _Glossiness (Smoothness, Range(0,1)) 0.5 _Metallic (Metallic, Range(0,1)) 0.0 } SubShader { Tags { RenderTypeOpaque } CGPROGRAM // 使用vertex:modifyVert 来指定自定义顶点修改函数 #pragma surface surf Standard fullforwardshadows vertex:vert #pragma target 3.0 #include UnityCG.cginc fixed4 _TopColor; fixed4 _BottomColor; float _WaveSpeed; float _WaveAmplitude; half _Glossiness; half _Metallic; struct Input { float2 uv_MainTex; float3 worldPos; // 需要世界坐标来计算渐变 }; // 自定义顶点函数 void vert (inout appdata_full v) { // 计算基于模型原始Y坐标和时间的正弦波偏移 float wave sin(v.vertex.y * 10.0 _Time.y * _WaveSpeed) * _WaveAmplitude; // 沿法线方向偏移顶点 v.vertex.xyz v.normal * wave; } void surf (Input IN, inout SurfaceOutputStandard o) { // 根据世界空间Y坐标在顶部颜色和底部颜色之间线性插值 // 假设物体在世界空间Y轴从0到10 frac用于取小数部分做循环这里简单用sin模拟 float gradient (sin(IN.worldPos.y * 0.5) 1.0) * 0.5; // 映射到0-1 fixed3 albedo lerp(_BottomColor.rgb, _TopColor.rgb, gradient).rgb; o.Albedo albedo; o.Metallic _Metallic; o.Smoothness _Glossiness; o.Alpha 1.0; } ENDCG } FallBack Standard }要点我们在#pragma指令中加入了vertex:vert并定义了vert函数来修改顶点。surf函数中则根据世界坐标的Y值计算渐变。注意这里的世界坐标渐变只是为了演示实际项目中可能需要根据模型本地坐标或UV来计算。5.2 用 Vertex Fragment Shader 实现同等效果现在我们用更底层的方式实现一模一样的效果你会看到代码量变多了但控制也更精细了。Shader Custom/VFGradientWave { Properties { _TopColor (Top Color, Color) (1,0,0,1) _BottomColor (Bottom Color, Color) (0,0,1,1) _WaveSpeed (Wave Speed, Float) 1.0 _WaveAmplitude (Wave Amplitude, Float) 0.1 } SubShader { Tags { RenderTypeOpaque LightModeForwardBase} // 指定光照模式 Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #pragma multi_compile_fwdbase // 编译多个正向渲染base Pass变体 #include UnityCG.cginc #include Lighting.cginc // 包含光照计算函数 #include AutoLight.cginc // 包含阴影宏 struct appdata { float4 vertex : POSITION; float3 normal : NORMAL; }; struct v2f { float4 pos : SV_POSITION; float3 worldNormal : TEXCOORD0; float3 worldPos : TEXCOORD1; LIGHTING_COORDS(2,3) // 声明阴影坐标宏 }; fixed4 _TopColor; fixed4 _BottomColor; float _WaveSpeed; float _WaveAmplitude; v2f vert (appdata v) { v2f o; // 顶点波动在模型空间计算 float wave sin(v.vertex.y * 10.0 _Time.y * _WaveSpeed) * _WaveAmplitude; v.vertex.xyz v.normal * wave; // 转换到裁剪空间 o.pos UnityObjectToClipPos(v.vertex); // 计算世界空间法线和位置用于光照和渐变 o.worldNormal UnityObjectToWorldNormal(v.normal); o.worldPos mul(unity_ObjectToWorld, v.vertex).xyz; // 传输阴影坐标 TRANSFER_VERTEX_TO_FRAGMENT(o); return o; } fixed4 frag (v2f i) : SV_Target { // 计算渐变基于世界空间Y float gradient (sin(i.worldPos.y * 0.5) 1.0) * 0.5; fixed3 albedo lerp(_BottomColor.rgb, _TopColor.rgb, gradient).rgb; // 简化版兰伯特光照计算 float3 worldNormal normalize(i.worldNormal); float3 lightDir normalize(_WorldSpaceLightPos0.xyz); float ndotl max(0, dot(worldNormal, lightDir)); fixed3 diffuse _LightColor0.rgb * albedo * ndotl; // 加入环境光 fixed3 ambient UNITY_LIGHTMODEL_AMBIENT.rgb * albedo; // 计算衰减和阴影 UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos); fixed3 finalColor (diffuse * atten ambient); return fixed4(finalColor, 1.0); } ENDCG } } FallBack Diffuse }要点我们手动计算了简单的漫反射光照兰伯特模型并使用了AutoLight.cginc中的宏UNITY_LIGHT_ATTENUATION来处理光源衰减和阴影。可以看到为了实现带阴影的基础光照代码复杂度已经远超Surface Shader。这就是Surface Shader为我们节省的工作量。5.3 用 Shader Graph 实现在Shader Graph中步骤会非常直观创建四个PropertyTop Color,Bottom Color,Wave Speed,Wave Amplitude。添加Position节点设置为Object Space获取顶点位置。添加Time节点获取时间。用Multiply和Add节点构建(Position.y * 10 Time.y * WaveSpeed)。连接至Sine节点再乘以Wave Amplitude得到波动值。添加Normal Vector节点与波动值Multiply得到偏移向量。使用Add节点将原始Position与偏移向量相加输出到Master Stack的Vertex Position输入端口。这就完成了顶点动画。对于渐变使用Position节点设置为World Space获取世界坐标Y分量。经过一系列Multiply、Sine、Add、Multiply节点将其映射到0-1范围作为Lerp节点的T值。将Top Color和Bottom Color连接到Lerp节点的A和B输出颜色连接到Master Stack的Base Color。设置Master Stack的表面类型为Opaque并配置好光滑度和金属度。整个过程无需写一行代码通过连线和参数调整即可实时看到效果。对于美术同学来说这个工作流友好得多。6. 常见问题与排查技巧实录无论用哪种方式Shader调试都是令人头疼的。下面是我积累的一些常见问题与解决思路。6.1 效果全黑或全白检查法线这是最常见的原因。在Surface Shader中确保o.Normal被正确赋值如果是SurfaceOutputStandard通常由Unity处理。在Vertex Fragment Shader中确保传递给光照计算的法线是归一化后的世界空间法线。检查坐标空间确保你使用的坐标空间是正确的。模型空间、世界空间、观察空间、裁剪空间不能混用。UnityObjectToClipPos是模型到裁剪空间的正确转换。检查光照和阴影如果使用了自定义的Vertex Fragment Shader且需要光照检查LightModeTag是否正确如LightModeForwardBase是否包含了必要的光照计算和阴影宏。6.2 纹理采样出错紫色或错乱检查UV坐标确认传递给tex2D函数的UV坐标是正确的。在顶点着色器中记得使用TRANSFORM_TEX(v.uv, _MainTex)来应用纹理的缩放和偏移Tiling Offset。检查纹理属性声明在CG代码中除了声明sampler2D _MainTex还需要声明float4 _MainTex_STST代表Scale和TranslateTRANSFORM_TEX宏依赖这个变量。检查纹理导入设置在Unity编辑器中确保纹理的Texture Type正确如Default用于普通颜色贴图Normal map用于法线贴图并且sRGB (Color Texture)选项勾选正确颜色贴图勾选法线/金属度等非颜色贴图不勾选。6.3 透明效果不正常渲染队列Queue错误透明物体必须使用QueueTransparent。在半透明物体渲染时Unity会进行从后往前的排序绘制。混合模式未设置在Vertex Fragment Shader的Pass中需要明确指定混合模式例如Blend SrcAlpha OneMinusSrcAlpha // 传统的Alpha混合深度写入ZWrite冲突透明物体通常需要关闭深度写入ZWrite Off避免后面的透明片元被错误剔除。但这也可能引起排序问题需要根据具体情况调整。在Surface Shader中确保#pragma指令中包含了alpha选项如alpha:blend并且在surf函数中正确输出了o.Alpha。6.4 Shader Graph 节点图编译错误或效果不符检查管线兼容性确认你使用的节点是否支持当前的渲染管线URP/HDRP。某些节点是特定管线独有的。检查节点连接类型确保连线两端的端口数据类型匹配。将一个浮点数输出连接到需要一个向量Vector3的输入端口肯定会出错。预览窗口定位问题Shader Graph提供了节点预览功能。点击某个节点在预览窗口可以单独查看该节点的输出这是排查复杂节点图数据流问题的利器。检查Master Stack设置表面类型Opaque/Transparent、混合模式、深度测试等设置错误会导致整个Shader行为异常。6.5 性能优化小贴士减少纹理采样纹理采样是GPU的昂贵操作。尽可能复用采样结果或者将多个贴图如金属度、光滑度、AO打包到一张贴图的不同通道RGBA中。警惕discard操作在片元着色器中使用clip()或discard操作会严重影响GPU的早期深度测试优化在移动平台上慎用。简化数学运算用mad乘加指令替代单独的乘法和加法用rsqrt求平方根倒数。但大多数情况下编译器会做优化不必过度纠结。在Shader Graph中复杂的数学运算节点如Noise、复杂的Custom Function节点会显著增加ALU算术逻辑单元压力。对于全屏后处理效果要格外注意。使用正确的精度在移动端对颜色、UV等使用fixed或half精度如果范围允许而不是float可以提升性能。在Shader Graph中也有精度设置选项。7. 进阶方向与工具链整合掌握了三种基本方式后你可以根据项目需求向更专业的方向深入。7.1 自定义渲染管线URP/HDRP下的Shader编写现代Unity项目越来越多地使用可编程渲染管线SRP即URP和HDRP。它们对Shader有新的要求和规范。Shader Graph是首选URP/HDRP官方大力推广Shader Graph它与管线特性如Renderer Features, Volume系统集成得最好。编写自定义HLSL代码在URP/HDRP中你依然可以编写HLSL代码。你需要引用新的核心库如Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl并遵循新的光照和阴影函数库。URP提供了更简洁的Lighting.hlsl来简化光照计算。Surface Shader的替代在URP中传统的Surface Shader不再被推荐使用。复杂的表面效果应通过Shader Graph或手写HLSL配合URP的Lit/Unlit Shader框架实现。7.2 利用插件提升效率Amplify Shader Editor这是一个强大的第三方可视化着色器编辑器功能比早期的Shader Graph更丰富节点库庞大许多资深技术美术偏爱它。它同样可以生成代码或者与手写代码结合。Shader Forge已停止更新老牌的可视化着色器工具在一些老项目中还能见到。Visual Studio Code/Visual Studio with Shaderlab插件提供语法高亮、自动补全和错误提示能极大提升编码效率。7.3 调试与性能分析工具Frame DebuggerUnity内置的神器。可以逐帧、逐个Draw Call查看渲染状态包括当前使用的Shader、渲染目标、纹理、混合状态等。是诊断“为什么这个物体没显示”或“为什么这个Pass被跳过了”的终极工具。RenderDoc独立的图形调试器。可以捕获一帧完整的GPU调用序列深入到每一个Shader指令、每一个纹理像素去分析问题功能极其强大是解决复杂渲染Bug的核武器。Unity Profiler的GPU模块分析Shader在GPU上的执行时间定位性能瓶颈。可以查看每个Pass的耗时以及具体的ALU、Texture采样等指标。Shader开发是一个需要不断实践、踩坑和积累经验的领域。我的建议是不要试图一次性掌握所有东西。先从Shader Graph或Surface Shader入手做出一个能看到的效果建立信心。然后当你遇到限制时再去学习Vertex Fragment Shader中对应的知识去突破那个限制。每一次解决问题的过程都会让你对渲染管线的理解加深一层。最终你会发现自己不再拘泥于某种特定的编写方式而是能像一个架构师一样根据项目需求、团队构成和性能目标自由地选择和组合最合适的技术方案。这才是真正掌握了Unity Shader的三种打开方式。