zoukankan      html  css  js  c++  java
  • 【Unity Shader】(七) ------ 复杂的光照(下)

    笔者使用的是 Unity 2018.2.0f2 + VS2017,建议读者使用与 Unity 2018 相近的版本,避免一些因为版本不一致而出现的问题。

                 Unity Shader】(三)------ 光照模型原理及漫反射和高光反射的实现
            【Unity Shader】(四)------ 纹理之法线纹理、单张纹理和遮罩纹理的实现
                 【Unity Shader】(五) ------ 透明效果之半透明效果的原理及实现
                        【Unity Shader】(六)------ 复杂的光照(上)

    目录

    前言

    一. 光照衰减

    1.1 使用 LUT

    1.2 关于光照衰减纹理

    1.3 关于光照衰减的总结

    二. 阴影

    2.1 阴影是如何实现出来的

    Algorithm overview

    2.2  普通非透明物体阴影的实现

    2.2.1 准备工作

    2.2.2 接收阴影

    2.2.3 完善的的光照衰减和阴影管理

    2.3 普通透明物体的阴影

    三. 完整的光照 shader

    四. 总结

     

     

    前言

    本文承接上文【Unity Shader】(六) ------ 复杂的光照(上),介绍剩下的光照衰减和阴影部分,最后实现包含了对不同光照类型进行光照计算,光照衰减,阴影产生等部分的真正意义上的标准光照 shader 。因为本文会上文有所联系,所以个人建议读者阅读上文,以免在本文某些地方出现思路上的突兀。

    一. 光照衰减

    1.1 使用 LUT

    前面说过,我们使用 LUT 来计算衰减,这种做法的优劣点如下:

    • 优点:因为直接计算光照衰减会涉及大量且复杂的数学运算,使用 LUT 可以不依赖数学表达式的复杂性,只需一个参数去采样即可。
    • 缺点 : ① 需要预处理得到纹理,纹理大小影响衰减的精度。② 不直观,且使用 LUT 后就无法使用其它数学公式来计算。

    当然,Unity 默认这种方法也是因为其在一定程度上提升了性能且大部分情况下,得到的效果是良好的。

    1.2 关于光照衰减纹理

    Unity 内使用 _LightTexture0 的纹理来计算光照衰减,在之前的代码中,我们已经使用过了。通常情况下,我们只关心 _LightTexture0 对角线上的纹理颜色值,其代表了在光源空间下不同位置的点的衰减值。(0,0)表示与光源重合的点的衰减值,(1,1)表示距离最远的点的光照衰减值。

    上面说过,需要用一个点对纹理采样,那么就要先知道该点在光源空间下位置信息。同样是空间转换,我们在这里需要用到的转换矩阵为 _LightMatrix0 。在 Unity 5.4 之后,这个矩阵更换为 unity_WorldToLight 了。

    所以这里转换语句应该为

     1 float3 lightCoord = mul(unity_WorldToLight, float4(i.worldPos, 1)).xyz; 

     然后使用这个坐标的摸的平方进行采样。当然,如果用距离值来计算就需要开方操作了,为了,避免这个繁琐的步骤,我们使用顶点距离的平方来采样

     1 fixed atten = tex2D(_LightTexture0, dot(lightCoord, lightCoord).rr).UNITY_ATTEN_CHANNEL; 

     其中宏 UNITY_ATTEN_CHANNEL 可以得到衰减值所在的分量。

    1.3 关于光照衰减的总结

    上述所说的知识足够读者应付大部分的光照计算中的光照衰减部分,如果读者着实不希望采用 LUT 的方法来计算衰减,也可以使用数学公式,只是这样需要对公式有更深入的理解。很遗憾的是,笔者并没有找到关于计算衰减的公式的资料,对于衰减方面的资料,着实所寻不多,日后如果我能找到相关知识,我会补充到这篇文章中。

    二. 阴影

    在许多游戏制作中,为了追求真实,光影效果是必不可少的。光我们之前介绍了,现在来介绍阴影。

    2.1 阴影是如何实现出来的

    想象一下,一条光线从远方射过来,当它遇到了第一个不透明的物体时,那么理所当然的是,这条射线就无法再去照亮别的物体了。同时,挡住这条光线的物体会向附近的物体投射阴影。也就是说,阴影是光线无法到达的区域

    在 Unity 的实时渲染中,我们采用的是 Shadow Map 技术。关于 Shadow Map,我们可以 WIKI 上看到它的解释。

     

    大意为:Shadow Map 是 Lance Williams 先生在 1978 年提出的技术,从此之后,它常用于预渲染和实时场景中。在 Unity 中,就是先把摄像机的位置与光源重合,然后摄像机看不到的区域就是阴影。这样理解是不是很简单?

    我们先来看看 Shadow Map 是如何定义其工作原理的:

    Algorithm overview

    Rendering a shadowed scene involves two major drawing steps. The first produces the shadow map itself, and the second applies it to the scene. Depending on the implementation (and number of lights), this may require two or more drawing passes.

    简单地说就是 : ① 生成阴影纹理  ② 在场景中使用阴影纹理

    比如:在前向渲染中,如果平行光开启了阴影(要注意需要手动开启,创建了一个新光源,默认是没有阴影的)

    Unity 就会为这个平行光计算阴影映射纹理。这张阴影映射纹理实质就是一张深度纹理,记录着从光源出发,距离光源最近的表面信息

    通常情况下,是通过调用 Base Pass 和 Additional Pass 来更新深度信息,但我们之前也说过,这两个 Pass 中包含了各种光照计算。为了避免多余的光照计算所造成的性能损耗,Unity 选择使用另外一个特别的 Pass 来管理光源的映射纹理。这个 Pass 就是 LightMode 标签中设置为 Shadow Caster 的那个 Pass。这个 Pass 的渲染目标是深度纹理。所以当一个光源开启了阴影效果之后,引擎就会在当前渲染物体的 shader 中寻找这个 Pass ,如果找不到,就去 Fallback 里面找;还找不到,就去 Fallback 的 Fallback 里面找。如果这样都找不到,那么该物体就无法向其它物体投射阴影,但是可以接收来自其它物体的阴影。

     

    文字有点多,总结一下:

    • 如果想要一个物体接收其它的物体的阴影,就要在 shader 中对阴影映射纹理进行采样,把采样结果和光照结果相乘得到阴影效果。
    • 如果想要一个物体向其它物体投射阴影,就要把该物体加入到阴影映射纹理之中,这一步骤是在 Shadow Pass 中实现的。
    • 如果想要一个光源产生阴影效果,则需要手动选择阴影类型:No Shadows , Hard Shadows , Soft Shadows。Hard Shadows 相对于 Soft Shadows 计算量少一些,能满足大部分场景,边缘不平滑,锯齿明显。
       

    2.2  普通非透明物体阴影的实现

    这一节我们来实现对一个不透明的物体的阴影处理,包括让它投射阴影和接收阴影。

    2.2.1 准备工作

    创建场景,去掉默认的天空盒子;新建一个 Material 和 一个 shader,命名为 Shadow;创建一个 Cube 和两个 plane,位置摆放如下;开启平行光的阴影;新建的 shader 中使用我上一篇最后给出的前向渲染的代码。

     

    看到上面的图,不知道读者有没有一种细思极恐的感觉,因为上图有两处诡异的地方。

    • 前文说过,需要一个 ShadowCaster 的 Pass 来处理阴影,但是在上一篇中实现的前向渲染的代码中,我们并没有定义这样的一个 Pass,也没有做出对阴影处理的操作,那么为什么正方体会有阴影呢?
    • 可以确定的是两个 plane 都开启了投射阴影和接收阴影,图中就可以看到地面上的 plane 接收了正方体的阴影,那么,为什么右边的 plane 没有投影呢?

    其实这是两个需要注意的地方

    • 前文说过,当 shader 中没有 ShadowCaster 的 Pass 时会去它的 Fallback 里面找,我们之前的 Fallback 为 Specular,Specular 中也没有这个 Pass,最后在某个角落找到了它。想看源码的读者,可以在 Unity 官方下载 内置着色器 ,解压之后,在 DefaultResourcesExtra 文件夹中的 Normal-VertexLit 这个 shader 中找到以下代码。
       1 // Pass to render object as a shadow caster
       2     Pass 
       3     {
       4         Name "ShadowCaster"
       5         Tags { "LightMode" = "ShadowCaster" }
       6 
       7         CGPROGRAM
       8         #pragma vertex vert
       9         #pragma fragment frag
      10         #pragma target 2.0
      11         #pragma multi_compile_shadowcaster
      12         #pragma multi_compile_instancing // allow instanced shadow pass for most of the shaders
      13         #include "UnityCG.cginc"
      14 
      15         struct v2f {
      16             V2F_SHADOW_CASTER;
      17             UNITY_VERTEX_OUTPUT_STEREO
      18         };
      19 
      20         v2f vert( appdata_base v )
      21         {
      22             v2f o;
      23             UNITY_SETUP_INSTANCE_ID(v);
      24             UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o);
      25             TRANSFER_SHADOW_CASTER_NORMALOFFSET(o)
      26             return o;
      27         }
      28 
      29         float4 frag( v2f i ) : SV_Target
      30         {
      31             SHADOW_CASTER_FRAGMENT(i)
      32         }
      33         ENDCG
      34 
      35    }

       所以事实上,我们之前实现的 ForwardRendering 也是可以产生阴影效果的。

    • 右边的 plane 确定是打开了 CasterShadow 的,而且 shader 为 Unity 内置标准shader。而它却无法投射阴影是因为在默认情况下,在计算光源的阴影映射纹理时剔除物体背面。而内置的平面其实只有一个面。不过我们可以设置为 Two Sided,就可以令物体的所有面都计算光照信息了。

    这样就看到阴影了。不过还是有一个奇怪的地方,那就是正方体为什么没有接收右边平面的阴影?因为 ForwardRendering 里面没有对接收的阴影做出处理。我们接下来就要完善这一步了。

    2.2.2 接收阴影

    我们开始对 Shadow 改造

    I. 添加一个头文件,我们计算阴影所需要的宏都是在这个文件中声明的

    II. 在输出结构体添加一个内置宏

    这个宏用于声明对阴影纹理采样的坐标,参数为下一个可用的插值寄存器的索引,在上面,我们在 worldNormal 和 worldPos 都使用了一个,所以此时这个宏的参数应该为2

    III. 在顶点着色器中添加一个宏

    TRANSFER_SHADOW 这个宏会计算上一步定义的阴影纹理坐标。我们可以在 AutoLight 中看到它的定义

    IV. 在片元着色器中计算阴影,同样使用一个内置宏

    V. 将得到的阴影值与漫反射颜色,高光反射颜色相乘

    VI. 保存,查看效果

    可以看到,正方体已经接收到了右边平面的阴影。

    此时,读者可能会有疑惑,上面步骤中那些代码应该添加在哪里,因为前向渲染中我们定义了两个 Pass,Base Pass 和 Additional Pass。事实上,两个 Pass 对阴影处理的原理是一样的,上面的步骤,我只对 Base Pass 做了修改,但这是不够完善的,所以接下来,我们来介绍完整的阴影管理。

    在这里,还需注意的是 SHADOW_COORDS,TRANSFER_SHADOW,SHADOW_ATTENUATION 这三个宏会根据不同的情况有不同的定义

     1 // ---- Screen space direction light shadows helpers (any version)
     2 #if defined (SHADOWS_SCREEN)
     3 
     4     #if defined(UNITY_NO_SCREENSPACE_SHADOWS)
     5         UNITY_DECLARE_SHADOWMAP(_ShadowMapTexture);
     6         #define TRANSFER_SHADOW(a) a._ShadowCoord = mul( unity_WorldToShadow[0], mul( unity_ObjectToWorld, v.vertex ) );
     7         inline fixed unitySampleShadow (unityShadowCoord4 shadowCoord)
     8         {
     9             #if defined(SHADOWS_NATIVE)
    10                 fixed shadow = UNITY_SAMPLE_SHADOW(_ShadowMapTexture, shadowCoord.xyz);
    11                 shadow = _LightShadowData.r + shadow * (1-_LightShadowData.r);
    12                 return shadow;
    13             #else
    14                 unityShadowCoord dist = SAMPLE_DEPTH_TEXTURE(_ShadowMapTexture, shadowCoord.xy);
    15                 // tegra is confused if we use _LightShadowData.x directly
    16                 // with "ambiguous overloaded function reference max(mediump float, float)"
    17                 unityShadowCoord lightShadowDataX = _LightShadowData.x;
    18                 unityShadowCoord threshold = shadowCoord.z;
    19                 return max(dist > threshold, lightShadowDataX);
    20             #endif
    21         }
    22 
    23     #else // UNITY_NO_SCREENSPACE_SHADOWS
    24         UNITY_DECLARE_SCREENSPACE_SHADOWMAP(_ShadowMapTexture);
    25         #define TRANSFER_SHADOW(a) a._ShadowCoord = ComputeScreenPos(a.pos);
    26         inline fixed unitySampleShadow (unityShadowCoord4 shadowCoord)
    27         {
    28             fixed shadow = UNITY_SAMPLE_SCREEN_SHADOW(_ShadowMapTexture, shadowCoord);
    29             return shadow;
    30         }
    31 
    32     #endif
    33 
    34     #define SHADOW_COORDS(idx1) unityShadowCoord4 _ShadowCoord : TEXCOORD##idx1;
    35     #define SHADOW_ATTENUATION(a) unitySampleShadow(a._ShadowCoord)
    36 #endif

    当然,你可以不必过于深究,但需要注意的是,这三个宏是天生的三个好基友,如果关闭了阴影,那么 SHADOW_COORDS,TRANSFER_SHADOW 会不起作用,而 SHADOW_ATTENUATION 的值为 1 。那么漫反射颜色和高光反射颜色不受 shadow 影响。而且这些宏会使用 v.vertex 和 a.pos 等变量来计算,所以 a2v 顶点坐标变量必须为 vertex,输入结构体 a2v 必须命名为 v ,且 v2f 中顶点位置坐标为 pos。

    2.2.3 完善的的光照衰减和阴影管理

    在之前实现前向渲染的时候,我们为了得到光照衰减值,对不同光源做了判断,然后将得到的结果与反射颜色相乘,在这一点上,阴影的计算过程类似。而幸运的是,Unity 为我们提供了一个内置宏 UNITY_LIGHT_ATTENUATION 来同时得到光照衰减因子和阴影值。我们可以在 AutoLight 中找到它的定义

    1 #define UNITY_LIGHT_ATTENUATION(destName, input, worldPos) 
    2         unityShadowCoord3 lightCoord = mul(unity_WorldToLight, unityShadowCoord4(worldPos, 1)).xyz; 
    3         fixed shadow = UNITY_SHADOW_ATTENUATION(input, worldPos); 
    4         fixed destName = tex2D(_LightTexture0, dot(lightCoord, lightCoord).rr).r * shadow;
    5 #endif

    接下来,我们用它替换 Base Pass 和 Additional Pass 中对光照衰减和阴影的计算。代码和之前大部分都是一样的,所以这里就不分步讲解,给出完整代码。读者可以自行实现一下。

      1 Shader "Unity/01-Shadow"
      2 {
      3     Properties
      4     {
      5         _Diffuse ("Diffuse", Color) = (1, 1, 1, 1)
      6         _Specular ("Specular", Color) = (1, 1, 1, 1)
      7         _Gloss ("Gloss", Range(8.0, 256)) = 20
      8     }
      9     SubShader
     10     {
     11         Tags { "RenderType"="Opaque" }
     12         
     13         Pass {
     14 
     15             Tags { "LightMode"="ForwardBase" }
     16         
     17             CGPROGRAM
     18             
     19             #pragma multi_compile_fwdbase    
     20             
     21             #pragma vertex vert
     22             #pragma fragment frag
     23             
     24             #include "Lighting.cginc"
     25             #include "AutoLight.cginc"
     26             
     27             fixed4 _Diffuse;
     28             fixed4 _Specular;
     29             float _Gloss;
     30             
     31             struct a2v {
     32                 float4 vertex : POSITION;
     33                 float3 normal : NORMAL;
     34             };
     35             
     36             struct v2f {
     37                 float4 pos : SV_POSITION;
     38                 float3 worldNormal : TEXCOORD0;
     39                 float3 worldPos : TEXCOORD1;
     40                 SHADOW_COORDS(2)
     41             };
     42             
     43             v2f vert(a2v v) {
     44                 v2f o;
     45                 o.pos = UnityObjectToClipPos(v.vertex);
     46                 
     47                 o.worldNormal = UnityObjectToWorldNormal(v.normal);
     48                 
     49                 o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
     50                 
     51                 TRANSFER_SHADOW(o);
     52                 return o;
     53             }
     54             
     55             fixed4 frag(v2f i) : SV_Target {
     56                 fixed3 worldNormal = normalize(i.worldNormal);
     57                 fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
     58                 
     59                 UNITY_LIGHT_ATTENUATION(atten,i,i.worldPos);
     60 
     61                 fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
     62                 
     63                  fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * max(0, dot(worldNormal, worldLightDir));
     64 
     65                  fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz);
     66                  fixed3 halfDir = normalize(worldLightDir + viewDir);
     67                  fixed3 specular = _LightColor0.rgb * _Specular.rgb  * pow(max(0, dot(worldNormal, halfDir)), _Gloss);
     68 
     69 
     70                 
     71                 return fixed4(ambient + (diffuse + specular) * atten, 1.0);
     72             }
     73             
     74             ENDCG
     75         }
     76     
     77         Pass {
     78 
     79             Tags { "LightMode"="ForwardAdd" }
     80             
     81             Blend One One
     82         
     83             CGPROGRAM
     84             
     85             #pragma multi_compile_fwdadd
     86             
     87             #pragma vertex vert
     88             #pragma fragment frag
     89             
     90             #include "Lighting.cginc"
     91             #include "AutoLight.cginc"
     92             
     93             fixed4 _Diffuse;
     94             fixed4 _Specular;
     95             float _Gloss;
     96             
     97             struct a2v {
     98                 float4 vertex : POSITION;
     99                 float3 normal : NORMAL;
    100             };
    101             
    102             struct v2f {
    103                 float4 pos : SV_POSITION;
    104                 float3 worldNormal : TEXCOORD0;
    105                 float3 worldPos : TEXCOORD1;
    106                 SHADOW_COORDS(2)
    107 
    108             };
    109             
    110             v2f vert(a2v v) {
    111                 v2f o;
    112                 o.pos = UnityObjectToClipPos(v.vertex);
    113                 
    114                 o.worldNormal = UnityObjectToWorldNormal(v.normal);
    115                 
    116                 o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
    117                 TRANSFER_SHADOW(o);
    118                 return o;
    119             }
    120             
    121             fixed4 frag(v2f i) : SV_Target {
    122                 fixed3 worldNormal = normalize(i.worldNormal);
    123                 #ifdef USING_DIRECTIONAL_LIGHT
    124                     fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
    125                 #else
    126                     fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz - i.worldPos.xyz);
    127                 #endif
    128                 
    129                 fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * max(0, dot(worldNormal, worldLightDir));
    130                 
    131                 fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz);
    132                 fixed3 halfDir = normalize(worldLightDir + viewDir);
    133                 fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(worldNormal, halfDir)), _Gloss);
    134                 
    135                 UNITY_LIGHT_ATTENUATION(atten,i,i.worldPos);
    136                 
    137 
    138                 return fixed4((diffuse + specular) * atten, 1.0);
    139             }
    140             
    141             ENDCG
    142         }
    143     }
    144     FallBack "Specular"
    145 }

    2.3 普通透明物体的阴影


    光源效果体现于实物,如果一个物体是透明的,那么它肯定是没有影子,那在 shader 中,这是如何实现呢? 用回我们在 【Unity Shader】(五) ------ 透明效果之半透明效果的实现及原理 实现的透明度测试并结合上面的代码,并把 Fallback 改为 VertexLit。看一下效果

     

    可以看到阴影部分其实是相当于整个正方体的阴影,但事实上,镂空区域不应该有阴影。这是因为 VertexLit 中处理阴影的 Pass 并没有做透明度测试的计算。所以为了提供这样的一个 Pass ,我们可以更改 Fallback 为 "Transparent/Cutout/VertexLit" 。要注意的是,需要提供一个 _CutOff 的属性和 SHADOW_COORDS 的索引值。现在看一下效果:

    这是属于正常的结果。另外关于透明度混合的物体添加阴影会更加的复杂麻烦,有兴趣的读者可以自行思考与实现一下,这里就不再赘述了。

    三. 完整的光照 shader

    emmm,在之前就提到过会给出完整的一个光照 shader ,现在我们给出一个包含了纹理计算,光照计算,阴影计算,基于 Blinn-Phong 的高光发射 shader。

      1 Shader "Unity/BumpedSpecular" {
      2     Properties {
      3         _Color ("Color Tint", Color) = (1, 1, 1, 1)
      4         _MainTex ("Main Tex", 2D) = "white" {}
      5         _BumpMap ("Normal Map", 2D) = "bump" {}
      6         _Specular ("Specular Color", Color) = (1, 1, 1, 1)
      7         _Gloss ("Gloss", Range(8.0, 256)) = 20
      8     }
      9     SubShader {
     10         Tags { "RenderType"="Opaque" "Queue"="Geometry"}
     11         
     12         Pass { 
     13             Tags { "LightMode"="ForwardBase" }
     14         
     15             CGPROGRAM
     16             
     17             #pragma multi_compile_fwdbase    
     18             
     19             #pragma vertex vert
     20             #pragma fragment frag
     21             
     22             #include "UnityCG.cginc"
     23             #include "Lighting.cginc"
     24             #include "AutoLight.cginc"
     25             
     26             fixed4 _Color;
     27             sampler2D _MainTex;
     28             float4 _MainTex_ST;
     29             sampler2D _BumpMap;
     30             float4 _BumpMap_ST;
     31             fixed4 _Specular;
     32             float _Gloss;
     33             
     34             struct a2v {
     35                 float4 vertex : POSITION;
     36                 float3 normal : NORMAL;
     37                 float4 tangent : TANGENT;
     38                 float4 texcoord : TEXCOORD0;
     39             };
     40             
     41             struct v2f {
     42                 float4 pos : SV_POSITION;
     43                 float4 uv : TEXCOORD0;
     44                 float4 TtoW0 : TEXCOORD1;  
     45                                 float4 TtoW1 : TEXCOORD2;  
     46                                 float4 TtoW2 : TEXCOORD3; 
     47                 SHADOW_COORDS(4)
     48             };
     49             
     50             v2f vert(a2v v) {
     51                  v2f o;
     52                  o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
     53              
     54                  o.uv.xy = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
     55                  o.uv.zw = v.texcoord.xy * _BumpMap_ST.xy + _BumpMap_ST.zw;
     56 
     57                 TANGENT_SPACE_ROTATION;
     58                 
     59                 float3 worldPos = mul(_Object2World, v.vertex).xyz;  
     60                                 fixed3 worldNormal = UnityObjectToWorldNormal(v.normal);  
     61                                 fixed3 worldTangent = UnityObjectToWorldDir(v.tangent.xyz);  
     62                                 fixed3 worldBinormal = cross(worldNormal, worldTangent) * v.tangent.w; 
     63                 
     64                                 o.TtoW0 = float4(worldTangent.x, worldBinormal.x, worldNormal.x, worldPos.x);  
     65                                 o.TtoW1 = float4(worldTangent.y, worldBinormal.y, worldNormal.y, worldPos.y);  
     66                                 o.TtoW2 = float4(worldTangent.z, worldBinormal.z, worldNormal.z, worldPos.z);  
     67                   
     68                   TRANSFER_SHADOW(o);
     69                  
     70                  return o;
     71             }
     72             
     73             fixed4 frag(v2f i) : SV_Target {
     74                 float3 worldPos = float3(i.TtoW0.w, i.TtoW1.w, i.TtoW2.w);
     75                 fixed3 lightDir = normalize(UnityWorldSpaceLightDir(worldPos));
     76                 fixed3 viewDir = normalize(UnityWorldSpaceViewDir(worldPos));
     77                 
     78                 fixed3 bump = UnpackNormal(tex2D(_BumpMap, i.uv.zw));
     79                 bump = normalize(half3(dot(i.TtoW0.xyz, bump), dot(i.TtoW1.xyz, bump), dot(i.TtoW2.xyz, bump)));
     80 
     81                 fixed3 albedo = tex2D(_MainTex, i.uv.xy).rgb * _Color.rgb;
     82                 
     83                 fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
     84                 
     85                  fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(bump, lightDir));
     86                  
     87                  fixed3 halfDir = normalize(lightDir + viewDir);
     88                  fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(bump, halfDir)), _Gloss);
     89             
     90                 UNITY_LIGHT_ATTENUATION(atten, i, worldPos);
     91 
     92                 return fixed4(ambient + (diffuse + specular) * atten, 1.0);
     93             }
     94             
     95             ENDCG
     96         }
     97         
     98         Pass { 
     99             Tags { "LightMode"="ForwardAdd" }
    100             
    101             Blend One One
    102         
    103             CGPROGRAM
    104             
    105             #pragma multi_compile_fwdadd
    106             // Use the line below to add shadows for point and spot lights
    107 //            #pragma multi_compile_fwdadd_fullshadows
    108             
    109             #pragma vertex vert
    110             #pragma fragment frag
    111             
    112             #include "Lighting.cginc"
    113             #include "AutoLight.cginc"
    114             
    115             fixed4 _Color;
    116             sampler2D _MainTex;
    117             float4 _MainTex_ST;
    118             sampler2D _BumpMap;
    119             float4 _BumpMap_ST;
    120             float _BumpScale;
    121             fixed4 _Specular;
    122             float _Gloss;
    123             
    124             struct a2v {
    125                 float4 vertex : POSITION;
    126                 float3 normal : NORMAL;
    127                 float4 tangent : TANGENT;
    128                 float4 texcoord : TEXCOORD0;
    129             };
    130             
    131             struct v2f {
    132                 float4 pos : SV_POSITION;
    133                 float4 uv : TEXCOORD0;
    134                 float4 TtoW0 : TEXCOORD1;  
    135                                 float4 TtoW1 : TEXCOORD2;  
    136                                 float4 TtoW2 : TEXCOORD3;
    137                 SHADOW_COORDS(4)
    138             };
    139             
    140             v2f vert(a2v v) {
    141                  v2f o;
    142                  o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
    143              
    144                  o.uv.xy = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
    145                  o.uv.zw = v.texcoord.xy * _BumpMap_ST.xy + _BumpMap_ST.zw;
    146 
    147                 float3 worldPos = mul(_Object2World, v.vertex).xyz;  
    148                                 fixed3 worldNormal = UnityObjectToWorldNormal(v.normal);  
    149                                 fixed3 worldTangent = UnityObjectToWorldDir(v.tangent.xyz);  
    150                                 fixed3 worldBinormal = cross(worldNormal, worldTangent) * v.tangent.w; 
    151     
    152                   o.TtoW0 = float4(worldTangent.x, worldBinormal.x, worldNormal.x, worldPos.x);
    153                   o.TtoW1 = float4(worldTangent.y, worldBinormal.y, worldNormal.y, worldPos.y);
    154                   o.TtoW2 = float4(worldTangent.z, worldBinormal.z, worldNormal.z, worldPos.z);  
    155                  
    156                  TRANSFER_SHADOW(o);
    157                  
    158                  return o;
    159             }
    160             
    161             fixed4 frag(v2f i) : SV_Target {
    162                 float3 worldPos = float3(i.TtoW0.w, i.TtoW1.w, i.TtoW2.w);
    163                 fixed3 lightDir = normalize(UnityWorldSpaceLightDir(worldPos));
    164                 fixed3 viewDir = normalize(UnityWorldSpaceViewDir(worldPos));
    165                 
    166                 fixed3 bump = UnpackNormal(tex2D(_BumpMap, i.uv.zw));
    167                 bump = normalize(half3(dot(i.TtoW0.xyz, bump), dot(i.TtoW1.xyz, bump), dot(i.TtoW2.xyz, bump)));
    168                 
    169                 fixed3 albedo = tex2D(_MainTex, i.uv.xy).rgb * _Color.rgb;
    170                 
    171                  fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(bump, lightDir));
    172                  
    173                  fixed3 halfDir = normalize(lightDir + viewDir);
    174                  fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(bump, halfDir)), _Gloss);
    175             
    176                 UNITY_LIGHT_ATTENUATION(atten, i, worldPos);
    177 
    178                 return fixed4((diffuse + specular) * atten, 1.0);
    179             }
    180             
    181             ENDCG
    182         }
    183     } 
    184     FallBack "Specular"
    185 }

    四. 总结

    关于光照衰减我们通常是使用 LUT 来得到衰减值,这样在大部分情况下都能得到良好的效果且能提升性能。当然,如果是为了保持灵活性,我们也可以使用最原始的数学公式。

    关于阴影,我们需要知道的是,光源需要开启阴影效果;物体的要开启 CasterShadow,让自身加入阴影映射纹理的计算,这样周围的物体才可以接收到自身投射的阴影

    阴影映射纹理本质上是一张深度图,这个技术原本是延迟渲染产生阴影的方法,也正好解释了我们之前所说的延迟渲染不依赖于场景的复杂度,而依赖于屏幕空间大小。需要注意的是这个技术是在 Unity 5.0 之后才有的,它要求显卡支持 MRT,即表明这个技术对硬件有要求

    最后,本文和上一篇 【Unity Shader】(六) ------ 复杂的光照(上)都介绍了许多理论知识,看起来十分的枯燥,但我还是衷心地希望本文能对你有所帮助。


  • 相关阅读:
    关于云原生应用的思考
    动手实现 LRU 算法,以及 Caffeine 和 Redis 中的缓存淘汰策略
    Spring5-Reactor函数式编程
    架构简洁之道:从阿里开源应用架构 COLA 说起
    如何优雅地运用位运算实现产品需求?
    如何优雅地运用位运算实现产品需求?
    图形处理:给 Canvas 文本填充线性渐变
    深入理解EnableAutoConfiguration原理
    pwnable.tw之3x17
    WebRTC之完整搭建Jitsi Meet指南
  • 原文地址:https://www.cnblogs.com/BFXYMY/p/9854536.html
Copyright © 2011-2022 走看看