Shader 学习最怕的一件事,就是照着教程敲完代码,画面出来了,但脑子里全是问号——为什么这行要这么写?为什么换个管线就报错?为什么同样的光照模型,别人跑出来是金属质感,我跑出来像塑料?我当初从《Unity Shader 入门精要》第 5 章啃到第 6 章,正好卡在这个坎上:第 5 章讲的是基础光照模型,第 6 章开始进入更贴近真实渲染的进阶光照。而现在的 Unity 生态里,Built-in 管线和 URP 管线并存,HLSL 写法又和书里的 CG 语法有出入,很多人学到一半就迷失了方向。这篇内容就是把我从 5.2 节一路推到 6.0 节过程中踩过的坑、想通的原理、以及实际项目里验证过的写法,完整地摊开来讲。不管你是刚学完基础渲染管线、准备啃光照模型的新手,还是从 Built-in 迁移到 URP 时被 Shader 报错折磨过的老手,都能从里面找到能直接用的东西。
1. 从 5.2 到 6.0 到底跨过了什么
1.1 第 5 章和第 6 章的分界线在哪里
《Unity Shader 入门精要》第 5 章的核心是"基础光照",它从标准光照模型讲起,把环境光、漫反射、高光反射这三块拆开,用逐顶点和逐像素两种方式分别实现。5.2 节通常对应的是漫反射光照模型的实现,也就是 Lambert 模型那套东西。而第 6 章进入的是"高级光照",开始处理更复杂的光照效果,比如半兰伯特、卡通渲染、以及基于物理的渲染思路。
这条分界线看起来只是章节编号的变化,实际上代表的是思维方式的转变。第 5 章你还在用"经验公式"拼凑光照,第 6 章开始要求你理解光与材质交互的物理本质。我当初学 5.2 的时候,觉得漫反射不就是法线和光线方向点乘一下嘛,有什么难的。结果到了第 6 章,发现同样的漫反射,要考虑能量守恒、要考虑 BRDF、要考虑不同管线下的光照方向语义差异,一下子就懵了。
所以从 5.2 到 6.0,真正跨过的不是几个公式,而是从"照着公式写代码"到"理解公式为什么长这样"的认知升级。
1.2 Built-in 与 URP 下同一段光照代码的命运差异
这是我最想先讲清楚的一件事,因为它直接决定了你书上的代码能不能跑起来。书里的示例基本是基于 Built-in 渲染管线写的,用的是 CG 语法,光照相关的变量比如_WorldSpaceLightPos0、_LightColor0都能直接用。但如果你在 URP 项目里照抄,大概率会遇到两个结果:要么编译报错,要么画面全黑。
原因在于 URP 把光照相关的内置变量做了封装,你需要引入Lighting.hlsl这个库文件,并且光照方向、颜色这些数据的获取方式变了。在 Built-in 里,_WorldSpaceLightPos0直接就是光源方向(对于平行光而言),但在 URP 里,你需要通过GetMainLight()函数拿到一个Light结构体,再从里面取direction和color。
我实测下来,最稳妥的做法是:先确定你的项目用的是哪条管线,然后选择对应的写法。如果你只是想学原理,用 Built-in 跟着书走最省心;如果你要做实际项目,尤其是移动端或者需要后续升级的,直接上 URP,但要做好把书里代码"翻译"一遍的准备。这个翻译过程不是简单的替换变量名,而是要理解每个变量背后的语义。
1.3 HLSL 语法在 Unity 里的实际地位
现在热搜词里频繁出现 HLSL,这不是偶然。Unity 官方这几年一直在推 HLSL 作为 Shader 编写的主力语言,URP 和 HDRP 的 Shader 库基本都是用 HLSL 写的。CG 语法虽然还能用,但本质上是被翻译成 HLSL 的,而且在新管线里支持得越来越差。
HLSL 和 CG 在语法层面差异不算大,都是类 C 的语言,但有几个关键区别你得知道。第一,HLSL 没有 CG 里的fixed类型,你得用half或者float代替。第二,HLSL 的库函数命名和参数顺序有些微调,比如tex2D在 HLSL 里更推荐用SAMPLE_TEXTURE2D宏。第三,HLSL 对语义(semantic)的要求更严格,比如顶点着色器的输入输出语义必须明确写清楚。
我个人的建议是,如果你是新学,直接学 HLSL 写法,别在 CG 上浪费时间。书里的 CG 代码你可以看懂就行,实际写的时候用 HLSL 的规范来。这样你写出来的 Shader 在 URP 和 Built-in 下都能有更好的兼容性,后续迁移也省事。
2. 漫反射光照模型的逐像素实现细节
2.1 为什么逐像素比逐顶点更值得掌握
书里在讲漫反射的时候,会先给一个逐顶点的版本,再给一个逐像素的版本。逐顶点的做法是在顶点着色器里算好光照,然后插值到片元。逐像素是把法线和光线方向传到片元着色器,在片元里算光照。两者的画面差异在低模上特别明显:逐顶点会出现明显的块状感,因为光照是在顶点之间线性插值的,而逐像素能得到平滑的光照过渡。
我当初偷懒,觉得逐顶点性能好,就先用了逐顶点。结果在一个角色模型上,脸部的高光区域出现了明显的多边形棱角,特别难看。换成逐像素之后,虽然性能开销大了一点,但画面质量提升是肉眼可见的。现在的 GPU 性能下,除非你是做极低端的移动设备,否则逐像素光照基本是标配。
这里有个细节要注意:逐像素计算时,法线要从顶点着色器传到片元着色器,这个传递过程会经过插值。如果你的模型法线没有归一化,插值后的法线长度可能不是 1,直接拿去做点乘会导致光照结果偏暗或者偏亮。所以片元着色器里拿到法线后,第一件事就是normalize一下。这个坑我踩过,当时调了半天光照强度,最后发现是法线没归一化。
2.2 法线从模型空间到世界空间的转换陷阱
漫反射计算需要在同一个空间里进行,通常是世界空间。法线从模型空间转到世界空间,不能直接用模型到世界的变换矩阵,因为法线是方向向量,不是位置点。如果模型有非均匀缩放,直接用UnityObjectToWorldNormal之外的矩阵变换,法线方向会歪掉。
Unity 提供了UnityObjectToWorldNormal这个函数,它内部处理了非均匀缩放的情况,用的是逆转置矩阵。我建议你直接用这个函数,别自己拼矩阵。书里有时候会写mul(v.normal, (float3x3)unity_WorldToObject)这种写法,那是老版本的写法,现在用UnityObjectToWorldNormal更安全。
还有一个容易忽略的点:光照方向的空间。在 Built-in 里,_WorldSpaceLightPos0对于平行光来说就是世界空间的光照方向,但它的方向定义是"从光源指向物体"还是"从物体指向光源",这个容易搞混。实际上_WorldSpaceLightPos0对于平行光,它的 xyz 分量是光源方向的反方向,也就是从物体指向光源的方向。所以你在做点乘的时候,直接用_WorldSpaceLightPos0.xyz和法线点乘,得到的就是正确的漫反射系数。这个语义我当初理解错了,导致光照方向反了,物体背光面反而亮。
2.3 一个可直接复用的逐像素漫反射 Shader 骨架
下面这个骨架是我在实际项目里反复用过的,基于 Built-in 管线,HLSL 风格写法,你可以直接拿去改。
Shader "Custom/PixelDiffuse" { Properties { _MainTex ("Main Texture", 2D) = "white" {} _DiffuseColor ("Diffuse Color", Color) = (1,1,1,1) } SubShader { Tags { "RenderType"="Opaque" "LightMode"="ForwardBase" } LOD 100 Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" #include "Lighting.cginc" struct appdata { float4 vertex : POSITION; float3 normal : NORMAL; float2 uv : TEXCOORD0; }; struct v2f { float4 pos : SV_POSITION; float3 worldNormal : TEXCOORD0; float2 uv : TEXCOORD1; }; sampler2D _MainTex; float4 _MainTex_ST; fixed4 _DiffuseColor; v2f vert (appdata v) { v2f o; o.pos = UnityObjectToClipPos(v.vertex); o.worldNormal = UnityObjectToWorldNormal(v.normal); o.uv = TRANSFORM_TEX(v.uv, _MainTex); return o; } fixed4 frag (v2f i) : SV_Target { float3 normal = normalize(i.worldNormal); float3 lightDir = normalize(_WorldSpaceLightPos0.xyz); float diff = max(0, dot(normal, lightDir)); float3 diffuse = _LightColor0.rgb * _DiffuseColor.rgb * diff; float3 ambient = UNITY_LIGHTMODEL_AMBIENT.rgb; fixed4 texColor = tex2D(_MainTex, i.uv); return fixed4((diffuse + ambient) * texColor.rgb, 1.0); } ENDCG } } }这个骨架里,LightMode设为ForwardBase是关键,它告诉 Unity 这是前向渲染的基础 Pass,会处理主平行光。_LightColor0和_WorldSpaceLightPos0都是在这个 Pass 下才有效的内置变量。如果你把LightMode去掉或者设错,光照就会失效。
2.4 环境光与自发光在漫反射里的角色
很多人写漫反射的时候只算主光,结果物体背光面全黑,看起来特别假。真实世界里,背光面也有光,来自环境反射。Unity 里用UNITY_LIGHTMODEL_AMBIENT来获取环境光,它是一个全局变量,在 Built-in 里可以直接用。
但要注意,环境光在 URP 里的获取方式完全不同,URP 用的是SH球谐函数来存储环境光,你需要通过SampleSH或者unity_AmbientSky之类的变量来取。这也是为什么 URP 下照抄 Built-in 代码会出问题的原因之一。
自发光(Emission)在漫反射阶段通常不参与光照计算,它是直接叠加到最终颜色上的。如果你想让物体有自发光效果,可以在片元着色器最后加上_EmissionColor。但自发光不影响其他物体的光照,它只是让物体自己看起来亮。如果你要做真正的发光效果影响周围,那需要用到全局光照或者点光源。
3. 从漫反射到高光:6.0 章节的进阶光照思路
3.1 高光反射的物理直觉与 Blinn-Phong 模型
第 6 章开始讲高光反射,书里会先讲 Phong 模型,再讲 Blinn-Phong 模型。Phong 模型的核心是反射向量和视线方向的点乘,Blinn-Phong 是半角向量和法线的点乘。两者的区别在于,Blinn-Phong 在视线和反射方向夹角很大时,高光会更柔和,而且计算量更小,因为半角向量只需要算一次。
我实测下来,Blinn-Phong 在大多数场景下视觉效果更自然,尤其是金属材质。Phong 模型的高光在边缘处容易出现过亮或者断裂。所以如果你没有特殊需求,直接用 Blinn-Phong 就行。
半角向量的计算是normalize(lightDir + viewDir),然后和法线点乘,再取pow的指数。这个指数就是高光范围,值越大高光越集中。书里通常会给一个_Gloss参数来控制,范围从 8 到 256 不等。我一般用 20 到 50 之间,具体看材质。
3.2 半兰伯特模型解决背光面死黑的问题
半兰伯特是第 6 章的一个重点,它解决的是传统兰伯特模型在背光面完全黑掉的问题。传统兰伯特用max(0, dot(normal, lightDir)),背光面点乘为负,直接截断成 0。半兰伯特把点乘结果从 [-1,1] 映射到 [0,1],公式是dot(normal, lightDir) * 0.5 + 0.5。
这样一来,背光面也有一定的亮度,画面更柔和。这个技巧在卡通渲染和角色渲染里特别常用。我做过一个卡通风格的角色,用半兰伯特之后,脸部的暗部不会死黑,看起来更舒服。
但半兰伯特也有个问题:它会让光照失去物理正确性,暗部亮度偏高,整体对比度降低。所以如果你做的是写实风格,慎用半兰伯特,或者只在小范围内用。
3.3 高光与漫反射的叠加顺序与能量守恒
在片元着色器里,漫反射和高光通常是分开算,最后相加。但相加的时候要注意能量守恒:漫反射和高光的总能量不应该超过入射光能量。实际写的时候,很多人会把高光叠得很亮,导致物体看起来像涂了油。
我的做法是,先算漫反射,再算高光,高光乘以一个系数控制强度,最后加上环境光。如果要做物理正确的渲染,那就要用 BRDF 模型,把漫反射和高光统一到一个公式里。但那是第 6 章之后的内容了,入门阶段先把 Blinn-Phong 用熟。
这里有个经验:高光的颜色通常取光源颜色,而不是材质颜色。金属的高光颜色会带一点材质的色调,但非金属的高光基本就是光源颜色。如果你把高光颜色设成材质颜色,金属感会更强,但非金属会显得很怪。
4. URP 下重写光照 Shader 的完整路径
4.1 URP 的 Shader 结构与传统 Built-in 的根本差异
URP 的 Shader 结构和 Built-in 差异很大。Built-in 用CGPROGRAM和ENDCG,URP 用HLSLPROGRAM和ENDHLSL。Built-in 的 Pass 里用LightMode标签,URP 也有类似的标签,但光照相关的库文件完全不同。
URP 的核心库是UniversalRenderPipeline.hlsl,光照相关的在Lighting.hlsl里。你需要引入这些库,才能用GetMainLight、GetAdditionalLight这些函数。而且 URP 的顶点输入输出结构体通常用Attributes和Varyings来命名,和 Built-in 的appdata、v2f对应。
我刚开始迁移的时候,最不习惯的就是 URP 把很多东西封装成了函数和结构体,不像 Built-in 那样直接暴露变量。但用久了会发现,这种封装其实更清晰,尤其是处理多光源的时候。
4.2 用 GetMainLight 获取主光源的正确姿势
在 URP 里获取主光源,标准写法是:
Light mainLight = GetMainLight(); float3 lightDir = mainLight.direction; float3 lightColor = mainLight.color;GetMainLight返回的Light结构体里,direction是从物体指向光源的方向,和 Built-in 的_WorldSpaceLightPos0语义一致。color是光源颜色,已经乘上了强度。如果你需要阴影,还可以用GetMainLight(shadowCoord)的重载版本。
这里有个坑:URP 里的GetMainLight默认不包含阴影衰减,你需要自己处理阴影。如果你直接拿mainLight.color去算光照,阴影是不会显示的。要显示阴影,得用mainLight.shadowAttenuation乘上去。这个我当初调了很久,一直以为阴影没开,后来发现是没乘衰减。
4.3 URP 下逐像素漫反射的完整实现
下面这个是我在 URP 下写的逐像素漫反射 Shader,可以直接用:
Shader "Custom/URP/PixelDiffuse" { Properties { _BaseMap ("Base Map", 2D) = "white" {} _BaseColor ("Base Color", Color) = (1,1,1,1) } SubShader { Tags { "RenderType"="Opaque" "RenderPipeline"="UniversalPipeline" } Pass { Tags { "LightMode"="UniversalForward" } HLSLPROGRAM #pragma vertex vert #pragma fragment frag #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl" #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl" struct Attributes { float4 positionOS : POSITION; float3 normalOS : NORMAL; float2 uv : TEXCOORD0; }; struct Varyings { float4 positionCS : SV_POSITION; float3 normalWS : TEXCOORD0; float2 uv : TEXCOORD1; }; TEXTURE2D(_BaseMap); SAMPLER(sampler_BaseMap); CBUFFER_START(UnityPerMaterial) float4 _BaseMap_ST; half4 _BaseColor; CBUFFER_END Varyings vert (Attributes input) { Varyings output; VertexPositionInputs posInputs = GetVertexPositionInputs(input.positionOS.xyz); VertexNormalInputs normInputs = GetVertexNormalInputs(input.normalOS); output.positionCS = posInputs.positionCS; output.normalWS = normInputs.normalWS; output.uv = TRANSFORM_TEX(input.uv, _BaseMap); return output; } half4 frag (Varyings input) : SV_Target { half3 normalWS = normalize(input.normalWS); Light mainLight = GetMainLight(); half3 lightDir = normalize(mainLight.direction); half diff = max(0, dot(normalWS, lightDir)); half3 diffuse = mainLight.color * _BaseColor.rgb * diff; half3 ambient = SampleSH(normalWS); half4 texColor = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, input.uv); return half4((diffuse + ambient) * texColor.rgb * _BaseColor.rgb, 1.0); } ENDHLSL } } }这个 Shader 里,GetVertexPositionInputs和GetVertexNormalInputs是 URP 提供的工具函数,帮你处理空间变换。SampleSH是采样球谐环境光,比 Built-in 的UNITY_LIGHTMODEL_AMBIENT更准确。CBUFFER_START和CBUFFER_END是 URP 要求的常量缓冲区声明,SRP Batcher 需要它来合批。
4.4 SRP Batcher 对 Shader 写法的约束
SRP Batcher 是 URP 的一个性能优化特性,它能大幅减少 Draw Call 的开销。但它对 Shader 写法有要求:所有材质属性必须放在CBUFFER_START(UnityPerMaterial)和CBUFFER_END之间。如果你把属性声明在外面,SRP Batcher 就不会生效,性能会下降。
我当初不知道这个,写了个 Shader 发现 Draw Call 特别高,后来查了半天才发现是没加 CBUFFER。加上之后,Draw Call 直接降了一半。这个细节书里没讲,但实际项目里很重要。
另外,SRP Batcher 还要求 Shader 的变体尽量少,所以你要控制#pragma multi_compile的使用。能用shader_feature的地方就别用multi_compile,因为shader_feature只编译实际用到的变体,而multi_compile会编译所有组合。
5. 光照调试中那些让人抓狂的瞬间
5.1 画面全黑或全白的排查链路
Shader 写完之后画面全黑,这是最常见的问题。我总结了一个排查顺序,按这个顺序走,基本能定位到问题。
第一步,检查LightMode标签。Built-in 下前向渲染基础 Pass 必须是ForwardBase,URP 下必须是UniversalForward。标签错了,光照变量就是空的,画面自然黑。
第二步,检查光照方向。把lightDir直接输出成颜色,看看方向对不对。如果输出是纯色,说明方向向量有问题。常见原因是法线没转到世界空间,或者光照方向没归一化。
第三步,检查点乘结果。把dot(normal, lightDir)输出成颜色,正常应该是从黑到白的渐变。如果全是黑,说明法线和光线方向夹角始终大于 90 度,可能是法线方向反了。
第四步,检查环境光。如果漫反射正常但整体偏暗,可能是环境光没加上。Built-in 下用UNITY_LIGHTMODEL_AMBIENT,URP 下用SampleSH。
第五步,检查纹理采样。如果颜色不对但光照正常,可能是 UV 或者纹理没设置对。把纹理采样去掉,直接输出纯色,看看光照是否正常。
这个排查链路我用了很多次,基本上五分钟内能定位到问题。
5.2 法线贴图与切线空间的方向混乱
法线贴图是进阶光照里绕不开的东西,但它带来的方向混乱也是最多的。法线贴图存储的是切线空间下的法线,你需要把切线空间的法线转到世界空间,才能参与光照计算。这个转换需要切线、副切线和法线构成的 TBN 矩阵。
Unity 里可以用UnpackNormal来解包法线贴图,然后手动构建 TBN 矩阵。但要注意,不同平台的法线贴图格式可能不同,有的平台是 DXT5nm 压缩,有的平台是 RGB。UnpackNormal会处理这些差异,但如果你自己写解包逻辑,就要小心平台兼容性。
我踩过的坑是:切线空间的手性(handedness)在不同模型上可能不一致,导致法线贴图看起来是反的。解决办法是在导入模型时勾选 "Calculate MikkTSpace" 或者手动调整切线。这个坑很隐蔽,因为法线贴图反了之后,光照看起来只是"有点怪",不一定会明显报错。
5.3 多光源场景下的 Pass 叠加问题
Built-in 的前向渲染里,多光源是通过多个 Pass 叠加实现的。主光源在ForwardBasePass 里处理,额外光源在ForwardAddPass 里处理。如果你只写了ForwardBase,那场景里只有主光源能影响物体,其他光源都没效果。
ForwardAddPass 需要设置Blend One One,因为它是叠加到已有颜色上的。而且ForwardAdd里不能用UNITY_LIGHTMODEL_AMBIENT,因为环境光已经在ForwardBase里加过了,再加就重复了。
URP 下多光源的处理方式不同,URP 默认只支持有限数量的额外光源,超过数量的光源会用球谐函数近似。如果你需要更多实时光源,得在 URP 设置里调整Additional Lights的数量。这个设置我当初找了很久,因为它不在 Shader 里,而在管线的 Asset 设置里。
6. 从入门到能用的几个关键习惯
6.1 用 Frame Debugger 看每个 Pass 的实际输出
Frame Debugger 是 Unity 里调试 Shader 的神器,但很多人不知道用。它能让你看到每一帧里每个 Draw Call 的实际渲染结果,包括每个 Pass 的输出。如果你怀疑某个 Pass 没生效,用 Frame Debugger 一看就知道。
我调试多光源的时候,就是用 Frame Debugger 发现ForwardAddPass 根本没执行。原因是LightMode标签写错了,改过来之后,额外光源就正常了。这个工具比在 Shader 里加return调试高效得多。
6.2 把光照计算拆成可单独验证的模块
写光照 Shader 的时候,不要一次性把所有东西都写进去。先把漫反射单独调通,确认光照方向、法线、颜色都对了,再加高光。加高光的时候,先把高光单独输出,确认高光位置和范围对了,再和漫反射叠加。
这种模块化的调试方式,能让你在出问题的时候快速定位到是哪一块的问题。我见过很多人写 Shader 是一大坨,出了问题是全黑,然后就开始瞎改,改了半天也不知道哪里错了。拆开写,虽然前期麻烦一点,但后期省时间。
6.3 版本管理:Shader 代码也要进 Git
Shader 代码是纯文本,完全可以进 Git。我建议每个 Shader 都单独一个文件,不要嵌在材质里。这样你可以追踪每次修改,出问题了也能回滚。
另外,Shader 的变体很多,编译一次很慢。如果你在团队里协作,建议把 Shader 的编译缓存也管理起来,或者用 Shader Variant Collection 来预编译常用变体。这个在大型项目里能省很多时间。
6.4 从 Built-in 迁移到 URP 的检查清单
如果你手头有 Built-in 的 Shader 要迁移到 URP,可以按这个清单来检查:
| 检查项 | Built-in 写法 | URP 写法 |
|---|---|---|
| 程序块 | CGPROGRAM | HLSLPROGRAM |
| 库文件 | UnityCG.cginc | Core.hlsl + Lighting.hlsl |
| 光照方向 | _WorldSpaceLightPos0 | GetMainLight().direction |
| 光源颜色 | _LightColor0 | GetMainLight().color |
| 环境光 | UNITY_LIGHTMODEL_AMBIENT | SampleSH(normalWS) |
| 纹理采样 | tex2D | SAMPLE_TEXTURE2D |
| 常量缓冲 | 无要求 | CBUFFER_START(UnityPerMaterial) |
| Pass 标签 | ForwardBase | UniversalForward |
| 阴影 | SHADOW_COORDS | 通过 GetMainLight 的 shadowAttenuation |
按这个清单逐项替换,基本能完成迁移。但要注意,URP 的光照模型和 Built-in 不完全一致,迁移后可能需要微调参数才能达到相同的视觉效果。
6.5 性能优化的几个实际抓手
光照 Shader 的性能优化,我实际用下来最有效的几个手段:第一,能用 half 就别用 float,移动端 half 的运算速度快很多。第二,避免在片元着色器里做复杂的数学运算,能移到顶点着色器的就移过去。第三,控制光照计算的次数,多光源场景下,能用球谐近似的就用球谐,别每个光源都算一遍。
还有一个容易被忽略的点:normalize是有开销的,如果法线在顶点着色器里已经归一化了,片元里可以不再归一化,除非经过了插值。插值后的法线长度会变,所以片元里通常还是要归一化一次。但如果你能保证插值后长度变化不大,可以省掉这次归一化。
7. 写在最后的一些个人体会
从 5.2 到 6.0 这段路,我走了大概两个月,中间反复回看了很多遍书,也在实际项目里试错了很多次。最大的体会是:Shader 入门难,不是因为数学有多复杂,而是因为信息太分散。书上的写法、官方文档的写法、实际项目的写法,三者之间往往有差异,你得自己把这些差异拼起来。
我的建议是,先把一个最简单的漫反射 Shader 在 Built-in 和 URP 下各写一遍,确保都能跑通。然后在这个基础上,逐步加高光、加法线贴图、加多光源。每加一个功能,就用 Frame Debugger 看一遍,确认输出符合预期。这样走下来,你对光照模型的理解会比只看书深得多。
另外,别怕看官方源码。URP 的Lighting.hlsl里有很多注释,解释了每个函数的用途和参数。你不需要全部看懂,但遇到问题的时候,去源码里搜一下相关函数,往往能找到答案。我很多疑惑都是在读源码的时候解开的。
最后说一个实际项目里的经验:光照效果好不好,参数调优占一半。同样的 Shader,_Gloss从 20 调到 50,金属感完全不同。所以写完 Shader 只是开始,花时间调参数才是让画面出效果的关键。别指望一个默认参数就能出好效果,多试几组值,对比着看,慢慢就有感觉了。