转自:http://blog.csdn.net/candycat1992/article/details/39994049

写在前面

一直以来,Unity Surface Shader背后的机制一直是初学者为之困惑的地方。Unity Surface Shader在Unity 3.0的时候被开放给公众使用,其宣传手段也是号称让所有人都可以轻松地写shader。但由于资料缺乏,很多人知其然不知其所以然,无法理解Unity Surface Shader在背后为我们做了哪些事情。

前几天一直被问到一个问题,为什么我的场景里没有灯光,但物体不是全黑的呢?为什么我把Light的颜色调成黑色,物体还是有一些默认颜色呢?这些问题其实都是因为那些物体使用了Surface Shader的缘故。因此,了解Surface Shader背后的机制是非常重要滴~

虽然Surface Shader一直是一个神秘的存在,但其实Unity给了我们揭开她面纱的方式:查看它生成的CG代码。大家应该都知道,所谓的Surface Shader实际上是封装了CG语言,隐藏了很多光照处理的细节,它的设计初衷是为了让用户仅仅使用一些指令(#pragma)就可以完成很多事情,并且封装了很多常用的光照模型和函数,例如Lambert、Blinn-Phong等。而查看Surface Shader生成的代码也很简单:在每个编译完成的Surface Shader的面板上,都有个“Show generated code”的按钮,像下面这样:

点开后,就可以查看啦~面板上还表明了很多其他的有用信息。而这些方便的功能实际上是Unity 4.5发布出来的。详情可见这篇博文

使用Surface Shader,很多时候,我们只需要告诉shader,“嘿,使用这些纹理去填充颜色,法线贴图去填充法线,使用Lambert光照模型,其他的不要来烦我!!!”我们不需要考虑是使用forward还是deferred rendering,有多少光源类型、怎样处理这些类型,每个pass需要处理多少个光源!!!(人们总会rant写一个shader是多么的麻烦。。。)So!Unity说,不要急,放着我来~

上面的情景当然对于小白是比较简单的方式,Surface Shader可以让初学者快速实现很多常见的shader,例如漫反射、高光反射、法线贴图等,这些常见的效果也都不错。而对应面就是,由于隐藏了很多细节,如果想要自定义一些比较复杂或特殊的效果,使用Surface Shader就无法达到了(或者非常麻烦)。在学了一段时间的Surface Shader后,我认为:

  • 如果你从来没有学习过怎样编写shader,而又想写一些常见的、比较简单的shader,那仅学习Surface Shader是一个不错的选择。
  • 如果你向往那些高品质的游戏画面,那么Surface Shader是远远无法满足你的,而且某种方面来说它会让你变得越来越困惑。
 
困惑了怎么办呢?老老实实去学习主流的渲染语言吧~比如CG、GLSL、HLSL等。等学了一些上述内容后,再回过头来看Surface Shader就会别有一番理解了。
 
说教了这么多,本篇的主旨其实是分析下Surface Shader背后做的事情啦!也就是,分析Surface Shader到底是怎样解析我们编写的那些surf、LightingXXX等函数的,又是如何得到像素颜色的。那么,开始吧!
 
 
 

流水线

 
首先,我们要明白Surface Shader支持哪些特性。详情请见官网
 
Surface Shader最重要的部分是两个结构体以及它的编译指令。
 
 

两个结构体

 
两个结构体就是指struct Input和SurfaceOutput。其中Input结构体是允许我们自定义的。它可以包含一些纹理坐标和其他提前定义的变量,例如view direction(float3 viewDir)、world space position(worldPos)、world space reflection vector(float3 worldRefl)等。这些变量只有在真正使用的时候才会被计算生成。比如,在某些Pass里生成而某些就生成。
 
另一个结构体是SurfaceOutput。我们无法自定义这个结构体内的变量。关于它最难理解的也就是每个变量的具体含义以及工作机制(对像素颜色的影响)。我们来看一下它的定义(在Lighting.cginc里面):
  1. struct SurfaceOutput {
  2. half3 Albedo;
  3. half3 Normal;
  4. half3 Emission;
  5. half Specular;
  6. half Gloss;
  7. half Alpha;
  8. };
  • Albedo:我们通常理解的对光源的反射率。它是通过在Fragment Shader中计算颜色叠加时,和一些变量(如vertex lights)相乘后,叠加到最后的颜色上的。
  • Normal:即其对应的法线方向。只要是受法线影响的计算都会受到影响。
  • Emission:自发光。会在Fragment 最后输出前(调用final函数前,如果定义了的话),使用下面的语句进行简单的颜色叠加:
    1. c.rgb += o.Emission;
  • Specular:高光反射中的指数部分的系数。影响一些高光反射的计算。按目前的理解,也就是在光照模型里会使用到(如果你没有在光照函数等函数——包括Unity内置的光照函数,中使用它,这个变量就算设置了也没用)。有时候,你只在surf函数里设置了它,但也会影响最后的结果。这是因为,你可能使用了Unity内置的光照模型,如BlinnPhong,它会使用如下语句计算高光反射的强度(在Lighting.cginc里):
    1. float spec = pow (nh, s.Specular*128.0) * s.Gloss;
  • Gloss:高光反射中的强度系数。和上面的Specular类似,一般在光照模型里使用。
  • Alpha:通常理解的透明通道。在Fragment Shader中会直接使用下列方式赋值(如果开启了透明通道的话):
    1. c.a = o.Alpha;
上述结论是分析生成的代码所得,若有不对欢迎指出。大家碰到不懂的,也可以像这样分析生成的代码,一般问题都可以理解啦~
 

编译指令

 
 
编译指令的一般格式如下:
  1. #pragma surface surfaceFunction lightModel [optionalparams]

Surface Shader和CG其他部分一样,代码也是要写在CGPROGRAM和ENDCG之间。但区别是,它必须写在SubShader内部,而不能写在Pass内部。Surface Shader自己会自动生成所需的各个Pass。由上面的编译格式可以看出,surfaceFunction和lightModel是必须指定的,而且是可选部分。

 
surfaceFunction通常就是名为surf的函数(函数名可以任意),它的函数格式是固定的:
  1. void surf (Input IN, inout SurfaceOutput o)

即Input是输入,SurfaceOutput是输出。

 
lightModel也是必须指定的。由于Unity内置了一些光照函数——Lambert(diffuse)和Blinn-Phong(specular),因此这里在默认情况下会使用内置的Lambert模型。当然我们也可以自定义。
 
optionalparams包含了很多可用的指令类型,包括开启、关闭一些状态,设置生成的Pass类型,指定可选函数等。这里,我们只关注可指定的函数,其他可去官网自行查看。除了上述的surfaceFuntion和lightModel,我们还可以自定义两种函数:vertex:VertexFunction和finalcolor:ColorFunction。也就是说,Surface Shader允许我们自定义四种函数。
 
两个结构体+四个函数——它们在整个的render pipeline中的流程如下:
 
从上图可以看出来,Surface Shader背后的”那些女人“就是vertex shader和fragment shader。除了VertexFunction外,另外两个结构体和三个函数都是在fragment shader中扮演了一些角色。Surface Shader首先根据我们的代码生成了很多Pass,用于forwardbase和forwardadd等,这不在本篇的讨论范围。而每个Pass的代码是基于上述四个函数生成的。
 
以一个Pass的代码为例,Surface Shader的生成过程简述如下:
  1. 直接将CGPROGRAM和ENDCG之间的代码复制过来(其实还是更改了一些编译指令),这些代码包括了我们对Input、surfaceFuntion、LightingXXX等变量和函数的定义。这些函数和变量会在之后的处理过程中当成普通的结构体和函数进行调用,就和在C++中我们会在main函数中调用某些函数一样;
  2. 分析上述代码,生成v2f_surf结构,用于在Vertex Shader和Fragment Shader之间进行数据传递。Unity会分析我们在四个自定义函数中所使用的变量,例如纹理坐标等。如果需要,它会在v2f_surf中生成相应的变量。而且,即便有时我们在Input中定义了某些变量(如某些纹理坐标),但Unity在分析后续代码时发现我们并没有使用这些变量,那么这些变量实际上是不会在v2f_surf中生成的。这也就是说,Unity做了一些优化动作。
  3. 生成Vertex Shader。
    * 如果我们自定义了VertexFunction,Unity会在这里首先调用VertexFunction修改顶点数据;然后分析VertexFunction修改的数据,最后通过Input结构体将修改结果存储到v2f_surf中。
    * 计算v2f_surf中其他默认的变量值。这主要包括了pos、纹理坐标、normal(如果没有使用LightMap)、vlight(如果没有使用LightMap)、lmap(如果使用LightMap)等。
    * 最后,通过内置的TRANSFER_VERTEX_TO_FRAGMENT指令将v2f_surf传递给下面的Fragment Shader。
  4. 生成Fragment Shader。
    * 使用v2f_surf中的对应变量填充Input结构,例如一些纹理坐标等。
    * 调用surfFuntion填充SurfaceOutput结构。
    * 调用LightingXXX函数得到初始的颜色值。
    * 进行其他的颜色叠加。如果没有启用LightMap,这里会使用SurfaceOutput.Albedo和v2f_surf.vlight的乘积和原颜色值进行叠加;否则会进行一些更复杂的颜色叠加。
    * 最后,如果自定了final函数,则调用它进行最后额颜色修改。
 

代码分析

 
我们以一个Surface Shader为例,分析它生成的代码。
 
Surface Shader如下:
  1. Shader "Custom/BasicDiffuse" {
  2. Properties {
  3. _EmissiveColor ("Emissive Color", Color) = (1,1,1,1)
  4. _AmbientColor  ("Ambient Color", Color) = (1,1,1,1)
  5. _MySliderValue ("This is a Slider", Range(0,10)) = 2.5
  6. _RampTex ("Ramp Texture", 2D) = "white"{}
  7. }
  8. SubShader {
  9. Tags { "RenderType"="Opaque" "RenderType"="Opaque" }
  10. LOD 200
  11. CGPROGRAM
  12. #pragma surface surf BasicDiffuse vertex:vert finalcolor:final noforwardadd
  13. #pragma debug
  14. float4 _EmissiveColor;
  15. float4 _AmbientColor;
  16. float _MySliderValue;
  17. sampler2D _RampTex;
  18. struct Input
  19. {
  20. float2 uv_RampTex;
  21. float4 vertColor;
  22. };
  23. void vert(inout appdata_full v, out Input o)
  24. {
  25. o.vertColor = v.color;
  26. }
  27. void surf (Input IN, inout SurfaceOutput o)
  28. {
  29. float4 c;
  30. c =  pow((_EmissiveColor + _AmbientColor), _MySliderValue);
  31. o.Albedo = c.rgb + tex2D(_RampTex, IN.uv_RampTex).rgb;
  32. o.Alpha = c.a;
  33. }
  34. inline float4 LightingBasicDiffuse (SurfaceOutput s, fixed3 lightDir, fixed atten)
  35. {
  36. float difLight = max(0, dot (s.Normal, lightDir));
  37. float hLambert = difLight * 0.5 + 0.5;
  38. float3 ramp = tex2D(_RampTex, float2(hLambert)).rgb;
  39. float4 col;
  40. col.rgb = s.Albedo * _LightColor0.rgb * (ramp) * atten;
  41. col.a = s.Alpha;
  42. return col;
  43. }
  44. void final(Input IN, SurfaceOutput o, inout fixed4 color) {
  45. color = color * 0.5 + 0.5;
  46. }
  47. ENDCG
  48. }
  49. FallBack "Diffuse"
  50. }

它包含了全部四个函数,以及一些比较常见的运算。为了只关注一个Pass,我添加了noforwardadd指令。它所得到的渲染结果不重要(事实上我只是在BasicDiffuse上瞎改了一些。。。)

 
我们点开查看它生成的代码:
  1. Shader "Custom/BasicDiffuse_Gen" {
  2. Properties {
  3. _EmissiveColor ("Emissive Color", Color) = (1,1,1,1)
  4. _AmbientColor  ("Ambient Color", Color) = (1,1,1,1)
  5. _MySliderValue ("This is a Slider", Range(0,10)) = 2.5
  6. _RampTex ("Ramp Texture", 2D) = "white"{}
  7. }
  8. SubShader {
  9. Tags { "RenderType"="Opaque" "RenderType"="Opaque" }
  10. LOD 200
  11. // ------------------------------------------------------------
  12. // Surface shader code generated out of a CGPROGRAM block:
  13. // ---- forward rendering base pass:
  14. Pass {
  15. Name "FORWARD"
  16. Tags { "LightMode" = "ForwardBase" }
  17. CGPROGRAM
  18. // compile directives
  19. #pragma vertex vert_surf
  20. #pragma fragment frag_surf
  21. #pragma multi_compile_fwdbase nodirlightmap
  22. #include "HLSLSupport.cginc"
  23. #include "UnityShaderVariables.cginc"
  24. #define UNITY_PASS_FORWARDBASE
  25. #include "UnityCG.cginc"
  26. #include "Lighting.cginc"
  27. #include "AutoLight.cginc"
  28. #define INTERNAL_DATA
  29. #define WorldReflectionVector(data,normal) data.worldRefl
  30. #define WorldNormalVector(data,normal) normal
  31. // Original surface shader snippet:
  32. #line 11 ""
  33. #ifdef DUMMY_PREPROCESSOR_TO_WORK_AROUND_HLSL_COMPILER_LINE_HANDLING
  34. #endif
  35. //#pragma surface surf BasicDiffuse vertex:vert finalcolor:final noforwardadd
  36. #pragma debug
  37. float4 _EmissiveColor;
  38. float4 _AmbientColor;
  39. float _MySliderValue;
  40. sampler2D _RampTex;
  41. struct Input
  42. {
  43. float2 uv_RampTex;
  44. float4 vertColor;
  45. };
  46. void vert(inout appdata_full v, out Input o)
  47. {
  48. o.vertColor = v.color;
  49. }
  50. void surf (Input IN, inout SurfaceOutput o)
  51. {
  52. float4 c;
  53. c =  pow((_EmissiveColor + _AmbientColor), _MySliderValue);
  54. o.Albedo = c.rgb + tex2D(_RampTex, IN.uv_RampTex).rgb;
  55. o.Alpha = c.a;
  56. }
  57. inline float4 LightingBasicDiffuse (SurfaceOutput s, fixed3 lightDir, fixed atten)
  58. {
  59. float difLight = max(0, dot (s.Normal, lightDir));
  60. float hLambert = difLight * 0.5 + 0.5;
  61. float3 ramp = tex2D(_RampTex, float2(hLambert)).rgb;
  62. float4 col;
  63. col.rgb = s.Albedo * _LightColor0.rgb * (ramp);
  64. col.a = s.Alpha;
  65. return col;
  66. }
  67. void final(Input IN, SurfaceOutput o, inout fixed4 color) {
  68. color = color * 0.5 + 0.5;
  69. }
  70. // vertex-to-fragment interpolation data
  71. #ifdef LIGHTMAP_OFF
  72. struct v2f_surf {
  73. float4 pos : SV_POSITION;
  74. float2 pack0 : TEXCOORD0;
  75. float4 cust_vertColor : TEXCOORD1;
  76. fixed3 normal : TEXCOORD2;
  77. fixed3 vlight : TEXCOORD3;
  78. // LIGHTING_COORDS在AutoLight.cginc里定义
  79. // 本质上就是一个#define指令
  80. //  e.g.
  81. // #define LIGHTING_COORDS(idx1,idx2) float3 _LightCoord : TEXCOORD##idx1; SHADOW_COORDS(idx2)
  82. // #define SHADOW_COORDS(idx1) float3 _ShadowCoord : TEXCOORD##idx1;
  83. LIGHTING_COORDS(4,5)
  84. };
  85. #endif
  86. #ifndef LIGHTMAP_OFF
  87. struct v2f_surf {
  88. float4 pos : SV_POSITION;
  89. float2 pack0 : TEXCOORD0;
  90. float4 cust_vertColor : TEXCOORD1;
  91. float2 lmap : TEXCOORD2;
  92. LIGHTING_COORDS(3,4)
  93. };
  94. #endif
  95. #ifndef LIGHTMAP_OFF
  96. float4 unity_LightmapST;
  97. #endif
  98. //  定义所需的纹理坐标
  99. float4 _RampTex_ST;
  100. // vertex shader
  101. v2f_surf vert_surf (appdata_full v) {
  102. v2f_surf o;
  103. //  使用自定义的vert函数填充Input结构
  104. Input customInputData;
  105. vert (v, customInputData);
  106. //  再赋值给真正所需的v2f_surf结构
  107. o.cust_vertColor = customInputData.vertColor;
  108. o.pos = mul (UNITY_MATRIX_MVP, v.vertex);
  109. //  将顶点的纹理坐标转换到纹理对应坐标
  110. o.pack0.xy = TRANSFORM_TEX(v.texcoord, _RampTex);
  111. #ifndef LIGHTMAP_OFF
  112. //  如果启用了LightMap,则计算对应的LightMap坐标
  113. o.lmap.xy = v.texcoord1.xy * unity_LightmapST.xy + unity_LightmapST.zw;
  114. #endif
  115. //  计算世界坐标系中法线的方向
  116. //  SCALED_NORMAL在UnityCG.cginc里定义
  117. // 本质上就是一个#define指令
  118. // #define SCALED_NORMAL (v.normal * unity_Scale.w)
  119. float3 worldN = mul((float3x3)_Object2World, SCALED_NORMAL);
  120. //  如果没有开启LightMap,
  121. // 顶点法线方向就是worldN
  122. #ifdef LIGHTMAP_OFF
  123. o.normal = worldN;
  124. #endif
  125. // SH/ambient and vertex lights
  126. #ifdef LIGHTMAP_OFF
  127. //  如果没有开启LightMap,
  128. //  vertex lights就是球面调和函数的结果
  129. //  球面调和函数ShadeSH9在UnityCG.cginc里定义
  130. float3 shlight = ShadeSH9 (float4(worldN,1.0));
  131. o.vlight = shlight;
  132. // unity_4LightPosX0等变量在UnityShaderVariables.cginc里定义
  133. #ifdef VERTEXLIGHT_ON
  134. float3 worldPos = mul(_Object2World, v.vertex).xyz;
  135. o.vlight += Shade4PointLights (
  136. unity_4LightPosX0, unity_4LightPosY0, unity_4LightPosZ0,
  137. unity_LightColor[0].rgb, unity_LightColor[1].rgb, unity_LightColor[2].rgb, unity_LightColor[3].rgb,
  138. unity_4LightAtten0, worldPos, worldN );
  139. #endif // VERTEXLIGHT_ON
  140. #endif // LIGHTMAP_OFF
  141. // pass lighting information to pixel shader
  142. // TRANSFER_VERTEX_TO_FRAGMENT在AutoLight.cginc里定义,
  143. // 本质上就是一个#define指令
  144. // 用于转换v2f_surf中的_LightCoord和_ShadowCoord
  145. TRANSFER_VERTEX_TO_FRAGMENT(o);
  146. return o;
  147. }
  148. #ifndef LIGHTMAP_OFF
  149. sampler2D unity_Lightmap;
  150. #ifndef DIRLIGHTMAP_OFF
  151. sampler2D unity_LightmapInd;
  152. #endif
  153. #endif
  154. // fragment shader
  155. fixed4 frag_surf (v2f_surf IN) : SV_Target {
  156. // prepare and unpack data
  157. #ifdef UNITY_COMPILER_HLSL
  158. Input surfIN = (Input)0;
  159. #else
  160. Input surfIN;
  161. #endif
  162. //  使用v2f_surf中的变量给Input中的纹理坐标进行赋值
  163. surfIN.uv_RampTex = IN.pack0.xy;
  164. surfIN.vertColor = IN.cust_vertColor;
  165. #ifdef UNITY_COMPILER_HLSL
  166. SurfaceOutput o = (SurfaceOutput)0;
  167. #else
  168. SurfaceOutput o;
  169. #endif
  170. // 初始化SurfaceOutput结构
  171. o.Albedo = 0.0;
  172. o.Emission = 0.0;
  173. o.Specular = 0.0;
  174. o.Alpha = 0.0;
  175. o.Gloss = 0.0;
  176. #ifdef LIGHTMAP_OFF
  177. o.Normal = IN.normal;
  178. #endif
  179. // call surface function
  180. //  调用自定义的surf函数填充SurfaceOutput结构
  181. surf (surfIN, o);
  182. // compute lighting & shadowing factor
  183. // LIGHT_ATTENUATION在AutoLight.cginc里定义,
  184. // 本质上就是一个#define指令
  185. // 用于计算光衰减
  186. fixed atten = LIGHT_ATTENUATION(IN);
  187. fixed4 c = 0;
  188. // realtime lighting: call lighting function
  189. #ifdef LIGHTMAP_OFF
  190. // 如果没有开启LightMap,
  191. // 调用自定义的LightXXX函数,
  192. // 使用填充好的SurfaceOutput等变量作为参数,
  193. // 得到初始的像素值
  194. c = LightingBasicDiffuse (o, _WorldSpaceLightPos0.xyz, atten);
  195. #endif // LIGHTMAP_OFF || DIRLIGHTMAP_OFF
  196. #ifdef LIGHTMAP_OFF
  197. // 如果没有开启LightMap,
  198. // 向像素叠加vertex light的光照颜色
  199. c.rgb += o.Albedo * IN.vlight;
  200. #endif // LIGHTMAP_OFF
  201. // lightmaps:
  202. #ifndef LIGHTMAP_OFF
  203. // 计算LightMap,这部分不懂
  204. #ifndef DIRLIGHTMAP_OFF
  205. // directional lightmaps
  206. fixed4 lmtex = tex2D(unity_Lightmap, IN.lmap.xy);
  207. fixed4 lmIndTex = tex2D(unity_LightmapInd, IN.lmap.xy);
  208. half3 lm = LightingLambert_DirLightmap(o, lmtex, lmIndTex, 0).rgb;
  209. #else // !DIRLIGHTMAP_OFF
  210. // single lightmap
  211. fixed4 lmtex = tex2D(unity_Lightmap, IN.lmap.xy);
  212. fixed3 lm = DecodeLightmap (lmtex);
  213. #endif // !DIRLIGHTMAP_OFF
  214. // combine lightmaps with realtime shadows
  215. #ifdef SHADOWS_SCREEN
  216. #if (defined(SHADER_API_GLES) || defined(SHADER_API_GLES3)) && defined(SHADER_API_MOBILE)
  217. c.rgb += o.Albedo * min(lm, atten*2);
  218. #else
  219. c.rgb += o.Albedo * max(min(lm,(atten*2)*lmtex.rgb), lm*atten);
  220. #endif
  221. #else // SHADOWS_SCREEN
  222. c.rgb += o.Albedo * lm;
  223. #endif // SHADOWS_SCREEN
  224. // 给Alpha通道赋值
  225. c.a = o.Alpha;
  226. #endif // LIGHTMAP_OFF
  227. // 调用自定义的final函数,
  228. // 对像素值进行最后的更改
  229. final (surfIN, o, c);
  230. return c;
  231. }
  232. ENDCG
  233. }
  234. // ---- end of surface shader generated code
  235. #LINE 57
  236. }
  237. FallBack "Diffuse"
  238. }

其中比较重要的部分我都写了注释。

 
 

一些问题

 
回到我们一开始的那个问题:为什么我的场景里没有灯光,但物体不是全黑的呢?这一切都是Fragment Shader中一些颜色叠加计算的结果。
 
我们仔细观察Fragment Shader中计算颜色的部分。前面说过,它使用LightingXXX对颜色值进行初始化,但后面还进行了一系列颜色叠加计算。其中,在没有使用LightMap的情况下,Unity还计算了vertex lights对颜色的影响,也就是下面这句话:
  1. #ifdef LIGHTMAP_OFF
  2. // 如果没有开启LightMap,
  3. // 向像素叠加vertex light的光照颜色
  4. c.rgb += o.Albedo * IN.vlight;
  5. #endif // LIGHTMAP_OFF

而IN.vlight是在Vertex Shader中计算的:

  1. //  如果没有开启LightMap,
  2. //  vertex lights就是球面调和函数的结果
  3. //  球面调和函数ShadeSH9在UnityCG.cginc里定义
  4. float3 shlight = ShadeSH9 (float4(worldN,1.0));
  5. o.vlight = shlight;

我们可以去查看ShadeSH9函数的实现:

  1. // normal should be normalized, w=1.0
  2. half3 ShadeSH9 (half4 normal)
  3. {
  4. half3 x1, x2, x3;
  5. // Linear + constant polynomial terms
  6. x1.r = dot(unity_SHAr,normal);
  7. x1.g = dot(unity_SHAg,normal);
  8. x1.b = dot(unity_SHAb,normal);
  9. // 4 of the quadratic polynomials
  10. half4 vB = normal.xyzz * normal.yzzx;
  11. x2.r = dot(unity_SHBr,vB);
  12. x2.g = dot(unity_SHBg,vB);
  13. x2.b = dot(unity_SHBb,vB);
  14. // Final quadratic polynomial
  15. float vC = normal.x*normal.x - normal.y*normal.y;
  16. x3 = unity_SHC.rgb * vC;
  17. return x1 + x2 + x3;
  18. }

它是一个球面调和函数,但unity_SHAr这些变量具体是什么我还不清楚。。。如果有人知道麻烦告诉我一下,不胜感激~但是,这些变量是和Unity使用了一个全局环境光(你可以在Edit->RenderSettings->Ambient Light中调整)有关。如果把这个环境光也调成黑色,那么场景就真的全黑了。

Unity Shader (三)Surface Shader机制的更多相关文章

  1. Unity Shader——Writing Surface Shaders(0)

    从今天起,开始翻译Unity关于shader的官方文档.翻译水平比较一般,目的主要是通过翻译来提升对shader的见解,也让其他人更容易的了解shader.以下开始正文内容: 编写Surface Sh ...

  2. Surface Shader

    Surface Shader: (1)必须放在SubShdader块,不能放在Pass内部: (2)#pragma sufrace surfaceFunction lightModel [option ...

  3. Surface Shader简单向导

    Surface Shader 表面着色器 描述 当你的Material要处理光照,则一般使用Surface Shader.Surface Shader隐藏了光照的计算,你只需要在surf函数里设置好反 ...

  4. surface shader相关参数,命令

    https://docs.unity3d.com/Manual/SL-SurfaceShaders.html 说明: 注意下surfaceshader相关开关选项,input结构体全部可用参数 goo ...

  5. 【Unity Shaders】初探Surface Shader背后的机制

    转载请注明出处:http://blog.csdn.net/candycat1992/article/details/39994049 写在前面 一直以来,Unity Surface Shader背后的 ...

  6. 【淡墨Unity3D Shader计划】五 圣诞用品: Unity在Shader三种形式的控制&混合操作编译

    本系列文章由@浅墨_毛星云 出品,转载请注明出处. 文章链接:http://blog.csdn.net/poem_qianmo/article/details/42060963 作者:毛星云(浅墨)  ...

  7. Unity Shader——Writing Surface Shaders(3)——Surface Shader Lighting Examples

    Surface Shader 光照例子 这里有一些自定义光照模型和Surface Shaders的例子.通常的Surface Shader例子在这里. 由于延迟光照在某些自定义的逐材质光照模型中表现得 ...

  8. Unity Shader——Writing Surface Shaders(2)——Custom Lighting models in Surface Shaders

    Surface Shader中的自定义光照模型 当你在编写 Surface Shaders 时,是在描述一个表面的属性(反射颜色.法线……),而且光的交互过程是由一个光照模型来计算的.内建的光照模型有 ...

  9. 【Unity Shaders】Shader学习资源和Surface Shader概述

    写在前面 写这篇文章的时候,我断断续续学习Unity Shader半年了,其实还是个门外汉.我也能体会很多童鞋那种想要学好Shader却无从下手的感觉.在这个期间,我找到一些学习Shader的教程以及 ...

随机推荐

  1. (转载)7个去伪存真的JavaScript面试题

    7个去伪存真的JavaScript面试题 上周,我发表了<C#程序员的7个面试问题>.这次我要说的是如何淘汰那些滥竽充数的JavaScript程序员. 作者:小峰来源:码农网|2015-0 ...

  2. css part 2

    CSS 盒子模型 margin:            用于控制元素与元素之间的距离:margin的最基本用途就是控制元素周围空间的间隔,从视觉角度上达到相互隔开的目的. padding:       ...

  3. redis 篇 - 键 and string

    redis 进入控制台 redis-cil 需要输入密码的时候可以是用 -a redis-cil -a abcd1234 redis 数据类型 string hash list set zset( 有 ...

  4. Node_进阶_3

    Express框架: 一.   Express框架 Express框架是后台的Node框架,类似于JS中的jquery. #原生Node开发会有很多问题: 1呈递静态页面很不方便,需要处理每个HTTP ...

  5. [CTSC2016]单调上升路径

    题目:UOJ#201. 题目大意:给定n个点(n是偶数)的完全图,现在要你给每条边确定一个权值(互不相等),使得最长的单调上升路径最短.现在要你输出边的权值. 一条路径被称为单调上升的,如果沿着它走时 ...

  6. [codevs3657]括号序列

    题目大意:有一列只有'(',')','[',']'构成的括号序列,求在序列中至少加上多少括号,能使该序列合法. 解题思路:区间dp. 我们以$f[i][j]$表示把区间$[i,j]$添成合法括号所需的 ...

  7. [codevs1048]石子归并&[codevs2102][洛谷P1880]石子归并加强版

    codevs1048: 题目大意:有n堆石子排成一列,每次可合并相邻两堆,代价为两堆的重量之和,求把他们合并成一堆的最小代价. 解题思路:经典区间dp.设$f[i][j]$表示合并i~j的石子需要的最 ...

  8. 【转载】02-PowerDesigner的下载及安装

    原创路径:https://blog.csdn.net/ruyu00/article/details/79842807 一.下载 下载路径:https://pan.baidu.com/s/1WD7QHT ...

  9. 使用Jmeter工具对http接口进行压力测试

    1.访问apache官网下载Jmeter工具 地址:https://jmeter.apache.org/download_jmeter.cgi 2.解压压缩包后运行bin目录下jmeter.bat启动 ...

  10. WIZnet相关产品介绍

    WIZnet  自1998年在韩国创立以来,一致专注研发全硬件TCP/IP协议栈芯片.同一时候开发设计相关网络模块和无线产品,同一时候 WIZnet 鼓舞开源硬件.相关开源硬件产品也已层出不断. 主要 ...