当前位置:   article > 正文

入门图形学:光照模型(五)_unity_light_function_apply_indirect

unity_light_function_apply_indirect

        具体做法就是添加一个预编译指令:

        #pragma surface surf Standard    就可以为我们的surf函数指定Standard光照模型了

        这次我们就来替换这个标准光照模型函数,替换成我们自己去实现的函数,比如这样:

        #pragma surface surf YangLightModel  意思就是说给我们的surf函数指定一个名为YangLightModel的光照模型。

        在开始写这个shader之前,首先让我们观察Lighting.cginc和UnityPBSLighting.cginc中的代码:

        ps:我这里我就不全部贴过来了,只拷一些具有代表性的,如下:

        


 
 
  1. struct SurfaceOutput {
  2. fixed3 Albedo;
  3. fixed3 Normal;
  4. fixed3 Emission;
  5. half Specular;
  6. fixed Gloss;
  7. fixed Alpha;
  8. };
  • 1

        这里我来说明一下,首先就是这个SurfaceOutput的结构体,这个结构体定义了光照模型函数所需要的参数数据 (比如反射率、法线、放射等参数) , 然后就是UnityLight和UnityGI,分别如下(请打开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. };
  7. struct UnityIndirect
  8. {
  9. half3 diffuse;
  10. half3 specular;
  11. };
  12. struct UnityGI
  13. {
  14. UnityLight light;
  15. UnityIndirect indirect;
  16. };
  • 1

        一目了然,UnityLight中包含光线颜色,朝向和ndotl计算好的数值,ndotl的意义我在光照模型(二)讲到了,是计算diffuse所需要的参数, 不过unity并不储存这个值了,得由我们自己去计算,UnityGI包含了一个Light结构体和一个Indirect结构体,Indirect中包含了漫反射和镜面反射颜色。     

        接下来就是这些结构体在光照模型函数中的使用了,如下:


 
 
  1. inline fixed4 UnityLambertLight (SurfaceOutput s, UnityLight light)
  2. {
  3. fixed diff = max ( 0, dot (s.Normal, light.dir));
  4. fixed4 c;
  5. c.rgb = s.Albedo * light.color * diff;
  6. c.a = s.Alpha;
  7. return c;
  8. }
  9. inline fixed4 LightingLambert (SurfaceOutput s, UnityGI gi)
  10. {
  11. fixed4 c;
  12. c = UnityLambertLight (s, gi.light);
  13. #ifdef UNITY_LIGHT_FUNCTION_APPLY_INDIRECT
  14. c.rgb += s.Albedo * gi.indirect.diffuse;
  15. #endif
  16. return c;
  17. }
  • 1

        这里展示的是Unity自带的Lambert光照模型,Lambert光照模型接收到来自Unity光照系统计算出的SurfaceOutput和UnityGI结构数据,然后算出基本的diffuse颜色,再加上GI自带的diffuse颜色,然后将最终的颜色rgb值返回给使用这个光照模型的surf函数,比如如下:

         #pragma surface surf Lambert   也就是说使用Lambert光照模型的surf表面着色函数

         接下来我们需要做的就是按照这个LightingLambert光照模型函数来仿写我们自己的光照模型函数,如下:

  


 
 
  1. Shader "Custom/YangSurfaceShader" {
  2. Properties {
  3. _MainTex ( "Texture", 2D) = "white" {}
  4. _Color ( "Color", Color) = ( 1, 1, 1, 1)
  5. _Specular( "Specular",Range( 1, 10)) = 5
  6. }
  7. SubShader{
  8. Tags { "RenderType"= "Opaque" }
  9. LOD 200
  10. CGPROGRAM
  11. #pragma surface surf YangLightModel
  12. #include "UnityPBSLighting.cginc"
  13. #include "UnityCG.cginc"
  14. #include "Lighting.cginc"
  15. #pragma target 3.0
  16. sampler2D _MainTex;
  17. float4 _Color;
  18. half _Specular;
  19. struct Input {
  20. float2 uv_MainTex;
  21. };
  22. void surf (Input IN, inout SurfaceOutput o) {
  23. fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
  24. o.Albedo = c.rgb;
  25. o.Alpha = c.a;
  26. o.Specular = _Specular;
  27. }
  28. inline float4 LightingYangLightModel(SurfaceOutput s, fixed3 lightDir, fixed3 viewDir, float atten)
  29. {
  30. /*首先计算当前环境光*/
  31. float3 unity_buildin_ambient_light_color = UNITY_LIGHTMODEL_AMBIENT.xyz;
  32. /*然后计算当前场景light的颜色值rgb*/
  33. float3 unity_buildin_light_color = _LightColor0.rgb;
  34. /*计算漫反射diffuse颜色*/
  35. float3 diffuse = s.Albedo * unity_buildin_light_color * max(dot(s.Normal,lightDir), 0);
  36. /*计算光源方向和视线方向的中和向量的单位向量H*/
  37. float3 h_dir_or_call_VPlusL_dir = normalize(lightDir + viewDir);
  38. /*计算反射specular颜色*/
  39. float3 specular = unity_buildin_light_color * pow(max(dot(s.Normal,h_dir_or_call_VPlusL_dir), 0),s.Specular);
  40. /*定义一个颜色合并所有的颜色并返回给使用这个光照模型的surf函数*/
  41. float4 col;
  42. col.xyz = diffuse + unity_buildin_ambient_light_color + specular;
  43. col.w = s.Alpha;
  44. return col;
  45. }
  46. ENDCG
  47. }
  48. FallBack "Diffuse"
  49. }
  • 1

         这里我要讲一下这个CG shader具体的含义,如下:

         一.Properties字段

              相信_MainTex,_Color,_Specular大家应该能一眼看懂,无非就是主纹理贴图,放射颜色值和镜面反射高亮幂参数。

         二.surf函数以及Input结构体

              从这里开始就让人迷惑了,void surf (Input IN, inout SurfaceOutput o),这个函数后面的SurfaceOutput参数我们聊过,无非就是让我们自己填充SurfaceOutput这个结构数据,然后提供给光照模型去使用。

              那么前面一个Input结构体是什么意思呢?这个其实是Unity SurfaceShader编译surf函数时必须的一个形参结构体,目的就是为了规范化封装储存surf函数计算需要的数据,比如uv_MainTex就储存了主纹理_MainTex采样顶点uv,传递给surf使用(这里要特别提醒的是,unity的shader编译器,已经将Input.uv_MainTex根据字符串匹配绑定为主纹理采样顶点uv的字段,这个名称是不能变的,修改该字符串将得不到编译绑定效果)当然我们可以定义多个变量去储存更多内容,比如如下:

                


 
 
  1. struct Input
  2. {
  3. float2 uv_MainTex;
  4. float2 uv_BumpMap;
  5. float3 viewDir;
  6. };
  • 1

              我们把主纹理,法线贴图,视线方向向量绑定封装进Input,然后给surf函数使用,surf函数利用Input结构数据来计算光照模型需要的数据,比如如下:

fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
 
 
  • 1

               上面代码的意义就是说根据uv_MainTex去计算主纹理_MainTex的采样数据(tex2D为纹理采样函数,cg语法篇章我会讲解),然后将采样后的颜色乘上我们定义的主颜色值,得到最终主颜色值。

           三.inline float4 LightingYangLightModel(SurfaceOutput s, fixed3 lightDir, fixed3 viewDir, float atten)

               首先,这个光照函数为什么要定义成这样呢?

              ①.首先这个光照函数前面要加上Lighting前缀,这个是Unity shader编译器的规范,为了识别这是一个光照函数,才能进行后续的处理。

               ②.(SurfaceOutput s, fixed3 lightDir, fixed3 viewDir, float atten)形参列表,除了SurfaceOutput这个我们知道外,其他的形参都是什么意思呢?虽然从形参名称我们能够知道lightDir就是光源方向向量,viewDir就是视线方向向量,atten代表某个衰减值。但是实际上是不是能起到如形参名一样的效果呢?比如lightDir和viewDir就是计算diffuse和specular颜色的关键参数(光照模型二和三中是我们自己计算的,这里直接提供的话就方便很多),我们直接在LightingYangLightModel中使用lightDir和viewDir去计算diffuse和specular,可以看到效果果然如同自己亲自计算这两个向量能达到的效果。也就是说这一系列形参列表的实际传递参数确实如其所命名。

               这个到底是为什么呢?还是以标准光照模型为例,如下:

                


 
 
  1. inline half4 LightingStandard (SurfaceOutputStandard s, half3 viewDir, UnityGI gi)
  2. {
  3. s.Normal = normalize(s.Normal);
  4. half oneMinusReflectivity;
  5. half3 specColor;
  6. s.Albedo = DiffuseAndSpecularFromMetallic (s.Albedo, s.Metallic, /*out*/ specColor, /*out*/ oneMinusReflectivity);
  7. // shader relies on pre-multiply alpha-blend (_SrcBlend = One, _DstBlend = OneMinusSrcAlpha)
  8. // this is necessary to handle transparency in physically correct way - only diffuse component gets affected by alpha
  9. half outputAlpha;
  10. s.Albedo = PreMultiplyAlpha (s.Albedo, s.Alpha, oneMinusReflectivity, /*out*/ outputAlpha);
  11. half4 c = UNITY_BRDF_PBS (s.Albedo, specColor, oneMinusReflectivity, s.Smoothness, s.Normal, viewDir, gi.light, gi.indirect);
  12. c.a = outputAlpha;
  13. return c;
  14. }
  • 1

        同时我们来打开UnityPBSLighting.cginc和Lighting.cginc这两个文件,可以看到很多各种光照模型的函数定义和形参列表,实际上我们定义的LightingYangLightModel无非就是其中一个重载函数而已,Unity Shader编译器跟我们的重载函数,编译调用时传递相应的数据。

        最后LightingYangLightModel中那些计算公式的意义,在光照模型二和三中都有相当详细的讲解,我就不赘述了,只做了简单的注释。

        接下来我们看下这个cg shader的具体表现效果,如下图:

        以上,就是自定义Surface着色器的通用CG shader写法,cg的语法我会额外开一个分类版块进行详细讲解学习。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小蓝xlanll/article/detail/107845
推荐阅读
相关标签
  

闽ICP备14008679号