Unity Shader入门精要——第9章 更复杂的光照

Unity Shader入门精要读书笔记系列

第1章 欢迎来到Shader的世界
第2章 渲染流水线
第3章 Unity Shader基础
第4章 学习Shader所需的数学基础
第5章 开始 Unity Shader 学习之旅
第6章 Unity中的基础光照
第7章 基础纹理
第8章 透明效果
第9章 更复杂的光照



前言

这一章开始进入中级篇的学习。在之前的学习中,我们实现了基础的光照模型。比如漫反射的兰伯特模型,高光反射的Phong模型等。
但他们都不是完整的光照模型,本章我们来学习包含了光照衰减和阴影的完整光照模型。

一、Unity的渲染路径

在Unity中,渲染路径(Rendering Path)决定了光照是如何应用到Unity Shader中的。即决定了当前渲染目标使用光照的流程。
大多数项目只使用一种渲染路径,可以在Edit下的ProjectSetting——Graphics——TierSettings中设置项目默认的渲染路径。
也可以在相机设置中修改每个相机的渲染路径:
在这里插入图片描述

在Shader中,我们需要为每个pass指定它使用的渲染路径。

1.前向渲染路径(Forward Rendering Path)

在前向渲染中,有三种处理光照的方式:逐顶点处理、逐像素处理、球谐函数(Spherical Harmonics)处理。
当我们渲染一个物体时, Unity会根据场景中各个光源的设置以及这些光源对物体的影响程度(例如, 距离该物体的远近、 光源强度等)对这些光源进行一个重要度排序。
其中,一定数目的光源会按逐像素的方式处理,然后最多有4个光源按逐顶点的方式处理,剩下的光源可以按SH方式处理。

规则一:最亮的几个光源会被实现为像素光照
规则二:然后最多4个光源会被实现为顶点光照
规则三:剩下的光源会被实现为效率较高的球面调谐光照(Spherical Hamanic),这是一种模拟光照
规则一补充说明:
1)最亮的那盏光一定是像素光照
2)Light的Render Mode是important的光一定是像素光照
3)如果前面的两条加起来的像素光照小于Quality Setting里的Pixel Light Count(最大像素光照数量),那么从剩下的光源中找出最亮的那几盏光源,实现为像素光照。
4)最后剩下的光源,按照规则2或3。
5)在Base Pass里执行一盏像素光、所有的顶点光和球面调谐光照,并且进行阴影计算。
6)其余的像素光每盏一个Additional Pass,并且这些pass里没有阴影计算。
7)场景中看到的阴影,全是Base Pass里计算出最亮那盏像素光的阴影,其他像素光是不计算阴影的。
在这里插入图片描述

根据Pass标签中设置的 LightMode,Unity会把不同的光照变量传递给Shader。
前向渲染可以使用的内置光照变量:

名称 类型 描述
_LightColor0 float4 该Pass处理的逐像素光源的颜色
_WorldSpaceLightPos0 float4 _WorldSpaceLightPos0.xyz 是该 Pass处理的逐像素光源的位置。如果该光源时平行光_WorldSpaceLightPos0.z是0,其他光源类型w值为1
_ LightMatrix0 float4X4 从世界空间到光源空间的变换矩阵。可以用于采样cookie和光强衰减(attenuation)纹理
unity_ 4LightPosX0,unity_ 4LightPosY0,unity_ 4LightPosZ0 float4 仅用于Base Pass。前4个非重要的点光源在世界空间中的位置
unity_ 4LightAtten0 float4 仅用于Base Pass。前4个非重要的点光源的衰减因子
unity_ LightColor float4 仅用于Base Pass。前4个非重要的点光源的颜色

2.延迟渲染路径(Deferred Rendering Path)

此部分参考:https://www.zhihu.com/people/tencentboy/posts?page=5

在前向渲染中,对于逐像素光源,如果场景中有N个物体,每个物体受到M个光源的影响,则渲染整个场景一共需要N*M个Pass。当场景比较复杂且有大量实时光源时,渲染性能会急速下降。

前向渲染简单直接,也很容易实现,但是同时它对程序性能的影响也很大,因为对每一个需要渲染的物体,程序都要对每个光源下每一个需要渲染的片段进行迭代,如果旧的片段完全被一些新的片段覆盖,最终无需显示出来,那么其着色计算花费的时间就完全浪费掉了。

为了解决这个问题,可以使用延迟渲染。

延迟渲染使用额外的缓冲区 G-buffer(Geometry Buffer),它包含两个Pass。
在第一 Pass 中,不进行任何光照计算,而是仅仅通过深度缓冲计算哪些片元是可见的,当发现一个片元是可见的,就把它的相关信息(位置(Position),法线(Normal),漫反射颜色(Diffuse Color)以及其他有用材质参数)存储到G缓冲区中。
第二个Pass用于计算真正的光照模型。该Pass会使用上一个Pass渲染的数据来计算最终的光照颜色,再存储到帧缓冲中。
在这里插入图片描述

延迟渲染最为重要的作用,就是将着色计算与场景复杂度解耦,渲染的效率不依赖于场景的复杂度,而是和我们使用的屏幕空间的大小有关。这是因为,我们需要的信息都存储在缓冲区中,而这些缓冲区可以理解成是一张张 2D 图像,我们的计算实际上就是在这些图像空间中进行的。

3.前向渲染与延迟渲染对比

前向渲染:
1)光源数量对计算复杂度影响巨大
2)访问深度等数据需要额外计算

3)支持半透明渲染
4)支持使用多个光照Pass
5)支持自定义光照计算方式
延迟渲染:
1)对MSAA抗锯齿支持不友好
2)透明物体渲染存在问题
3)G-buffer占用大量显存带宽
4)只能用同一套lighting pass

5)在大量光照场景下优势明显
6)只渲染可见像素,节省计算量
7)对后处理支持好

二、Unity的光源类型

Unity 一共支持4种光源类型:平行光、点光源、聚光灯和面光源(area light)。面光源仅在烘焙时才可发挥作用。

光源的五个属性
位置、方向、颜色、强度、衰减

1.平行光
没有位置(无处不在)
到场景中所有点的方向一样
不会衰减(光强不变)

2.点光源
有位置
向所有方向延伸(球形)
光照强度越靠近球心越强
边界处光强为0,衰减值可以由一个函数定义

3.聚光灯
由空间中的一块锥形区域定义、在计算衰减值时要判断是否在椎体范围内。
锥形区域的半径由面板中的Range属性决定。
椎体的张开角度由Spot Angle属性决定。

三、在前向渲染中处理不同的光源类型

以下前向渲染实例中使用两个Pass,在第二个Pass中对不同类型的光源方向和衰减做了相应处理。

// Upgrade NOTE: replaced '_LightMatrix0' with 'unity_WorldToLight'
// Upgrade NOTE: replaced '_Object2World' with 'unity_ObjectToWorld'
// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'

Shader "Unity Shaders Book/Chapter 9/ForwardRendering"
{
    
    
    Properties
    {
    
    
        _Diffuse ("Diffuse", Color) = (1, 1, 1, 1) 
        _Specular ("Specular", Color) = (1, 1, 1, 1) 
        _Gloss ("Gloss", Range(8.0, 256)) = 20
    }
    SubShader
    {
    
    
        Tags {
    
     "RenderType"="Opaque" }

        // Base Pass
        Pass
        {
    
    
            // 用于前向渲染。该 Pass 会计算环境光、最重要的平行光、逐顶点/SH 光源和 lightmaps
            Tags{
    
     "LightMode" = "ForwardBase" }
            
            CGPROGRAM
            // 保证我们在 Shader 中使用光照衰减等光照变量可以被正确赋值。
			#pragma multi_compile_fwdbase

            #pragma vertex vert
            #pragma fragment frag

            #include "Lighting.cginc"
            #include "UnityCG.cginc"

            float4 _Diffuse;
            float4 _Specular;
            float _Gloss;

            struct a2v
            {
    
    
                float4 vertex : POSITION;
                float3 normal : NORMAL;
            };

            struct v2f
            {
    
    
                float4 pos : SV_POSITION;
                float3 worldNormal : TEXCOORD0;
                float3 worldPos : TEXCOORD1;
            };

            v2f vert (a2v v)
            {
    
    
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);
                o.worldNormal = UnityObjectToWorldNormal(v.normal);
                o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
    
    
                fixed3 worldNormal = normalize(i.worldNormal);
                // 使用_WorldSpaceLightPos0获取平行光方向
                fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
                // 环境光只在Bass Pass中计算一次,Additional Pass中不再计算(自发光也类似,本例中假设物体没有自发光)
                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
                 //_LightColor0: 平行光颜色和强度相乘后的结果
                fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * max(0, dot(worldNormal, worldLightDir));

                fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz);
                fixed3 halfDir = normalize(worldLightDir + viewDir);

                fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(worldNormal, halfDir)), _Gloss);
                
                // 假设平行光没有衰减
                fixed atten = 1.0;

                return fixed4(ambient + (diffuse + specular) * atten, 1.0);
            }

            ENDCG
        }
        
        // Additional pass
        Pass
        {
    
    
            // 用于前向渲染。该 Pass会计算额外的逐像素光源,每个 Pass对应一个光源。
            Tags{
    
     "LightMode" = "ForwardAdd" }

            // 使用Blend命令将Additional Pass 计算得到的光照结果与缓存中之前的光照结果进行叠加。
            // 否则之前的光照结果会被覆盖
            Blend One One

            CGPROGRAM

            #pragma multi_compile_fwdadd
            
            #pragma vertex vert
			#pragma fragment frag

            #include "Lighting.cginc"
			#include "AutoLight.cginc"

            fixed4 _Diffuse;
			fixed4 _Specular;
			float _Gloss;
			
			struct a2v 
            {
    
    
				float4 vertex : POSITION;
				float3 normal : NORMAL;
			};
			
			struct v2f 
            {
    
    
				float4 pos : SV_POSITION;
				float3 worldNormal : TEXCOORD0;
				float3 worldPos : TEXCOORD1;
			};
			
			v2f vert(a2v v) 
            {
    
    
				v2f o;
				o.pos = UnityObjectToClipPos(v.vertex);
				o.worldNormal = UnityObjectToWorldNormal(v.normal);
				o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
				
				return o;
			}
			
			fixed4 frag(v2f i) : SV_Target 
            {
    
    
				fixed3 worldNormal = normalize(i.worldNormal);

                // 添加对不同光源类型的支持
				#ifdef USING_DIRECTIONAL_LIGHT
					fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
				#else
					fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz - i.worldPos.xyz);
				#endif
				
				fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * max(0, dot(worldNormal, worldLightDir));
				
				fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz);
				fixed3 halfDir = normalize(worldLightDir + viewDir);

				fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(worldNormal, halfDir)), _Gloss);
				
                // 处理不同光源类型的衰减
                // 尽管我们可以使用数学表达式来计算给定点相对于点光源和聚光灯的衰减 但这些计算往往涉及开根号、除法等
                // 计算量相对较大的操作
                // 因此 Unity 选择了使用一张纹理作为查找表 (Lookup Table, LUT), 
                // 在片元着色器中得到光源的衰减。 我们首先得到光源空间下的坐标,然后使用该坐标对衰减纹理
                // 进行采样得到衰减值。
				#ifdef USING_DIRECTIONAL_LIGHT
					fixed atten = 1.0;
				#else
					#if defined (POINT)
						// 计算光源空间下坐标
				        float3 lightCoord = mul(unity_WorldToLight, float4(i.worldPos, 1)).xyz;
				        fixed atten = tex2D(_LightTexture0, dot(lightCoord, lightCoord).rr).UNITY_ATTEN_CHANNEL;
				    #elif defined (SPOT)
				        float4 lightCoord = mul(unity_WorldToLight, float4(i.worldPos, 1));
				        fixed atten = (lightCoord.z > 0) * tex2D(_LightTexture0, lightCoord.xy / lightCoord.w + 0.5).w * tex2D(_LightTextureB0, dot(lightCoord, lightCoord).rr).UNITY_ATTEN_CHANNEL;
				    #else
				        fixed atten = 1.0;
				    #endif
				#endif

				return fixed4((diffuse + specular) * atten, 1.0);
			}

            ENDCG
        }
    }

    FallBack "Specular"
}

在Unity中可以通过 Frame Debugger来查看前向渲染中每一个渲染事件发生的过程。

Unity中的LUT(Lookup Texture)

由于使用数学公式来计算光照衰减计算量较大,Unity 在内部使用一张名为 _LightTexture0 的纹理(LUT)来计算光源衰减。
虽然会影响衰减精度,但在一定程度上提升了性能。

注意:如果我们对该光源使用了 cookie, 那么衰减查找纹理是 _LightTextureB0

我们通常只关心_LightTexture0 对角线上的纹理颜色值,这些值表明了在光源空间中不同位置的点的衰减值。例如, (0, 0) 点表明了与光源位置重合的点的衰减值,而(1, 1) 点表明了在光源空间中所关心的距离最远的点的衰减。
采样的时候需要使用 unity_WorldToLight 矩阵 变换得到光源空间下的坐标。

四、Unity的阴影

阴影的实现

在实时渲染中,使用 Shadow Map 的基数实现阴影。
把摄像机的位置放在与光源重合的位置上,那么场景中该光源的阴影区域就是那些摄像机看不到的地方。

在前向渲染路径中,如果场景中最重要的平行光开启了阴影, Unity 就会为该光源计算它的阴影映射纹理 (shadowmap) 这张阴影映射纹理本质上也是一张深度图,它记录了从该光源的位置出发、能看到的场景中距离它最近的表面位置(深度信息)。

当开启了光源的阴影效果后,底层渲染引擎首先会在当前渲染物体的 Unity Shader 中找到 LightMode 为 ShadowCaster 的Pass, 如果没有,它就会在 Fallback 指定的 Unity Shader 中继续寻找,如果仍然没有找到 该物体就无法向其他物体投射阴影(但它仍然可以接收来自其他物体的阴影) 当找到了 LightMode 为SbadowCaster 的 Pass 后, Unity 会使用该 Pass 来更新光源的阴影映射纹理。

具体实现
1)把摄像机放置到光源的位置上
2)使用一个额外的 Pass 来专门更新光源的阴影映射纹理,这个 Pass 就是 LightMode 标签被设置为 ShadowCaster 的Pass。
它的渲染目标不是帧缓存,而是阴影映射纹理(或深度纹理)。
3)对顶点变换后得到光源空间下的位置,并据此来输出深度信息到阴影映射纹理中。

接下来就可以利用这张阴影映射纹理来计算阴影:
1)在正常渲染的 Pass 中把顶点位置变换到光源空间下,以得到它在光源空间中的三维位置信息。
2)使用 xy 分量对阴影映射纹理进行采样,得到阴影映射纹理中该位置的深度信息。如果该深度值小于该顶点的深度值(通常由 z分量得到),那么说明该点位于阴影中。
3)把采样结果和最后的光照结果相乘来产生阴影效果。

如果显卡支持MRT,还可以使用屏幕空间的阴影映射技术(Screenspace Shadow Map)。
使用屏幕空间的阴影映射技术时, Unity 首先会通过调用 LightMode 为 ShadowCaster的 Pass来得到可投射阴影的光源的阴影映射纹理以及摄像机的深度纹理。然后,根据光源的阴影映射纹理和摄像机的深度纹理来得到屏幕空间的阴影图。如果摄像机的深度图中记录的表面深度大于转换到阴影映射纹理中的深度值,就说明该表面虽然是可见的,但是却处于该光源的阴影中。通过这样的方式,阴影图就包含了屏幕空间中所有有阴影的区域。如果我们想要某个物体接收来自其他物体的阴影,只需要在 Shader 中对阴影图进行采样。由于阴影图是屏幕空间下的,因此,我们首先需要把表面坐标从模型空间变换到屏幕空间中,然后使用这个坐标对阴影图进行采样即可。

不透明物体的阴影

设置物体的Mesh Renderer的Cat Shadows和Receive Shadows:
Cat Shadows:投射阴影。有四种模式
1)off:没用投射阴影
2)on:有投射阴影
3)Two Sided:可以从网格的任意一面投射出阴影。适用于半封闭物体
4)Shadows Only:只显示阴影,不显示网格

Receive Shadows:这个物体是否接收阴影

1.投射阴影

通常在Shader中通过 Fallback 指定一个Unity内置的Shader,这个内置Shader中的Fallback 调用一个VertexLit。
最后找到一个LightMode为ShadowCaster的Pass,从而让物体产生阴影。
VertexLit.shader

2.接收阴影

通过 “AutoLight.cginc” 中的内置宏
SHADOW_COORDS :声明_ShadowCoord变量,用于对阴影纹理采样的坐标,参数是下一个可用的插值寄存器的索引值。
TRANSFER_SHADOW:调用内置的_ComputeScreenPos 函数计算上一步中声明的阴影纹理坐标。
SHADOW_ATTENUATION:使用 _ShadowCoord对相关的纹理进行采样,在片元着色器中计算阴影值。
最终乘上光照结果来得到最终阴影效果。

注意:由于这些宏中会使用上下文变量来进行相关计算 , 例如TRANSFER_SHADOW会使用v.vertex或a.pos来计算坐标, 因此为了能够让这些宏正确工作, 我们需要保证自定义的变量名和这些宏中使用的变量名相匹配。 我们需要保证: a2v 结构体中的顶点坐标变量名必须是vertex, 顶点着色器的输入结构体v2f必须命名为v, 且v2f中的顶点位置必须命名为pos。

// Upgrade NOTE: replaced '_LightMatrix0' with 'unity_WorldToLight'
// Upgrade NOTE: replaced '_Object2World' with 'unity_ObjectToWorld'
// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'

Shader "Unity Shaders Book/Chapter 9/Shadow"
{
    
    
    Properties
    {
    
    
        _Diffuse ("Diffuse", Color) = (1, 1, 1, 1) 
        _Specular ("Specular", Color) = (1, 1, 1, 1) 
        _Gloss ("Gloss", Range(8.0, 256)) = 20
    }
    SubShader
    {
    
    
        Tags {
    
     "RenderType"="Opaque" }

        // Base Pass
        Pass
        {
    
    
            Tags{
    
     "LightMode" = "ForwardBase" }
            
            CGPROGRAM

			#pragma multi_compile_fwdbase

            #pragma vertex vert
            #pragma fragment frag

            #include "Lighting.cginc"
            #include "UnityCG.cginc"
            // 包含一些计算阴影时所用的宏
            #include "AutoLight.cginc"

            float4 _Diffuse;
            float4 _Specular;
            float _Gloss;

            struct a2v
            {
    
    
                float4 vertex : POSITION;
                float3 normal : NORMAL;
            };

            struct v2f
            {
    
    
                float4 pos : SV_POSITION;
                float3 worldNormal : TEXCOORD0;
                float3 worldPos : TEXCOORD1;
                // 添加一个内置宏,声明一个用于对阴影纹理采样的坐标,参数是下一个可用的插值寄存器的索引值
                SHADOW_COORDS(2)
            };

            v2f vert (a2v v)
            {
    
    
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);
                o.worldNormal = UnityObjectToWorldNormal(v.normal);
                o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;

                // 计算上一步中声明的阴影纹理坐标
			 	TRANSFER_SHADOW(o);
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
    
    
                fixed3 worldNormal = normalize(i.worldNormal);
                fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
                //_LightColor0 平行光颜色和强度相乘后的结果
                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
                fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * max(0, dot(worldNormal, worldLightDir));

                fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz);
                fixed3 halfDir = normalize(worldLightDir + viewDir);

                fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(worldNormal, halfDir)), _Gloss);

                fixed atten = 1.0;

                // 在片元着色器中计算阴影值
                fixed shadow = SHADOW_ATTENUATION(i);

                return fixed4(ambient + (diffuse + specular) * atten * shadow, 1.0);
            }

            ENDCG
        }
        
        // Additional pass
        Pass
        {
    
    
            Tags{
    
     "LightMode" = "ForwardAdd" }

            Blend One One

            CGPROGRAM

            #pragma multi_compile_fwdadd
            
            #pragma vertex vert
			#pragma fragment frag

            #include "Lighting.cginc"
			#include "AutoLight.cginc"

            fixed4 _Diffuse;
			fixed4 _Specular;
			float _Gloss;
			
			struct a2v 
            {
    
    
				float4 vertex : POSITION;
				float3 normal : NORMAL;
			};
			
			struct v2f 
            {
    
    
				float4 pos : SV_POSITION;
				float3 worldNormal : TEXCOORD0;
				float3 worldPos : TEXCOORD1;
			};
			
			v2f vert(a2v v) 
            {
    
    
				v2f o;
				o.pos = UnityObjectToClipPos(v.vertex);
				o.worldNormal = UnityObjectToWorldNormal(v.normal);
				o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
				
				return o;
			}
			
			fixed4 frag(v2f i) : SV_Target 
            {
    
    
				fixed3 worldNormal = normalize(i.worldNormal);
				#ifdef USING_DIRECTIONAL_LIGHT
					fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
				#else
					fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz - i.worldPos.xyz);
				#endif
				
				fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * max(0, dot(worldNormal, worldLightDir));
				
				fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz);
				fixed3 halfDir = normalize(worldLightDir + viewDir);
				fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(worldNormal, halfDir)), _Gloss);
				
				#ifdef USING_DIRECTIONAL_LIGHT
					fixed atten = 1.0;
				#else
					#if defined (POINT)
				        float3 lightCoord = mul(unity_WorldToLight, float4(i.worldPos, 1)).xyz;
				        fixed atten = tex2D(_LightTexture0, dot(lightCoord, lightCoord).rr).UNITY_ATTEN_CHANNEL;
				    #elif defined (SPOT)
				        float4 lightCoord = mul(unity_WorldToLight, float4(i.worldPos, 1));
				        fixed atten = (lightCoord.z > 0) * tex2D(_LightTexture0, lightCoord.xy / lightCoord.w + 0.5).w * tex2D(_LightTextureB0, dot(lightCoord, lightCoord).rr).UNITY_ATTEN_CHANNEL;
				    #else
				        fixed atten = 1.0;
				    #endif
				#endif

				return fixed4((diffuse + specular) * atten, 1.0);
			}

            ENDCG
        }
    }

    FallBack "Specular"
}

统一计算光照衰减和阴影

// Upgrade NOTE: replaced '_Object2World' with 'unity_ObjectToWorld'
// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'

Shader "Unity Shaders Book/Chapter 9/Attenuation And Shadow Use Build-in Functions" {
    
    
	Properties {
    
    
		_Diffuse ("Diffuse", Color) = (1, 1, 1, 1)
		_Specular ("Specular", Color) = (1, 1, 1, 1)
		_Gloss ("Gloss", Range(8.0, 256)) = 20
	}
	SubShader {
    
    
		Tags {
    
     "RenderType"="Opaque" }
		
		Pass {
    
    
			// Pass for ambient light & first pixel light (directional light)
			Tags {
    
     "LightMode"="ForwardBase" }
		
			CGPROGRAM
			
			// Apparently need to add this declaration
			#pragma multi_compile_fwdbase	
			
			#pragma vertex vert
			#pragma fragment frag
			
			// Need these files to get built-in macros
			#include "Lighting.cginc"
			#include "AutoLight.cginc"
			
			fixed4 _Diffuse;
			fixed4 _Specular;
			float _Gloss;
			
			struct a2v {
    
    
				float4 vertex : POSITION;
				float3 normal : NORMAL;
			};
			
			struct v2f {
    
    
				float4 pos : SV_POSITION;
				float3 worldNormal : TEXCOORD0;
				float3 worldPos : TEXCOORD1;
				SHADOW_COORDS(2)
			};
			
			v2f vert(a2v v) {
    
    
			 	v2f o;
			 	o.pos = UnityObjectToClipPos(v.vertex);
			 	
			 	o.worldNormal = UnityObjectToWorldNormal(v.normal);
			 	
			 	o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
			 	
			 	// Pass shadow coordinates to pixel shader
			 	TRANSFER_SHADOW(o);
			 	
			 	return o;
			}
			
			fixed4 frag(v2f i) : SV_Target {
    
    
				fixed3 worldNormal = normalize(i.worldNormal);
				fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
				
				fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
				
			 	fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * max(0, dot(worldNormal, worldLightDir));

			 	fixed3 viewDir = normalize(UnityWorldSpaceViewDir(i.worldPos));
			 	fixed3 halfDir = normalize(worldLightDir + viewDir);
			 	fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(worldNormal, halfDir)), _Gloss);

				// UNITY_LIGHT_ATTENUATION not only compute attenuation, but also shadow infos
				UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos);
				
				return fixed4(ambient + (diffuse + specular) * atten, 1.0);
			}
			
			ENDCG
		}
	
		Pass {
    
    
			// Pass for other pixel lights
			Tags {
    
     "LightMode"="ForwardAdd" }
			
			Blend One One
		
			CGPROGRAM
			
			// Apparently need to add this declaration
			#pragma multi_compile_fwdadd
			// Use the line below to add shadows for point and spot lights
			#pragma multi_compile_fwdadd_fullshadows
			
			#pragma vertex vert
			#pragma fragment frag
			
			#include "Lighting.cginc"
			#include "AutoLight.cginc"
			
			fixed4 _Diffuse;
			fixed4 _Specular;
			float _Gloss;
			
			struct a2v {
    
    
				float4 vertex : POSITION;
				float3 normal : NORMAL;
			};
			
			struct v2f {
    
    
				float4 pos : SV_POSITION;
				float3 worldNormal : TEXCOORD0;
				float3 worldPos : TEXCOORD1;
				SHADOW_COORDS(2)
			};
			
			v2f vert(a2v v) {
    
    
			 	v2f o;
			 	o.pos = UnityObjectToClipPos(v.vertex);
			 	
			 	o.worldNormal = UnityObjectToWorldNormal(v.normal);
			 	
			 	o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
			 	
			 	// Pass shadow coordinates to pixel shader
			 	TRANSFER_SHADOW(o);
			 	
			 	return o;
			}
			
			fixed4 frag(v2f i) : SV_Target {
    
    
				fixed3 worldNormal = normalize(i.worldNormal);
				fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
				
			 	fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * max(0, dot(worldNormal, worldLightDir));

			 	fixed3 viewDir = normalize(UnityWorldSpaceViewDir(i.worldPos));
			 	fixed3 halfDir = normalize(worldLightDir + viewDir);
			 	fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(worldNormal, halfDir)), _Gloss);

				// UNITY_LIGHT_ATTENUATION not only compute attenuation, but also shadow infos
				UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos);
			 	
				return fixed4((diffuse + specular) * atten, 1.0);
			}
			
			ENDCG
		}
	}
	FallBack "Specular"
}

UNITY_LIGHT_ATTENUATION 是Unity 内置的用于计算光照衰减和阴影的宏,我们可以在内置的 AutoLight.cginc 里找到它的相关声明。
它接受三个参数,第1个参数为 atten 由Unity帮助我们声明。
第二个参数为片元着色器的输入 i,第三个参数为世界空间下的坐标。
它会将光照衰减和阴影值相乘后的结果存储到第1个参数中。

透明物体的阴影

1.透明度测试

通过将 Fallback 设置为 Transparent/CutoutNertexLit,并声明_Cutoff属性进行透明度测试。
注意Mesh Renderer 组件中的 Cas Shadows 属性设置为 Two Sided。

// Upgrade NOTE: replaced '_Object2World' with 'unity_ObjectToWorld'
// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'

Shader "Unity Shaders Book/Chapter 9/Alpha Test With Shadow" {
    
    
	Properties {
    
    
		_Color ("Color Tint", Color) = (1, 1, 1, 1)
		_MainTex ("Main Tex", 2D) = "white" {
    
    }
		_Cutoff ("Alpha Cutoff", Range(0, 1)) = 0.5
	}
	SubShader {
    
    
		Tags {
    
    "Queue"="AlphaTest" "IgnoreProjector"="True" "RenderType"="TransparentCutout"}
		
		Pass {
    
    
			Tags {
    
     "LightMode"="ForwardBase" }
			
			Cull Off
			
			CGPROGRAM
			
			#pragma multi_compile_fwdbase
			
			#pragma vertex vert
			#pragma fragment frag
			
			#include "Lighting.cginc"
			#include "AutoLight.cginc"
			
			fixed4 _Color;
			sampler2D _MainTex;
			float4 _MainTex_ST;
			fixed _Cutoff;
			
			struct a2v {
    
    
				float4 vertex : POSITION;
				float3 normal : NORMAL;
				float4 texcoord : TEXCOORD0;
			};
			
			struct v2f {
    
    
				float4 pos : SV_POSITION;
				float3 worldNormal : TEXCOORD0;
				float3 worldPos : TEXCOORD1;
				float2 uv : TEXCOORD2;
				SHADOW_COORDS(3)
			};
			
			v2f vert(a2v v) {
    
    
			 	v2f o;
			 	o.pos = UnityObjectToClipPos(v.vertex);
			 	
			 	o.worldNormal = UnityObjectToWorldNormal(v.normal);
			 	
			 	o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;

			 	o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
			 	
			 	// Pass shadow coordinates to pixel shader
			 	TRANSFER_SHADOW(o);
			 	
			 	return o;
			}
			
			fixed4 frag(v2f i) : SV_Target {
    
    
				fixed3 worldNormal = normalize(i.worldNormal);
				fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
				
				fixed4 texColor = tex2D(_MainTex, i.uv);

				clip (texColor.a - _Cutoff);
				
				fixed3 albedo = texColor.rgb * _Color.rgb;
				
				fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
				
				fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(worldNormal, worldLightDir));
							 	
			 	// UNITY_LIGHT_ATTENUATION not only compute attenuation, but also shadow infos
				UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos);
			 	
				return fixed4(ambient + diffuse * atten, 1.0);
			}
			
			ENDCG
		}
	} 
	FallBack "Transparent/Cutout/VertexLit"
}

2.透明度混合

在 Unity 中,所有内置的半透明 Shader 是不会产生任何阴影效果的。可以通过把它们的 Fallback 设置为
VertexLit、 Diffuse 这些不透明物体使用的 Unity Shader, 这样 Unity 就会在它的 Fallback 找到阴影投射的 Pass 。
然后使用之前学习的透明度混合+阴影计算的方法来渲染。

Shader "Unity Shaders Book/Chapter 9/Alpha Blend With Shadow" {
    
    
	Properties {
    
    
		_Color ("Color Tint", Color) = (1, 1, 1, 1)
		_MainTex ("Main Tex", 2D) = "white" {
    
    }
		_AlphaScale ("Alpha Scale", Range(0, 1)) = 1
	}
	SubShader {
    
    
		Tags {
    
    "Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent"}
		
		Pass {
    
    
			Tags {
    
     "LightMode"="ForwardBase" }
			
			ZWrite Off
			Blend SrcAlpha OneMinusSrcAlpha
			
			CGPROGRAM
			
			#pragma multi_compile_fwdbase
			
			#pragma vertex vert
			#pragma fragment frag
			
			#include "Lighting.cginc"
			#include "AutoLight.cginc"
			
			fixed4 _Color;
			sampler2D _MainTex;
			float4 _MainTex_ST;
			fixed _AlphaScale;
			
			struct a2v {
    
    
				float4 vertex : POSITION;
				float3 normal : NORMAL;
				float4 texcoord : TEXCOORD0;
			};
			
			struct v2f {
    
    
				float4 pos : SV_POSITION;
				float3 worldNormal : TEXCOORD0;
				float3 worldPos : TEXCOORD1;
				float2 uv : TEXCOORD2;
				SHADOW_COORDS(3)
			};
			
			v2f vert(a2v v) {
    
    
			 	v2f o;
			 	o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
			 	
			 	o.worldNormal = UnityObjectToWorldNormal(v.normal);
			 	
			 	o.worldPos = mul(_Object2World, v.vertex).xyz;

			 	o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
			 	
			 	// Pass shadow coordinates to pixel shader
			 	TRANSFER_SHADOW(o);
			 	
			 	return o;
			}
			
			fixed4 frag(v2f i) : SV_Target {
    
    
				fixed3 worldNormal = normalize(i.worldNormal);
				fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
				
				fixed4 texColor = tex2D(_MainTex, i.uv);
				
				fixed3 albedo = texColor.rgb * _Color.rgb;
				
				fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
				
				fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(worldNormal, worldLightDir));

			 	// UNITY_LIGHT_ATTENUATION not only compute attenuation, but also shadow infos
				UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos);
			 	
				return fixed4(ambient + diffuse * atten, texColor.a * _AlphaScale);
			}
			
			ENDCG
		}
	} 
	FallBack "Transparent/VertexLit"
	// Or  force to apply shadow
	//	FallBack "VertexLit"
}

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/qq_41044598/article/details/126451503