【Unity】Standard Shader实现分析

记录Unity的标准着色器实现,基于Unity 2017.1版本的代码进行分析。

Standard Shader

文件位于DefaultResourcesExtraStandard.shader

  1 // Unity built-in shader source. Copyright (c) 2016 Unity Technologies. MIT license (see license.txt)
  2 
  3 Shader "Standard"
  4 {
  5     Properties
  6     {
  7         _Color("Color", Color) = (1,1,1,1)
  8         _MainTex("Albedo", 2D) = "white" {}
  9 
 10         _Cutoff("Alpha Cutoff", Range(0.0, 1.0)) = 0.5
 11 
 12         _Glossiness("Smoothness", Range(0.0, 1.0)) = 0.5
 13         _GlossMapScale("Smoothness Scale", Range(0.0, 1.0)) = 1.0
 14         [Enum(Metallic Alpha,0,Albedo Alpha,1)] _SmoothnessTextureChannel ("Smoothness texture channel", Float) = 0
 15 
 16         [Gamma] _Metallic("Metallic", Range(0.0, 1.0)) = 0.0
 17         _MetallicGlossMap("Metallic", 2D) = "white" {}
 18 
 19         [ToggleOff] _SpecularHighlights("Specular Highlights", Float) = 1.0
 20         [ToggleOff] _GlossyReflections("Glossy Reflections", Float) = 1.0
 21 
 22         _BumpScale("Scale", Float) = 1.0
 23         _BumpMap("Normal Map", 2D) = "bump" {}
 24 
 25         _Parallax ("Height Scale", Range (0.005, 0.08)) = 0.02
 26         _ParallaxMap ("Height Map", 2D) = "black" {}
 27 
 28         _OcclusionStrength("Strength", Range(0.0, 1.0)) = 1.0
 29         _OcclusionMap("Occlusion", 2D) = "white" {}
 30 
 31         _EmissionColor("Color", Color) = (0,0,0)
 32         _EmissionMap("Emission", 2D) = "white" {}
 33 
 34         _DetailMask("Detail Mask", 2D) = "white" {}
 35 
 36         _DetailAlbedoMap("Detail Albedo x2", 2D) = "grey" {}
 37         _DetailNormalMapScale("Scale", Float) = 1.0
 38         _DetailNormalMap("Normal Map", 2D) = "bump" {}
 39 
 40         [Enum(UV0,0,UV1,1)] _UVSec ("UV Set for secondary textures", Float) = 0
 41 
 42 
 43         // Blending state
 44         [HideInInspector] _Mode ("__mode", Float) = 0.0
 45         [HideInInspector] _SrcBlend ("__src", Float) = 1.0
 46         [HideInInspector] _DstBlend ("__dst", Float) = 0.0
 47         [HideInInspector] _ZWrite ("__zw", Float) = 1.0
 48     }
 49 
 50     CGINCLUDE
 51         #define UNITY_SETUP_BRDF_INPUT MetallicSetup
 52     ENDCG
 53 
 54     SubShader
 55     {
 56         Tags { "RenderType"="Opaque" "PerformanceChecks"="False" }
 57         LOD 300
 58 
 59 
 60         // ------------------------------------------------------------------
 61         //  Base forward pass (directional light, emission, lightmaps, ...)
 62         Pass
 63         {
 64             Name "FORWARD"
 65             Tags { "LightMode" = "ForwardBase" }
 66 
 67             Blend [_SrcBlend] [_DstBlend]
 68             ZWrite [_ZWrite]
 69 
 70             CGPROGRAM
 71             #pragma target 3.0
 72 
 73             // -------------------------------------
 74 
 75             #pragma shader_feature _NORMALMAP
 76             #pragma shader_feature _ _ALPHATEST_ON _ALPHABLEND_ON _ALPHAPREMULTIPLY_ON
 77             #pragma shader_feature _EMISSION
 78             #pragma shader_feature _METALLICGLOSSMAP
 79             #pragma shader_feature ___ _DETAIL_MULX2
 80             #pragma shader_feature _ _SMOOTHNESS_TEXTURE_ALBEDO_CHANNEL_A
 81             #pragma shader_feature _ _SPECULARHIGHLIGHTS_OFF
 82             #pragma shader_feature _ _GLOSSYREFLECTIONS_OFF
 83             #pragma shader_feature _PARALLAXMAP
 84 
 85             #pragma multi_compile_fwdbase
 86             #pragma multi_compile_fog
 87             #pragma multi_compile_instancing
 88             // Uncomment the following line to enable dithering LOD crossfade. Note: there are more in the file to uncomment for other passes.
 89             //#pragma multi_compile _ LOD_FADE_CROSSFADE
 90 
 91             #pragma vertex vertBase
 92             #pragma fragment fragBase
 93             #include "UnityStandardCoreForward.cginc"
 94 
 95             ENDCG
 96         }
 97         // ------------------------------------------------------------------
 98         //  Additive forward pass (one light per pass)
 99         Pass
100         {
101             Name "FORWARD_DELTA"
102             Tags { "LightMode" = "ForwardAdd" }
103             Blend [_SrcBlend] One
104             Fog { Color (0,0,0,0) } // in additive pass fog should be black
105             ZWrite Off
106             ZTest LEqual
107 
108             CGPROGRAM
109             #pragma target 3.0
110 
111             // -------------------------------------
112 
113 
114             #pragma shader_feature _NORMALMAP
115             #pragma shader_feature _ _ALPHATEST_ON _ALPHABLEND_ON _ALPHAPREMULTIPLY_ON
116             #pragma shader_feature _METALLICGLOSSMAP
117             #pragma shader_feature _ _SMOOTHNESS_TEXTURE_ALBEDO_CHANNEL_A
118             #pragma shader_feature _ _SPECULARHIGHLIGHTS_OFF
119             #pragma shader_feature ___ _DETAIL_MULX2
120             #pragma shader_feature _PARALLAXMAP
121 
122             #pragma multi_compile_fwdadd_fullshadows
123             #pragma multi_compile_fog
124             // Uncomment the following line to enable dithering LOD crossfade. Note: there are more in the file to uncomment for other passes.
125             //#pragma multi_compile _ LOD_FADE_CROSSFADE
126 
127             #pragma vertex vertAdd
128             #pragma fragment fragAdd
129             #include "UnityStandardCoreForward.cginc"
130 
131             ENDCG
132         }
133         // ------------------------------------------------------------------
134         //  Shadow rendering pass
135         Pass {
136             Name "ShadowCaster"
137             Tags { "LightMode" = "ShadowCaster" }
138 
139             ZWrite On ZTest LEqual
140 
141             CGPROGRAM
142             #pragma target 3.0
143 
144             // -------------------------------------
145 
146 
147             #pragma shader_feature _ _ALPHATEST_ON _ALPHABLEND_ON _ALPHAPREMULTIPLY_ON
148             #pragma shader_feature _METALLICGLOSSMAP
149             #pragma shader_feature _PARALLAXMAP
150             #pragma multi_compile_shadowcaster
151             #pragma multi_compile_instancing
152             // Uncomment the following line to enable dithering LOD crossfade. Note: there are more in the file to uncomment for other passes.
153             //#pragma multi_compile _ LOD_FADE_CROSSFADE
154 
155             #pragma vertex vertShadowCaster
156             #pragma fragment fragShadowCaster
157 
158             #include "UnityStandardShadow.cginc"
159 
160             ENDCG
161         }
162         // ------------------------------------------------------------------
163         //  Deferred pass
164         Pass
165         {
166             Name "DEFERRED"
167             Tags { "LightMode" = "Deferred" }
168 
169             CGPROGRAM
170             #pragma target 3.0
171             #pragma exclude_renderers nomrt
172 
173 
174             // -------------------------------------
175 
176             #pragma shader_feature _NORMALMAP
177             #pragma shader_feature _ _ALPHATEST_ON _ALPHABLEND_ON _ALPHAPREMULTIPLY_ON
178             #pragma shader_feature _EMISSION
179             #pragma shader_feature _METALLICGLOSSMAP
180             #pragma shader_feature _ _SMOOTHNESS_TEXTURE_ALBEDO_CHANNEL_A
181             #pragma shader_feature _ _SPECULARHIGHLIGHTS_OFF
182             #pragma shader_feature ___ _DETAIL_MULX2
183             #pragma shader_feature _PARALLAXMAP
184 
185             #pragma multi_compile_prepassfinal
186             #pragma multi_compile_instancing
187             // Uncomment the following line to enable dithering LOD crossfade. Note: there are more in the file to uncomment for other passes.
188             //#pragma multi_compile _ LOD_FADE_CROSSFADE
189 
190             #pragma vertex vertDeferred
191             #pragma fragment fragDeferred
192 
193             #include "UnityStandardCore.cginc"
194 
195             ENDCG
196         }
197 
198         // ------------------------------------------------------------------
199         // Extracts information for lightmapping, GI (emission, albedo, ...)
200         // This pass it not used during regular rendering.
201         Pass
202         {
203             Name "META"
204             Tags { "LightMode"="Meta" }
205 
206             Cull Off
207 
208             CGPROGRAM
209             #pragma vertex vert_meta
210             #pragma fragment frag_meta
211 
212             #pragma shader_feature _EMISSION
213             #pragma shader_feature _METALLICGLOSSMAP
214             #pragma shader_feature _ _SMOOTHNESS_TEXTURE_ALBEDO_CHANNEL_A
215             #pragma shader_feature ___ _DETAIL_MULX2
216             #pragma shader_feature EDITOR_VISUALIZATION
217 
218             #include "UnityStandardMeta.cginc"
219             ENDCG
220         }
221     }
222 
223     SubShader
224     {
225         Tags { "RenderType"="Opaque" "PerformanceChecks"="False" }
226         LOD 150
227 
228         // ------------------------------------------------------------------
229         //  Base forward pass (directional light, emission, lightmaps, ...)
230         Pass
231         {
232             Name "FORWARD"
233             Tags { "LightMode" = "ForwardBase" }
234 
235             Blend [_SrcBlend] [_DstBlend]
236             ZWrite [_ZWrite]
237 
238             CGPROGRAM
239             #pragma target 2.0
240 
241             #pragma shader_feature _NORMALMAP
242             #pragma shader_feature _ _ALPHATEST_ON _ALPHABLEND_ON _ALPHAPREMULTIPLY_ON
243             #pragma shader_feature _EMISSION
244             #pragma shader_feature _METALLICGLOSSMAP
245             #pragma shader_feature _ _SMOOTHNESS_TEXTURE_ALBEDO_CHANNEL_A
246             #pragma shader_feature _ _SPECULARHIGHLIGHTS_OFF
247             #pragma shader_feature _ _GLOSSYREFLECTIONS_OFF
248             // SM2.0: NOT SUPPORTED shader_feature ___ _DETAIL_MULX2
249             // SM2.0: NOT SUPPORTED shader_feature _PARALLAXMAP
250 
251             #pragma skip_variants SHADOWS_SOFT DIRLIGHTMAP_COMBINED
252 
253             #pragma multi_compile_fwdbase
254             #pragma multi_compile_fog
255 
256             #pragma vertex vertBase
257             #pragma fragment fragBase
258             #include "UnityStandardCoreForward.cginc"
259 
260             ENDCG
261         }
262         // ------------------------------------------------------------------
263         //  Additive forward pass (one light per pass)
264         Pass
265         {
266             Name "FORWARD_DELTA"
267             Tags { "LightMode" = "ForwardAdd" }
268             Blend [_SrcBlend] One
269             Fog { Color (0,0,0,0) } // in additive pass fog should be black
270             ZWrite Off
271             ZTest LEqual
272 
273             CGPROGRAM
274             #pragma target 2.0
275 
276             #pragma shader_feature _NORMALMAP
277             #pragma shader_feature _ _ALPHATEST_ON _ALPHABLEND_ON _ALPHAPREMULTIPLY_ON
278             #pragma shader_feature _METALLICGLOSSMAP
279             #pragma shader_feature _ _SMOOTHNESS_TEXTURE_ALBEDO_CHANNEL_A
280             #pragma shader_feature _ _SPECULARHIGHLIGHTS_OFF
281             #pragma shader_feature ___ _DETAIL_MULX2
282             // SM2.0: NOT SUPPORTED shader_feature _PARALLAXMAP
283             #pragma skip_variants SHADOWS_SOFT
284 
285             #pragma multi_compile_fwdadd_fullshadows
286             #pragma multi_compile_fog
287 
288             #pragma vertex vertAdd
289             #pragma fragment fragAdd
290             #include "UnityStandardCoreForward.cginc"
291 
292             ENDCG
293         }
294         // ------------------------------------------------------------------
295         //  Shadow rendering pass
296         Pass {
297             Name "ShadowCaster"
298             Tags { "LightMode" = "ShadowCaster" }
299 
300             ZWrite On ZTest LEqual
301 
302             CGPROGRAM
303             #pragma target 2.0
304 
305             #pragma shader_feature _ _ALPHATEST_ON _ALPHABLEND_ON _ALPHAPREMULTIPLY_ON
306             #pragma shader_feature _METALLICGLOSSMAP
307             #pragma skip_variants SHADOWS_SOFT
308             #pragma multi_compile_shadowcaster
309 
310             #pragma vertex vertShadowCaster
311             #pragma fragment fragShadowCaster
312 
313             #include "UnityStandardShadow.cginc"
314 
315             ENDCG
316         }
317 
318         // ------------------------------------------------------------------
319         // Extracts information for lightmapping, GI (emission, albedo, ...)
320         // This pass it not used during regular rendering.
321         Pass
322         {
323             Name "META"
324             Tags { "LightMode"="Meta" }
325 
326             Cull Off
327 
328             CGPROGRAM
329             #pragma vertex vert_meta
330             #pragma fragment frag_meta
331 
332             #pragma shader_feature _EMISSION
333             #pragma shader_feature _METALLICGLOSSMAP
334             #pragma shader_feature _ _SMOOTHNESS_TEXTURE_ALBEDO_CHANNEL_A
335             #pragma shader_feature ___ _DETAIL_MULX2
336             #pragma shader_feature EDITOR_VISUALIZATION
337 
338             #include "UnityStandardMeta.cginc"
339             ENDCG
340         }
341     }
342 
343 
344     FallBack "VertexLit"
345     CustomEditor "StandardShaderGUI"
346 }
Standard Shader

Standard Shader中主要有三个分支,一个是SM3.0的Forward渲染实现,一个是Deferred渲染实现,一个是针对SM2.0的Forward渲染实现。

在SM3.0下,Unity实现Forward渲染有两个Pass。第一个是Pass是针对主光源的ForwardBase,第二个Pass是针对其他光源的ForwardAdd。实现两个Pass的顶点着色器和片段着色器函数名称也已经给出,包含在"UnityStandardCoreForward.cginc"文件中。

在UnityStandardCoreForward.cginc文件中出现了分支,一个是Simple实现一个是标准的实现。从学习的目的来讲,主要看Unity的标准实现。

根据上述代码,我们在UnityStandardCore.cginc中找到了顶点着色器和片段着色器的具体实现。为了减轻工作量,先研究Forward渲染的代码。顶点着色器为vertForwardBase/vertForwardAdd,片段着色器为fragForwardBase/fragForwardAdd。ForwardAdd实现和ForwardBase实现类似,只有少量区别。所以主要分析ForwardBase,ForwardAdd会在之后简单介绍与ForwardBase的差异。

vertForwardBase函数

作为一个顶点着色器,vertForwardBase的实现很常规,主要是一系列相关的坐标变换工作。在分析vertForwardBase函数之前,需要先分析一下顶点着色器输出到片段着色器的结构体VertexOutputForwardBase。这部分内容不重要,只会简单的说明一下。

 1 struct VertexOutputForwardBase
 2 {
 3     UNITY_POSITION(pos);
 4     float4 tex                          : TEXCOORD0;
 5     half3 eyeVec                        : TEXCOORD1;
 6     half4 tangentToWorldAndPackedData[3]    : TEXCOORD2;    // [3x3:tangentToWorld | 1x3:viewDirForParallax or worldPos]
 7     half4 ambientOrLightmapUV           : TEXCOORD5;    // SH or Lightmap UV
 8     UNITY_SHADOW_COORDS(6)
 9     UNITY_FOG_COORDS(7)
10 
11     // next ones would not fit into SM2.0 limits, but they are always for SM3.0+
12     #if UNITY_REQUIRE_FRAG_WORLDPOS && !UNITY_PACK_WORLDPOS_WITH_TANGENT
13         float3 posWorld                 : TEXCOORD8;
14     #endif
15 
16     UNITY_VERTEX_INPUT_INSTANCE_ID
17     UNITY_VERTEX_OUTPUT_STEREO
18 };
VertexOutputForwardBase

总体来说做了以下的工作:

1.定义变量:顶点坐标,纹理坐标,视线向量。UNITY_POSITION(pos)宏定义位于HLSLSupport.cginc,不赘述。

2.tangentToWorldAndPackedData[3]:大小为3x4,其中3x3矩阵为切线空间变换到世界空间矩阵(xyz分量),1x3为视差视线向量或世界坐标(w分量)。

3.定义变量:环境或光照贴图的坐标,阴影坐标,雾坐标。

4.针对SM3.0,定义变量:顶点世界坐标。

5.UNITY_VERTEX_INPUT_INSTANCE_ID为顶点实例化一个ID,UNITY_VERTEX_OUTPUT_STEREO来声明该顶点是否位于视线域中,来判断这个顶点是否输出到片段着色器。两个宏定义位于UnityInstancing.cginc中,GPU Instancing所需,这里不赘述。

顶点函数vertForwardBase用于填充VertexOutputForwardBase结构体。

 1 VertexOutputForwardBase vertForwardBase (VertexInput v)
 2 {
 3     UNITY_SETUP_INSTANCE_ID(v);
 4     VertexOutputForwardBase o;
 5     UNITY_INITIALIZE_OUTPUT(VertexOutputForwardBase, o);
 6     UNITY_TRANSFER_INSTANCE_ID(v, o);
 7     UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o);
 8 
 9     float4 posWorld = mul(unity_ObjectToWorld, v.vertex);
10     #if UNITY_REQUIRE_FRAG_WORLDPOS
11         #if UNITY_PACK_WORLDPOS_WITH_TANGENT
12             o.tangentToWorldAndPackedData[0].w = posWorld.x;
13             o.tangentToWorldAndPackedData[1].w = posWorld.y;
14             o.tangentToWorldAndPackedData[2].w = posWorld.z;
15         #else
16             o.posWorld = posWorld.xyz;
17         #endif
18     #endif
19     o.pos = UnityObjectToClipPos(v.vertex);
20 
21     o.tex = TexCoords(v);
22     o.eyeVec = NormalizePerVertexNormal(posWorld.xyz - _WorldSpaceCameraPos);
23     float3 normalWorld = UnityObjectToWorldNormal(v.normal);
24     #ifdef _TANGENT_TO_WORLD
25         float4 tangentWorld = float4(UnityObjectToWorldDir(v.tangent.xyz), v.tangent.w);
26 
27         float3x3 tangentToWorld = CreateTangentToWorldPerVertex(normalWorld, tangentWorld.xyz, tangentWorld.w);
28         o.tangentToWorldAndPackedData[0].xyz = tangentToWorld[0];
29         o.tangentToWorldAndPackedData[1].xyz = tangentToWorld[1];
30         o.tangentToWorldAndPackedData[2].xyz = tangentToWorld[2];
31     #else
32         o.tangentToWorldAndPackedData[0].xyz = 0;
33         o.tangentToWorldAndPackedData[1].xyz = 0;
34         o.tangentToWorldAndPackedData[2].xyz = normalWorld;
35     #endif
36 
37     //We need this for shadow receving
38     UNITY_TRANSFER_SHADOW(o, v.uv1);
39 
40     o.ambientOrLightmapUV = VertexGIForward(v, posWorld, normalWorld);
41 
42     #ifdef _PARALLAXMAP
43         TANGENT_SPACE_ROTATION;
44         half3 viewDirForParallax = mul (rotation, ObjSpaceViewDir(v.vertex));
45         o.tangentToWorldAndPackedData[0].w = viewDirForParallax.x;
46         o.tangentToWorldAndPackedData[1].w = viewDirForParallax.y;
47         o.tangentToWorldAndPackedData[2].w = viewDirForParallax.z;
48     #endif
49 
50     UNITY_TRANSFER_FOG(o,o.pos);
51     return o;
52 }
vertForwardBase

输入为VertexInput结构体,分析见后文。

总体来说做了以下的工作:

1.初始化顶点信息。这部分是GPU Instancing的相关宏定义,位于UnityInstancing.cginc中。

2.顶点世界坐标计算,并根据Shader Mode的不同来将其存储在posWorld(SM3.0)或tangentToWorldAndPackedData[3]的w分量(SM2.0)中。在SM3.0下,tangentToWorldAndPackedData[3]的w分量用来存储视差视线。

3.计算裁剪空间顶点坐标,纹理坐标,世界空间视线以及法线。TexCoords函数实现在UnityStandardInput.cginc,UnityObjectToClipPosInstanced在UnityInstancing.cginc,NormalizePerVertexNormal在UnityStandardCore.cginc,不赘述。

4.计算切线空间变换到世界空间矩阵。CreateTangentToWorldPerVertex位于UnityStandardUtils.cginc。

5.阴影坐标转换,雾坐标转换。UNITY_TRANSFER_SHADOW位于AutoLight.cginc,UNITY_TRANSFER_FOG位于UnityCG.cginc。雾的计算会根据SM不同,选择逐顶点或逐像素的计算。

6.视差视线计算,ObjSpaceViewDir和rotation都位于UnityCG.cginc

7.环境或光照贴图纹理坐标的计算,VertexGIForward的实现位于UnityStandardCore.cginc。

VertexInput结构体位于UnityStandardInput.cginc,具体实现如下。

 1 struct VertexInput
 2 {
 3     float4 vertex   : POSITION;
 4     half3 normal    : NORMAL;
 5     float2 uv0      : TEXCOORD0;
 6     float2 uv1      : TEXCOORD1;
 7 #if defined(DYNAMICLIGHTMAP_ON) || defined(UNITY_PASS_META)
 8     float2 uv2      : TEXCOORD2;
 9 #endif
10 #ifdef _TANGENT_TO_WORLD
11     half4 tangent   : TANGENT;
12 #endif
13     UNITY_VERTEX_INPUT_INSTANCE_ID
14 };
VertexInput

VertexInput结构体包含了Unity提供给顶点着色器的模型的各项信息,包括模型空间的顶点坐标,纹理坐标,顶点法线和切线。纹理坐标有三个,第一个是贴图的纹理坐标,第二个是静态光照UV(Bake GI),第三个是动态光照UV(Precompute Realtime GI)。

VertexGIForward主要进行顶点的GI计算,具体的实现分析如下。

 1 inline half4 VertexGIForward(VertexInput v, float3 posWorld, half3 normalWorld)
 2 {
 3     half4 ambientOrLightmapUV = 0;
 4     // Static lightmaps
 5     #ifdef LIGHTMAP_ON
 6         ambientOrLightmapUV.xy = v.uv1.xy * unity_LightmapST.xy + unity_LightmapST.zw;
 7         ambientOrLightmapUV.zw = 0;
 8     // Sample light probe for Dynamic objects only (no static or dynamic lightmaps)
 9     #elif UNITY_SHOULD_SAMPLE_SH
10         #ifdef VERTEXLIGHT_ON
11             // Approximated illumination from non-important point lights
12             ambientOrLightmapUV.rgb = Shade4PointLights (
13                 unity_4LightPosX0, unity_4LightPosY0, unity_4LightPosZ0,
14                 unity_LightColor[0].rgb, unity_LightColor[1].rgb, unity_LightColor[2].rgb, unity_LightColor[3].rgb,
15                 unity_4LightAtten0, posWorld, normalWorld);
16         #endif
17 
18         ambientOrLightmapUV.rgb = ShadeSHPerVertex (normalWorld, ambientOrLightmapUV.rgb);
19     #endif
20 
21     #ifdef DYNAMICLIGHTMAP_ON
22         ambientOrLightmapUV.zw = v.uv2.xy * unity_DynamicLightmapST.xy + unity_DynamicLightmapST.zw;
23     #endif
24 
25     return ambientOrLightmapUV;
26 }
VertexGIForward

输入:VertexInput,顶点世界坐标,世界法线。

Unity的GI实现有两种方式,一种是烘焙GI(Bake GI),一种是预计算实时GI(PRGI,Precompute Realtime GI)。对于烘焙GI来说,lightmap是静态的;对于预计算实时GI来说,lightmap是动态的。预计算实时GI在Unity官方中文论坛有比较详细的介绍,不赘述。

然而,在VertexGIForward的计算中,根据GI的实现方式有三个分支。首先是烘焙GI的实现,unity_LightmapST的xy记录了lightmap的scale值,zw记录了lightmap的offset值。第二个分支是SH(球谐函数)的计算,SH的计算在存在GI的情况下是不进行计算的,因为lightmap中已经包含了漫反射间接环境光照。所以在没有lightmap的情况下,进行SH计算。追求效率,用于SH计算的点光源被设置为了4个,同时unity在QualitySetting中的pixel light count也是设置为4。第三个分支是预计算实时GI的实现,unity_DynamicLightmapST和unity_LightmapST类似。

ambientOrLightmapUV在启用光照贴图的情况下,其xyzw分量用来存储光照贴图的UV。在不启用光照贴图的情况下,其rgb(xyz)分量用来保存SH计算的颜色。

Shade4PointLights,ShadeSHPerVertex函数实现暂时不赘述,Shade4PointLights计算四个点光源的方式比较巧妙,有时间会讲。

fragForwardBaseInternal函数

 1 half4 fragForwardBaseInternal (VertexOutputForwardBase i)
 2 {
 3     UNITY_APPLY_DITHER_CROSSFADE(i.pos.xy);
 4 
 5     FRAGMENT_SETUP(s)
 6 
 7     UNITY_SETUP_INSTANCE_ID(i);
 8     UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(i);
 9 
10     UnityLight mainLight = MainLight ();
11     UNITY_LIGHT_ATTENUATION(atten, i, s.posWorld);
12 
13     half occlusion = Occlusion(i.tex.xy);
14     UnityGI gi = FragmentGI (s, occlusion, i.ambientOrLightmapUV, atten, mainLight);
15 
16     half4 c = UNITY_BRDF_PBS (s.diffColor, s.specColor, s.oneMinusReflectivity, s.smoothness, s.normalWorld, -s.eyeVec, gi.light, gi.indirect);
17     c.rgb += Emission(i.tex.xy);
18 
19     UNITY_APPLY_FOG(i.fogCoord, c.rgb);
20     return OutputForward (c, s.alpha);
21 }
fragForwardBaseInternal

总体来说做了以下的工作:

1.初始化片段设置,暂时不赘述。

2.获取设置的主光源信息。UnityLight结构体记录了灯光的方向,颜色。MainLight函数则把主光源的信息填充到结构体中。

UnityLight结构体定义在UnityLightingCommon.cginc中。

1 struct UnityLight
2 {
3     half3 color;
4     half3 dir;
5     half  ndotl; // Deprecated: Ndotl is now calculated on the fly and is no longer stored. Do not used it.
6 };
UnityLight

MainLight函数定义在UnityStandardCore.cginc中。

1 UnityLight MainLight ()
2 {
3     UnityLight l;
4 
5     l.color = _LightColor0.rgb;
6     l.dir = _WorldSpaceLightPos0.xyz;
7     return l;
8 }
MainLight

3.计算灯光的衰减信息。

4.计算遮罩,遮罩的意义是利用Occlusion Map以及Occlusion Strength(SM3.0)来控制物体表面接收间接光照的强度。

Occlusion函数——UnityStandardInput.cginc中。

 1 half Occlusion(float2 uv)
 2 {
 3 #if (SHADER_TARGET < 30)
 4     // SM20: instruction count limitation
 5     // SM20: simpler occlusion
 6     return tex2D(_OcclusionMap, uv).g;
 7 #else
 8     half occ = tex2D(_OcclusionMap, uv).g;
 9     return LerpOneTo (occ, _OcclusionStrength);
10 #endif
11 }
Occlusion

实现原理比较简单:在SM2.0下,由于指令数限制,直接从遮罩图中采样返回green通道即可;在SM3.0下,采样后,需要多做一步计算。Occlusion Map一般是一张灰度图,Unity这里只使用了它的Green通道。_OcclusionMap和_OcclusionStrength都是Standard Shader暴露给编辑器的变量。

LerpOneTo函数——UnityStandardUtils.cginc

1 half LerpOneTo(half b, half t)
2 {
3     half oneMinusT = 1 - t;
4     return oneMinusT + b * t;
5 }
LerpOneTo

LerpOneTo的实现比较简单,类似Lerp函数的计算,返回值为1-_OcclusionStrength+occ*_OcclusionStrength。其实也就等价于Lerp(1,occ,_OcclusionStrength)。

5.计算片段GI。

在分析Fragment函数之前,先简单过一下Fragment函数用到的几个结构体。

FragmentCommonData:包括了片段函数需要的一些常规的数据,包括漫反射颜色,镜面反射颜色,一减反射率,平滑度,世界空间法线,视线,世界空间坐标,alpha。以及如果是simple模式下,定义反射uvw和切线空间法线。

 1 struct FragmentCommonData
 2 {
 3     half3 diffColor, specColor;
 4     // Note: smoothness & oneMinusReflectivity for optimization purposes, mostly for DX9 SM2.0 level.
 5     // Most of the math is being done on these (1-x) values, and that saves a few precious ALU slots.
 6     half oneMinusReflectivity, smoothness;
 7     half3 normalWorld, eyeVec, posWorld;
 8     half alpha;
 9 
10 #if UNITY_STANDARD_SIMPLE
11     half3 reflUVW;
12 #endif
13 
14 #if UNITY_STANDARD_SIMPLE
15     half3 tangentSpaceNormal;
16 #endif
17 };
FragmentCommonData

UnityGI:记录了一个记录灯光信息的UnityLight对象和一个记录间接光照信息的UnityIndirect对象。

1 struct UnityGI
2 {
3     UnityLight light;
4     UnityIndirect indirect;
5 };
UnityGI

UnityIndirect结构体的两个变量分别是漫反射颜色和镜面反射颜色。

1 struct UnityIndirect
2 {
3     half3 diffuse;
4     half3 specular;
5 };
UnityIndirect

UnityGIInput——-UnityLightingCommon.cginc

 1 struct UnityGIInput
 2 {
 3     UnityLight light; // pixel light, sent from the engine
 4 
 5     float3 worldPos;
 6     half3 worldViewDir;
 7     half atten;
 8     half3 ambient;
 9 
10     // interpolated lightmap UVs are passed as full float precision data to fragment shaders
11     // so lightmapUV (which is used as a tmp inside of lightmap fragment shaders) should
12     // also be full float precision to avoid data loss before sampling a texture.
13     float4 lightmapUV; // .xy = static lightmap UV, .zw = dynamic lightmap UV
14 
15     #if defined(UNITY_SPECCUBE_BLENDING) || defined(UNITY_SPECCUBE_BOX_PROJECTION)
16     float4 boxMin[2];
17     #endif
18     #ifdef UNITY_SPECCUBE_BOX_PROJECTION
19     float4 boxMax[2];
20     float4 probePosition[2];
21     #endif
22     // HDR cubemap properties, use to decompress HDR texture
23     float4 probeHDR[2];
24 };
UnityGIInput

包括一个UnityLight对象,以及片段的世界空间坐标,世界空间视线,灯光的衰减,环境色。光照贴图的UV,出于精度考虑使用float来避免光照贴图采样精度丢失。xy分量是静态光照贴图UV,zw分量是动态光照贴图UV。之后是用于反射探针盒投影,反射探针混合,以及HDR天空的变量,在FragmentGI函数中会用到。

UNITY_SPECCUBE_BOX_PROJECTION&UNITY_SPECCUBE_BLENDING——UnityStandardConfig.cginc。

1 // "platform caps" defines: they are controlled from TierSettings (Editor will determine values and pass them to compiler)
2 // UNITY_SPECCUBE_BOX_PROJECTION:                   TierSettings.reflectionProbeBoxProjection
3 // UNITY_SPECCUBE_BLENDING:                         TierSettings.reflectionProbeBlending
4 // UNITY_ENABLE_DETAIL_NORMALMAP:                   TierSettings.detailNormalMap
5 // UNITY_USE_DITHER_MASK_FOR_ALPHABLENDED_SHADOWS:  TierSettings.semitransparentShadows
TierSettings

TierSettings来控制的,当设置为启用时,会自动生成相关的宏定义。具体使用查看Unity Scripting API。

UNITY_SPECCUBE_BOX_PROJECTION: TierSettings.reflectionProbeBoxProjection——指定反射探针盒投影是否启用。
UNITY_SPECCUBE_BLENDING: TierSettings.reflectionProbeBlending——指定反射探针混合是否启用。

FragmentGI函数——UnityStandardCore.cginc

 1 inline UnityGI FragmentGI (FragmentCommonData s, half occlusion, half4 i_ambientOrLightmapUV, half atten, UnityLight light, bool reflections)
 2 {
 3     UnityGIInput d;
 4     d.light = light;
 5     d.worldPos = s.posWorld;
 6     d.worldViewDir = -s.eyeVec;
 7     d.atten = atten;
 8     #if defined(LIGHTMAP_ON) || defined(DYNAMICLIGHTMAP_ON)
 9         d.ambient = 0;
10         d.lightmapUV = i_ambientOrLightmapUV;
11     #else
12         d.ambient = i_ambientOrLightmapUV.rgb;
13         d.lightmapUV = 0;
14     #endif
15 
16     d.probeHDR[0] = unity_SpecCube0_HDR;
17     d.probeHDR[1] = unity_SpecCube1_HDR;
18     #if defined(UNITY_SPECCUBE_BLENDING) || defined(UNITY_SPECCUBE_BOX_PROJECTION)
19       d.boxMin[0] = unity_SpecCube0_BoxMin; // .w holds lerp value for blending
20     #endif
21     #ifdef UNITY_SPECCUBE_BOX_PROJECTION
22       d.boxMax[0] = unity_SpecCube0_BoxMax;
23       d.probePosition[0] = unity_SpecCube0_ProbePosition;
24       d.boxMax[1] = unity_SpecCube1_BoxMax;
25       d.boxMin[1] = unity_SpecCube1_BoxMin;
26       d.probePosition[1] = unity_SpecCube1_ProbePosition;
27     #endif
28 
29     if(reflections)
30     {
31         Unity_GlossyEnvironmentData g = UnityGlossyEnvironmentSetup(s.smoothness, -s.eyeVec, s.normalWorld, s.specColor);
32         // Replace the reflUVW if it has been compute in Vertex shader. Note: the compiler will optimize the calcul in UnityGlossyEnvironmentSetup itself
33         #if UNITY_STANDARD_SIMPLE
34             g.reflUVW = s.reflUVW;
35         #endif
36 
37         return UnityGlobalIllumination (d, occlusion, s.normalWorld, g);
38     }
39     else
40     {
41         return UnityGlobalIllumination (d, occlusion, s.normalWorld);
42     }
43 }
FragmentGI

FragmentGI函数主要可以分为两个部分,一个是填充UnityGIInput结构体,一个计算反射调用UnityGlobalIllumination函数的过程。

填充UnityGIInput的过程,灯光+世界空间顶点坐标+观察方向(视线的反方向)+衰减直接赋值即可。随后是光照贴图,在启用了静态光照贴图或者动态光照贴图的情况下,环境光为0,然后获得光照贴图的UV。否则的话,ambient直接使用VertexGIForward计算的rgb值。

然后是反射探针的相关计算,这部分计算需要涉及到一系列变量声明。

Reflection Probes——UnityShaderVariables.cginc

 1 UNITY_DECLARE_TEXCUBE(unity_SpecCube0);
 2 UNITY_DECLARE_TEXCUBE_NOSAMPLER(unity_SpecCube1);
 3 
 4 CBUFFER_START(UnityReflectionProbes)
 5     float4 unity_SpecCube0_BoxMin;
 6     float4 unity_SpecCube0_ProbePosition;
 7     half4  unity_SpecCube0_HDR;
 8 
 9     float4 unity_SpecCube1_BoxMax;
10     float4 unity_SpecCube1_BoxMin;
11     float4 unity_SpecCube1_ProbePosition;
12     half4  unity_SpecCube1_HDR;
13 CBUFFER_END
Reflection Probes

UNITY_DECLARE_TEXCUBE:声明了一个TextureCube类型的对象。
UNITY_DECLARE_TEXCUBE_NOSAMPLER:声明了一个TextureCube类型的对象(无Sampler)。

CBUFFER_START&CBUFFER_END:声明了一块常量缓冲区。

以上的宏定义在HLSLSupport.cginc文件中。

HDR探针将常量缓冲中的对应变量保存。在UNITY_SPECCUBE_BOX_PROJECTION或者UNITY_SPECCUBE_BLENDING被启用的情况下,保存相应的反射探针属性。

如果反射为真的情况下,计算反射,先计算反射的环境数据,包括镜面照明和天空等。然后调用UnityGlobalIllumination函数。

Unity_GlossyEnvironmentData结构体:延迟渲染只有一个cubemap,前向渲染的情况下可以有两个混合的cubemap,不常用应该会被弃用。另外,粗糙度这里是感性粗糙度,因为兼容性而使用了粗糙度的变量名。关于粗糙度和感性粗糙度之间的区别,见后文。

1 struct Unity_GlossyEnvironmentData
2 {
3     // - Deferred case have one cubemap
4     // - Forward case can have two blended cubemap (unusual should be deprecated).
5 
6     // Surface properties use for cubemap integration
7     half    roughness; // CAUTION: This is perceptualRoughness but because of compatibility this name can't be change :(
8     half3   reflUVW;
9 };
Unity_GlossyEnvironmentData

UnityGlossyEnvironmentSetup函数:计算感性粗糙度,计算反射,并返回对象。

1 Unity_GlossyEnvironmentData UnityGlossyEnvironmentSetup(half Smoothness, half3 worldViewDir, half3 Normal, half3 fresnel0)
2 {
3     Unity_GlossyEnvironmentData g;
4 
5     g.roughness /* perceptualRoughness */   = SmoothnessToPerceptualRoughness(Smoothness);
6     g.reflUVW   = reflect(-worldViewDir, Normal);
7 
8     return g;
9 }
UnityGlossyEnvironmentSetup

UnityGlobalIllumination函数

 1 inline UnityGI UnityGlobalIllumination (UnityGIInput data, half occlusion, half3 normalWorld)
 2 {
 3     return UnityGI_Base(data, occlusion, normalWorld);
 4 }
 5 
 6 inline UnityGI UnityGlobalIllumination (UnityGIInput data, half occlusion, half3 normalWorld, Unity_GlossyEnvironmentData glossIn)
 7 {
 8     UnityGI o_gi = UnityGI_Base(data, occlusion, normalWorld);
 9     o_gi.indirect.specular = UnityGI_IndirectSpecular(data, occlusion, glossIn);
10     return o_gi;
11 }
UnityGlobalIllumination

UnityGlobalIllumination函数位于同名的cginc文件中,同名的函数有四个(形参不同)。有两个函数实现是旧版本的,并在注释上说明了只是为了旧版本兼容即将被移除,所以我们这里只看最新的函数实现。

由FragmentGI函数可知,两种实现分别对应无反射和有反射两种情况。

函数1(无反射):直接返回UnityGI_Base函数的值,UnityGI_Base解释见下文。

函数2(有反射):调用UnityGI_Base函数,得到返回值o_gi 然后调用UnityGI_IndirectSpecular修改o_gi.indirect.specular的值,UnityGI_IndirectSpecular解释见下文。

UnityGI_Base函数

 1 inline UnityGI UnityGI_Base(UnityGIInput data, half occlusion, half3 normalWorld)
 2 {
 3     UnityGI o_gi;
 4     ResetUnityGI(o_gi);
 5 
 6     // Base pass with Lightmap support is responsible for handling ShadowMask / blending here for performance reason
 7     #if defined(HANDLE_SHADOWS_BLENDING_IN_GI)
 8         half bakedAtten = UnitySampleBakedOcclusion(data.lightmapUV.xy, data.worldPos);
 9         float zDist = dot(_WorldSpaceCameraPos - data.worldPos, UNITY_MATRIX_V[2].xyz);
10         float fadeDist = UnityComputeShadowFadeDistance(data.worldPos, zDist);
11         data.atten = UnityMixRealtimeAndBakedShadows(data.atten, bakedAtten, UnityComputeShadowFade(fadeDist));
12     #endif
13 
14     o_gi.light = data.light;
15     o_gi.light.color *= data.atten;
16 
17     #if UNITY_SHOULD_SAMPLE_SH
18         o_gi.indirect.diffuse = ShadeSHPerPixel (normalWorld, data.ambient, data.worldPos);
19     #endif
20 
21     #if defined(LIGHTMAP_ON)
22         // Baked lightmaps
23         half4 bakedColorTex = UNITY_SAMPLE_TEX2D(unity_Lightmap, data.lightmapUV.xy);
24         half3 bakedColor = DecodeLightmap(bakedColorTex);
25 
26         #ifdef DIRLIGHTMAP_COMBINED
27             fixed4 bakedDirTex = UNITY_SAMPLE_TEX2D_SAMPLER (unity_LightmapInd, unity_Lightmap, data.lightmapUV.xy);
28             o_gi.indirect.diffuse = DecodeDirectionalLightmap (bakedColor, bakedDirTex, normalWorld);
29 
30             #if defined(LIGHTMAP_SHADOW_MIXING) && !defined(SHADOWS_SHADOWMASK) && defined(SHADOWS_SCREEN)
31                 ResetUnityLight(o_gi.light);
32                 o_gi.indirect.diffuse = SubtractMainLightWithRealtimeAttenuationFromLightmap (o_gi.indirect.diffuse, data.atten, bakedColorTex, normalWorld);
33             #endif
34 
35         #else // not directional lightmap
36             o_gi.indirect.diffuse = bakedColor;
37 
38             #if defined(LIGHTMAP_SHADOW_MIXING) && !defined(SHADOWS_SHADOWMASK) && defined(SHADOWS_SCREEN)
39                 ResetUnityLight(o_gi.light);
40                 o_gi.indirect.diffuse = SubtractMainLightWithRealtimeAttenuationFromLightmap(o_gi.indirect.diffuse, data.atten, bakedColorTex, normalWorld);
41             #endif
42 
43         #endif
44     #endif
45 
46     #ifdef DYNAMICLIGHTMAP_ON
47         // Dynamic lightmaps
48         fixed4 realtimeColorTex = UNITY_SAMPLE_TEX2D(unity_DynamicLightmap, data.lightmapUV.zw);
49         half3 realtimeColor = DecodeRealtimeLightmap (realtimeColorTex);
50 
51         #ifdef DIRLIGHTMAP_COMBINED
52             half4 realtimeDirTex = UNITY_SAMPLE_TEX2D_SAMPLER(unity_DynamicDirectionality, unity_DynamicLightmap, data.lightmapUV.zw);
53             o_gi.indirect.diffuse += DecodeDirectionalLightmap (realtimeColor, realtimeDirTex, normalWorld);
54         #else
55             o_gi.indirect.diffuse += realtimeColor;
56         #endif
57     #endif
58 
59     o_gi.indirect.diffuse *= occlusion;
60     return o_gi;
61 }
UnityGI_Base

函数依次实现的是阴影遮罩,SH计算(非静态GI非动态GI),静态GI,动态GI。

ShadowMask阴影遮罩是Unity5.6版本的新特性。

烘焙GI的实现:首先从烘焙的光照贴图中取得对应的像素值,其次根据编码对像素进行译码。如果定义了directional lightmap混合,会进行和烘焙光照贴图类似的过程并进行混合计算。否则的话,直接使用烘焙GI的颜色进行计算。

动态GI的实现:和静态GI的实现类似。

ResetUnityGI函数:将UnityGI结构体初始化,无需多说。

1 inline void ResetUnityGI(out UnityGI outGI)
2 {
3     ResetUnityLight(outGI.light);
4     outGI.indirect.diffuse = 0;
5     outGI.indirect.specular = 0;
6 }
ResetUnityGI

UnityGI_IndirectSpecular函数

 1 inline half3 UnityGI_IndirectSpecular(UnityGIInput data, half occlusion, Unity_GlossyEnvironmentData glossIn)
 2 {
 3     half3 specular;
 4 
 5     #ifdef UNITY_SPECCUBE_BOX_PROJECTION
 6         // we will tweak reflUVW in glossIn directly (as we pass it to Unity_GlossyEnvironment twice for probe0 and probe1), so keep original to pass into BoxProjectedCubemapDirection
 7         half3 originalReflUVW = glossIn.reflUVW;
 8         glossIn.reflUVW = BoxProjectedCubemapDirection (originalReflUVW, data.worldPos, data.probePosition[0], data.boxMin[0], data.boxMax[0]);
 9     #endif
10 
11     #ifdef _GLOSSYREFLECTIONS_OFF
12         specular = unity_IndirectSpecColor.rgb;
13     #else
14         half3 env0 = Unity_GlossyEnvironment (UNITY_PASS_TEXCUBE(unity_SpecCube0), data.probeHDR[0], glossIn);
15         #ifdef UNITY_SPECCUBE_BLENDING
16             const float kBlendFactor = 0.99999;
17             float blendLerp = data.boxMin[0].w;
18             UNITY_BRANCH
19             if (blendLerp < kBlendFactor)
20             {
21                 #ifdef UNITY_SPECCUBE_BOX_PROJECTION
22                     glossIn.reflUVW = BoxProjectedCubemapDirection (originalReflUVW, data.worldPos, data.probePosition[1], data.boxMin[1], data.boxMax[1]);
23                 #endif
24 
25                 half3 env1 = Unity_GlossyEnvironment (UNITY_PASS_TEXCUBE_SAMPLER(unity_SpecCube1,unity_SpecCube0), data.probeHDR[1], glossIn);
26                 specular = lerp(env1, env0, blendLerp);
27             }
28             else
29             {
30                 specular = env0;
31             }
32         #else
33             specular = env0;
34         #endif
35     #endif
36 
37     return specular * occlusion;
38 }
39 
40 // Deprecated old prototype but can't be move to Deprecated.cginc file due to order dependency
41 inline half3 UnityGI_IndirectSpecular(UnityGIInput data, half occlusion, half3 normalWorld, Unity_GlossyEnvironmentData glossIn)
42 {
43     // normalWorld is not used
44     return UnityGI_IndirectSpecular(data, occlusion, glossIn);
45 }
UnityGI_IndirectSpecular

BoxProjectedCubemapDirection函数

 1 inline half3 BoxProjectedCubemapDirection (half3 worldRefl, float3 worldPos, float4 cubemapCenter, float4 boxMin, float4 boxMax)
 2 {
 3     // Do we have a valid reflection probe?
 4     UNITY_BRANCH
 5     if (cubemapCenter.w > 0.0)
 6     {
 7         half3 nrdir = normalize(worldRefl);
 8 
 9         #if 1
10             half3 rbmax = (boxMax.xyz - worldPos) / nrdir;
11             half3 rbmin = (boxMin.xyz - worldPos) / nrdir;
12 
13             half3 rbminmax = (nrdir > 0.0f) ? rbmax : rbmin;
14 
15         #else // Optimized version
16             half3 rbmax = (boxMax.xyz - worldPos);
17             half3 rbmin = (boxMin.xyz - worldPos);
18 
19             half3 select = step (half3(0,0,0), nrdir);
20             half3 rbminmax = lerp (rbmax, rbmin, select);
21             rbminmax /= nrdir;
22         #endif
23 
24         half fa = min(min(rbminmax.x, rbminmax.y), rbminmax.z);
25 
26         worldPos -= cubemapCenter.xyz;
27         worldRefl = worldPos + nrdir * fa;
28     }
29     return worldRefl;
30 }
BoxProjectedCubemapDirection

 6.计算颜色,这部分为重点内容,后文详细讲解。

7.计算自发光。

1 half3 Emission(float2 uv)
2 {
3 #ifndef _EMISSION
4     return 0;
5 #else
6     return tex2D(_EmissionMap, uv).rgb * _EmissionColor.rgb;
7 #endif
8 }
Emission

8.应用雾

 1 // ------------------------------------------------------------------
 2 //  Fog helpers
 3 //
 4 //  multi_compile_fog Will compile fog variants.
 5 //  UNITY_FOG_COORDS(texcoordindex) Declares the fog data interpolator.
 6 //  UNITY_TRANSFER_FOG(outputStruct,clipspacePos) Outputs fog data from the vertex shader.
 7 //  UNITY_APPLY_FOG(fogData,col) Applies fog to color "col". Automatically applies black fog when in forward-additive pass.
 8 //  Can also use UNITY_APPLY_FOG_COLOR to supply your own fog color.
 9 
10 // In case someone by accident tries to compile fog code in one of the g-buffer or shadow passes:
11 // treat it as fog is off.
12 #if defined(UNITY_PASS_PREPASSBASE) || defined(UNITY_PASS_DEFERRED) || defined(UNITY_PASS_SHADOWCASTER)
13 #undef FOG_LINEAR
14 #undef FOG_EXP
15 #undef FOG_EXP2
16 #endif
17 
18 #if defined(UNITY_REVERSED_Z)
19     //D3d with reversed Z => z clip range is [near, 0] -> remapping to [0, far]
20     //max is required to protect ourselves from near plane not being correct/meaningfull in case of oblique matrices.
21     #define UNITY_Z_0_FAR_FROM_CLIPSPACE(coord) max(((1.0-(coord)/_ProjectionParams.y)*_ProjectionParams.z),0)
22 #elif UNITY_UV_STARTS_AT_TOP
23     //D3d without reversed z => z clip range is [0, far] -> nothing to do
24     #define UNITY_Z_0_FAR_FROM_CLIPSPACE(coord) (coord)
25 #else
26     //Opengl => z clip range is [-near, far] -> should remap in theory but dont do it in practice to save some perf (range is close enought)
27     #define UNITY_Z_0_FAR_FROM_CLIPSPACE(coord) (coord)
28 #endif
29 
30 #if defined(FOG_LINEAR)
31     // factor = (end-z)/(end-start) = z * (-1/(end-start)) + (end/(end-start))
32     #define UNITY_CALC_FOG_FACTOR_RAW(coord) float unityFogFactor = (coord) * unity_FogParams.z + unity_FogParams.w
33 #elif defined(FOG_EXP)
34     // factor = exp(-density*z)
35     #define UNITY_CALC_FOG_FACTOR_RAW(coord) float unityFogFactor = unity_FogParams.y * (coord); unityFogFactor = exp2(-unityFogFactor)
36 #elif defined(FOG_EXP2)
37     // factor = exp(-(density*z)^2)
38     #define UNITY_CALC_FOG_FACTOR_RAW(coord) float unityFogFactor = unity_FogParams.x * (coord); unityFogFactor = exp2(-unityFogFactor*unityFogFactor)
39 #else
40     #define UNITY_CALC_FOG_FACTOR_RAW(coord) float unityFogFactor = 0.0
41 #endif
42 
43 #define UNITY_CALC_FOG_FACTOR(coord) UNITY_CALC_FOG_FACTOR_RAW(UNITY_Z_0_FAR_FROM_CLIPSPACE(coord))
44 
45 #define UNITY_FOG_COORDS_PACKED(idx, vectype) vectype fogCoord : TEXCOORD##idx;
46 
47 #if defined(FOG_LINEAR) || defined(FOG_EXP) || defined(FOG_EXP2)
48     #define UNITY_FOG_COORDS(idx) UNITY_FOG_COORDS_PACKED(idx, float1)
49 
50     #if (SHADER_TARGET < 30) || defined(SHADER_API_MOBILE)
51         // mobile or SM2.0: calculate fog factor per-vertex
52         #define UNITY_TRANSFER_FOG(o,outpos) UNITY_CALC_FOG_FACTOR((outpos).z); o.fogCoord.x = unityFogFactor
53     #else
54         // SM3.0 and PC/console: calculate fog distance per-vertex, and fog factor per-pixel
55         #define UNITY_TRANSFER_FOG(o,outpos) o.fogCoord.x = (outpos).z
56     #endif
57 #else
58     #define UNITY_FOG_COORDS(idx)
59     #define UNITY_TRANSFER_FOG(o,outpos)
60 #endif
61 
62 #define UNITY_FOG_LERP_COLOR(col,fogCol,fogFac) col.rgb = lerp((fogCol).rgb, (col).rgb, saturate(fogFac))
63 
64 
65 #if defined(FOG_LINEAR) || defined(FOG_EXP) || defined(FOG_EXP2)
66     #if (SHADER_TARGET < 30) || defined(SHADER_API_MOBILE)
67         // mobile or SM2.0: fog factor was already calculated per-vertex, so just lerp the color
68         #define UNITY_APPLY_FOG_COLOR(coord,col,fogCol) UNITY_FOG_LERP_COLOR(col,fogCol,(coord).x)
69     #else
70         // SM3.0 and PC/console: calculate fog factor and lerp fog color
71         #define UNITY_APPLY_FOG_COLOR(coord,col,fogCol) UNITY_CALC_FOG_FACTOR((coord).x); UNITY_FOG_LERP_COLOR(col,fogCol,unityFogFactor)
72     #endif
73 #else
74     #define UNITY_APPLY_FOG_COLOR(coord,col,fogCol)
75 #endif
76 
77 #ifdef UNITY_PASS_FORWARDADD
78     #define UNITY_APPLY_FOG(coord,col) UNITY_APPLY_FOG_COLOR(coord,col,fixed4(0,0,0,0))
79 #else
80     #define UNITY_APPLY_FOG(coord,col) UNITY_APPLY_FOG_COLOR(coord,col,unity_FogColor)
81 #endif
UNITY_APPLY_FOG_COLOR

9.返回颜色和Alpha值。

UNITY_BRDF_PBS函数

代码中有三个BRDF的实现函数,对应不同的BRDF模型实现。这里主要分析第一个BRDF函数,也就是BRDF1_Unity_PBS,使用的BRDF模型的公式可以参考:http://graphicrants.blogspot.com/2013/08/specular-brdf-reference.html

首先,我们要清楚BRDF1_Unity_PBS函数的依据,即根据Torrance-Sparrow 微表面模型的公式:
f(l,v)=D(h)F(v,h)G(l,v,h)/(4(n⋅l)(n⋅v))
以及BRDF公式:
BRDF = kD / pi + kS * (D * V * F) / 4
然后拆分公式,一项项的实现。

D——微表面分布项
V——遮挡可见性项
F——菲涅尔反射项
kD——漫反射系数
kS——镜面反射系数
Note:V(Visibility)项即G(l,v,h)/(4(n⋅l)(n⋅v))的集合。

菲涅尔反射F

Schlick菲涅尔反射的公式为:

F=F0+(1-F0)*(1-(H*V))^5
F0:光线垂直入射时的表面反射率
H:半角向量
V:视线
此处输入的参数cosA,为saturate(dot(H,V))的值,也可能是abs(dot(H,V))。

1 inline half3 FresnelTerm (half3 F0, half cosA)
2 {
3     half t = Pow5 (1 - cosA);   // ala Schlick interpoliation
4     return F0 + (1-F0) * t;
5 }
FresnelTerm

V&D分支

V项和D项,在代码中出现了分支,用来选择不同的模型:
如果UNITY_BRDF_GGX为真,V项和D项使用GGX的公式来实现。
否则,V项和D项使用Smith-BeckmannBlinn-Phong公式实现。

GGX相比Blinn-Phong,它更接近粗糙表面上真实反射的外观。

分支选择代码如下:

 1 #if UNITY_BRDF_GGX
 2     // GGX with roughtness to 0 would mean no specular at all, using max(roughness, 0.002) here to match HDrenderloop roughtness remapping.
 3     roughness = max(roughness, 0.002);
 4     half V = SmithJointGGXVisibilityTerm (nl, nv, roughness);
 5     float D = GGXTerm (nh, roughness);
 6 #else
 7     // Legacy
 8     half V = SmithBeckmannVisibilityTerm (nl, nv, roughness);
 9     half D = NDFBlinnPhongNormalizedTerm (nh, PerceptualRoughnessToSpecPower(perceptualRoughness));
10 #endif
V&D

遮挡可见性项V

依次分析两个Visbility函数。首先是SmithJointGGXVisibilityTerm ,Unity使用了简化版的近似公式。

在Unity中V项即是公式中的G项,只是Unity为了简化运算,使得V=G/(4(n⋅l)(n⋅v))。

SmithJointGGXVisibilityTerm使用了Smith-Joint GGX公式,具体的G项公式见:

https://hal.inria.fr/hal-00942452v1/document

Page 26 ——section 5 Equation (21)

计算lambda的公式见:

Page 13——section 3.2 

χ+(α):Heaviside function: 1 if a > 0 and 0 if a ≤ 0

上面就是Original formulation这部分被注释掉的原始公式了,正式的代码对上述公式进行了以下优化:

1.每个lambda都+0.5,来抵消 G = 1 / (1 + lambda_v + lambda_l)中分母的1。

2.每项lambda乘(2.0f * NdotL * NdotV)进行化简并整理即可得出。

3.V=G/(4(n⋅l)(n⋅v))的计算,来抵消部分上式乘的(2.0f * NdotL * NdotV)。

当然,因为#if 0以上的代码都没有执行……

Unity最后使用了Smith-Joint的近似公式(UE4使用了同样的公式),因为上述的代码依旧很复杂。考虑到整体的性能,牺牲一部分难以察觉到的精度来提升效率是必要的。

lambdaV = NdotL * sqrt((-NdotV * a2 + NdotV) * NdotV + a2);
lambdaV = NdotL * sqrt((a2-1)* NdotV2  + a2);
lambdaV ≈ NdotL * ((a-1)* NdotV + a);

1e-5f用来避免分母为0的情况。

 1 inline half SmithJointGGXVisibilityTerm (half NdotL, half NdotV, half roughness)
 2 {
 3 #if 0
 4     // Original formulation:
 5     //  lambda_v    = (-1 + sqrt(a2 * (1 - NdotL2) / NdotL2 + 1)) * 0.5f;
 6     //  lambda_l    = (-1 + sqrt(a2 * (1 - NdotV2) / NdotV2 + 1)) * 0.5f;
 7     //  G           = 1 / (1 + lambda_v + lambda_l);
 8 
 9     // Reorder code to be more optimal
10     half a          = roughness;
11     half a2         = a * a;
12 
13     half lambdaV    = NdotL * sqrt((-NdotV * a2 + NdotV) * NdotV + a2);
14     half lambdaL    = NdotV * sqrt((-NdotL * a2 + NdotL) * NdotL + a2);
15 
16     // Simplify visibility term: (2.0f * NdotL * NdotV) /  ((4.0f * NdotL * NdotV) * (lambda_v + lambda_l + 1e-5f));
17     return 0.5f / (lambdaV + lambdaL + 1e-5f);  // This function is not intended to be running on Mobile,
18                                                 // therefore epsilon is smaller than can be represented by half
19 #else
20     // Approximation of the above formulation (simplify the sqrt, not mathematically correct but close enough)
21     half a = roughness;
22     half lambdaV = NdotL * (NdotV * (1 - a) + a);
23     half lambdaL = NdotV * (NdotL * (1 - a) + a);
24 
25     return 0.5f / (lambdaV + lambdaL + 1e-5f);
26 #endif
27 }
SmithJointGGXVisibilityTerm

 然后是下一个V项的实现,SmithBeckmannVisibilityTerm函数,并在函数中调用了SmithVisibilityTerm函数。

使用的是SmithBeckmann公式,公式具体见首段链接。

SmithBeckmannVisibilityTerm函数计算了k的值,并调用SmithVisibilityTerm函数进一步计算,*0.25是先抵消V=G/(4(n⋅l)(n⋅v))中的4。

SmithBeckmann公式的分子在SmithVisibilityTerm中的gL&gV计算中与V=G/(4(n⋅l)(n⋅v))中的(n⋅l)(n⋅v)消项。

1 inline half SmithBeckmannVisibilityTerm (half NdotL, half NdotV, half roughness)
2 {
3     half c = 0.797884560802865h; // c = sqrt(2 / Pi)
4     half k = roughness * c;
5     return SmithVisibilityTerm (NdotL, NdotV, k) * 0.25f; // * 0.25 is the 1/4 of the visibility term
6 }
SmithBeckmannVisibilityTerm
1 inline half SmithVisibilityTerm (half NdotL, half NdotV, half k)
2 {
3     half gL = NdotL * (1-k) + k;
4     half gV = NdotV * (1-k) + k;
5     return 1.0 / (gL * gV + 1e-5f); // This function is not intended to be running on Mobile,
6                                     // therefore epsilon is smaller than can be represented by half
7 }
SmithVisibilityTerm

微表面分布项D

微表面分布项依旧有两个实现,依旧从GGX的实现开始。

没什么好说的,这里直接根据首段链接里的公式转换为代码即可。

UNITY_INV_PI =1 / UNITY_PI

1 inline float GGXTerm (float NdotH, float roughness)
2 {
3     float a2 = roughness * roughness;
4     float d = (NdotH * a2 - NdotH) * NdotH + 1.0f; // 2 mad
5     return UNITY_INV_PI * a2 / (d * d + 1e-7f); // This function is not intended to be running on Mobile,
6                                             // therefore epsilon is smaller than what can be represented by half
7 }
GGXTerm

然后是BlinnPhong的D项实现,NDFBlinnPhongNormalizedTerm函数。这里需要注意的是函数的输入参数n,是公式中的Power项的计算,实现函数为PerceptualRoughnessToSpecPower。

normTerm即公式中的正态分布项(首项)。

1 inline half NDFBlinnPhongNormalizedTerm (half NdotH, half n)
2 {
3     // norm = (n+2)/(2*pi)
4     half normTerm = (n + 2.0) * (0.5/UNITY_PI);
5 
6     half specTerm = pow (NdotH, n);
7     return specTerm * normTerm;
8 }
NDFBlinnPhongNormalizedTerm

PerceptualRoughnessToSpecPower函数的具体实现如下,PerceptualRoughnessToRoughness函数是返回perceptualRoughness的平方(UE4的roughness=Unity的perceptualRoughness)。

1 inline half PerceptualRoughnessToSpecPower (half perceptualRoughness)
2 {
3     half m = PerceptualRoughnessToRoughness(perceptualRoughness);   // m is the true academic roughness.
4     half sq = max(1e-4f, m*m);
5     half n = (2.0 / sq) - 2.0;                          // https://dl.dropboxusercontent.com/u/55891920/papers/mm_brdf.pdf
6     n = max(n, 1e-4f);                                  // prevent possible cases of pow(0,0), which could happen when roughness is 1.0 and NdotH is zero
7     return n;
8 }
PerceptualRoughnessToSpecPower

PerceptualRoughnessToRoughness函数的具体实现如下,用于将感性粗糙度计算为学术意义上的粗糙度。perceptualRoughness的值等于1-smoothness,在SmoothnessToPerceptualRoughness函数中实现。

1 float PerceptualRoughnessToRoughness(float perceptualRoughness)
2 {
3     return perceptualRoughness * perceptualRoughness;
4 }
PerceptualRoughnessToRoughness

SmoothnessToPerceptualRoughness函数的具体实现,用于计算感性粗糙度,smoothness即材质的光滑度贴图/参数。

1 float SmoothnessToPerceptualRoughness(float smoothness)
2 {
3     return (1 - smoothness);
4 }
SmoothnessToPerceptualRoughness

以上,Unity BRDF公式中的D/V/F项全部实现。

BRDF1_Unity_PBS函数

接下来,开始对代码中的BRDF1_Unity_PBS函数进行分析。由于函数代码比较长,在分析时,会对函数进行分块解释。同时,还会对调用的函数进行展示和讲解,所以为方便观看,我会把每块代码都标序号。

Part-1

输入:漫反射颜色,镜面反射颜色,一减反射率,平滑度,法线,视线,灯光,GI

1.计算感性粗糙度,实现函数见上文。

2.计算半角向量。

3.这段注释涉及到NdotV的取值问题。对于可见的像素来说,NdotV的取值不能为负值,但是因为透视投影和法线映射会造成这种情况。在这种情况下,法线应该修改的有效(例如面向摄像机)而不会造成奇怪的异常。但是这个修改的操作会占用一些ALU,是用户所不希望的。另一种方法是取NdotV的绝对值(不太正确,但还可以)。下面定义的宏用来控制两种实现方式,如果你的平台算术逻辑单元紧张的话,那就会将负值设置为0。这种校正对于使用Smith-Joint GGX能见度函数是很有用的,因为会导致粗糙表面的高光边缘异常会更明显。

Note:默认情况下禁用这块代码,因为跟SpeedTree使用的双面光照不兼容。

定义宏UNITY_HANDLE_CORRECTLY_NEGATIVE_NDOTV ,值为0。

0:取NdotV的绝对值。

1:计算NdotV,然后判断值的正负。若值为正,返回normal;若值为负,返回normal + viewDir * (-shiftAmount + 1e-5f)。最后,根据返回的值计算NdotV。

normal + viewDir * (-shiftAmount + 1e-5f)其实是对normal向量向视线向量接近的计算。对返回的结果应该进行重新规范化,为了节省ALU并没有这么做。之后的NdotV计算,saturate()其实没有必要了。但是因为对于操作的输出应用saturate()是没有开销的,因此这里依然使用了saturate()。

 1 half4 BRDF1_Unity_PBS (half3 diffColor, half3 specColor, half oneMinusReflectivity, half smoothness,
 2     float3 normal, float3 viewDir,
 3     UnityLight light, UnityIndirect gi)
 4 {
 5     float perceptualRoughness = SmoothnessToPerceptualRoughness (smoothness);
 6     float3 halfDir = Unity_SafeNormalize (float3(light.dir) + viewDir);
 7 
 8 // NdotV should not be negative for visible pixels, but it can happen due to perspective projection and normal mapping
 9 // In this case normal should be modified to become valid (i.e facing camera) and not cause weird artifacts.
10 // but this operation adds few ALU and users may not want it. Alternative is to simply take the abs of NdotV (less correct but works too).
11 // Following define allow to control this. Set it to 0 if ALU is critical on your platform.
12 // This correction is interesting for GGX with SmithJoint visibility function because artifacts are more visible in this case due to highlight edge of rough surface
13 // Edit: Disable this code by default for now as it is not compatible with two sided lighting used in SpeedTree.
14 #define UNITY_HANDLE_CORRECTLY_NEGATIVE_NDOTV 0
15 
16 #if UNITY_HANDLE_CORRECTLY_NEGATIVE_NDOTV
17     // The amount we shift the normal toward the view vector is defined by the dot product.
18     half shiftAmount = dot(normal, viewDir);
19     normal = shiftAmount < 0.0f ? normal + viewDir * (-shiftAmount + 1e-5f) : normal;
20     // A re-normalization should be applied here but as the shift is small we don't do it to save ALU.
21     //normal = normalize(normal);
22 
23     half nv = saturate(dot(normal, viewDir)); // TODO: this saturate should no be necessary here
24 #else
25     half nv = abs(dot(normal, viewDir));    // This abs allow to limit artifact
26 #endif
Part-1

Part-2

1.计算NdotL/NdotH/LdotV/LdotH用于后续计算。

2.计算漫反射项,使用的是DisneyDiffuse函数,之后会提到。

3.计算粗糙度,使用PerceptualRoughnessToRoughness函数将感性粗糙度转换到学术意义上的粗糙度,上文已讲解。

4.选择V项和D项的不同实现函数,上文已讲解。

理论上,应该对diffuse项除π(见首段BRDF公式),并且不乘specularTerm。

但是——1.这会导致shader看起来比原来的颜色暗很多。2.在引擎看来,设置为Non-importance的灯光当加入ambient SH计算时需要除π。(ambient SH解释见后文)

所以,Unity中的diffuseTerm 计算,并没有除π,反而乘了NdotL。

 1 half nl = saturate(dot(normal, light.dir));
 2     float nh = saturate(dot(normal, halfDir));
 3 
 4     half lv = saturate(dot(light.dir, viewDir));
 5     half lh = saturate(dot(light.dir, halfDir));
 6 
 7     // Diffuse term
 8     half diffuseTerm = DisneyDiffuse(nv, nl, lh, perceptualRoughness) * nl;
 9 
10     // Specular term
11     // HACK: theoretically we should divide diffuseTerm by Pi and not multiply specularTerm!
12     // BUT 1) that will make shader look significantly darker than Legacy ones
13     // and 2) on engine side "Non-important" lights have to be divided by Pi too in cases when they are injected into ambient SH
14     float roughness = PerceptualRoughnessToRoughness(perceptualRoughness);
15 #if UNITY_BRDF_GGX
16     // GGX with roughtness to 0 would mean no specular at all, using max(roughness, 0.002) here to match HDrenderloop roughtness remapping.
17     roughness = max(roughness, 0.002);
18     half V = SmithJointGGXVisibilityTerm (nl, nv, roughness);
19     float D = GGXTerm (nh, roughness);
20 #else
21     // Legacy
22     half V = SmithBeckmannVisibilityTerm (nl, nv, roughness);
23     half D = NDFBlinnPhongNormalizedTerm (nh, PerceptualRoughnessToSpecPower(perceptualRoughness));
24 #endif
Part-2

 DisneyDiffuse函数

输入:NdotV,NdotL,LdotH,感性粗糙度。

传统的漫反射计算使用的是Lambert模型,但是使用Lambert模型会使得物体的边缘过暗,和真实的表现有差异。因此,Disney的Diffuse计算采用了schlick的近似菲涅尔计算公式来弥补效果,公式见:

https://disney-animation.s3.amazonaws.com/library/s2012_pbs_disney_brdf_notes_v2.pdf

Page 14——section 5.3

在Unity的计算中,将公式的diffuseAlbedo / PI,即baseColor/π挪到函数外进行计算。

1 half DisneyDiffuse(half NdotV, half NdotL, half LdotH, half perceptualRoughness)
2 {
3     half fd90 = 0.5 + 2 * LdotH * LdotH * perceptualRoughness;
4     // Two schlick fresnel term
5     half lightScatter   = (1 + (fd90 - 1) * Pow5(1 - NdotL));
6     half viewScatter    = (1 + (fd90 - 1) * Pow5(1 - NdotV));
7 
8     return lightScatter * viewScatter;
9 }
DisneyDiffuse

 Part-3

BRDF1_Unity_PBS函数的最后一段代码,反而内容比较多。

1.计算高光项的一部分,菲涅尔项最后再添加。如果镜面反射高光关闭,那么镜面反射项为0。

2.如果开启了颜色空间GAMMA校正,那么这里会进行一次计算。

3.计算surfaceReduction参数。Unity在注释中给出了它的公式,但我并没有查到计算他的目的,在这里它用于间接光照的计算。

4.为了提供真正的Lambert照明,如果SpecColor的各个通道值均为0,那么就是全漫反射。

 any - returns true if a boolean scalar or any component of a boolean vector is true.

5.最后的color输出,分为三个部分:漫反射+镜面反射+表面衰减。

漫反射:输入的漫反射颜色(纹理)*GI的漫反射颜色(间接光照)+输入的漫反射颜色(纹理)*光照颜色(直接光照)*漫反射项

镜面反射:镜面反射项(V项和D项)*光照颜色(直接光照)*菲涅尔项(F项)

表面衰减:表面衰减系数*GI镜面反射(间接光照)*菲涅尔插值

gi的类型为UnityIndirect结构体,解释见下文。

 1  half specularTerm = V*D * UNITY_PI; // Torrance-Sparrow model, Fresnel is applied later
 2 
 3 #   ifdef UNITY_COLORSPACE_GAMMA
 4         specularTerm = sqrt(max(1e-4h, specularTerm));
 5 #   endif
 6 
 7     // specularTerm * nl can be NaN on Metal in some cases, use max() to make sure it's a sane value
 8     specularTerm = max(0, specularTerm * nl);
 9 #if defined(_SPECULARHIGHLIGHTS_OFF)
10     specularTerm = 0.0;
11 #endif
12 
13     // surfaceReduction = Int D(NdotH) * NdotH * Id(NdotL>0) dH = 1/(roughness^2+1)
14     half surfaceReduction;
15 #   ifdef UNITY_COLORSPACE_GAMMA
16         surfaceReduction = 1.0-0.28*roughness*perceptualRoughness;      // 1-0.28*x^3 as approximation for (1/(x^4+1))^(1/2.2) on the domain [0;1]
17 #   else
18         surfaceReduction = 1.0 / (roughness*roughness + 1.0);           // fade in [0.5;1]
19 #   endif
20 
21     // To provide true Lambert lighting, we need to be able to kill specular completely.
22     specularTerm *= any(specColor) ? 1.0 : 0.0;
23 
24     half grazingTerm = saturate(smoothness + (1-oneMinusReflectivity));
25     half3 color =   diffColor * (gi.diffuse + light.color * diffuseTerm)
26                     + specularTerm * light.color * FresnelTerm (specColor, lh)
27                     + surfaceReduction * gi.specular * FresnelLerp (specColor, grazingTerm, nv);
28 
29     return half4(color, 1);
30 }
Part-3

FresnelLerp函数

返回F0到F90之间的线性插值,t的实现和菲涅尔项中的实现一致。

1 inline half3 FresnelLerp (half3 F0, half3 F90, half cosA)
2 {
3     half t = Pow5 (1 - cosA);   // ala Schlick interpoliation
4     return lerp (F0, F90, t);
5 }
FresnelLerp

计算oneMinusReflectivity的函数是EnergyConservationBetweenDiffuseAndSpecular,位于UnityStandardUtils.cginc。余下代码用在他处,暂不讨论。

 1 inline half3 EnergyConservationBetweenDiffuseAndSpecular (half3 albedo, half3 specColor, out half oneMinusReflectivity)
 2 {
 3     oneMinusReflectivity = 1 - SpecularStrength(specColor);
 4     #if !UNITY_CONSERVE_ENERGY
 5         return albedo;
 6     #elif UNITY_CONSERVE_ENERGY_MONOCHROME
 7         return albedo * oneMinusReflectivity;
 8     #else
 9         return albedo * (half3(1,1,1) - specColor);
10     #endif
11 }
EnergyConservationBetweenDiffuseAndSpecular

SpecularStrength函数

如果Shader Mode<3.0,返回镜面反射颜色的R通道。

否则,返回镜面反射颜色RGB三个通道中最大的通道值。

Note:Shader Mode 2.0由于指令数的限制,简化了这项运算,直接返回R通道值(因为大多数的金属要么是单色,要么是淡黄色/黄色调的,主要影响的是R通道)。

 1 half SpecularStrength(half3 specular)
 2 {
 3     #if (SHADER_TARGET < 30)
 4         // SM2.0: instruction count limitation
 5         // SM2.0: simplified SpecularStrength
 6         return specular.r; // Red channel - because most metals are either monocrhome or with redish/yellowish tint
 7     #else
 8         return max (max (specular.r, specular.g), specular.b);
 9     #endif
10 }
SpecularStrength
原文地址:https://www.cnblogs.com/jaffhan/p/7358418.html