前言

这个并不是技术分享的教程类博客,只是我记录的笔记而已。不,不要误会了!(虽然溜一遍下来发现好像也能看

Unity的渲染路径

概念

渲染路径决定光照是如何应用到Unity Shader里的。只有为每个Pass指定正确的渲染路径才能正确执行光照计算。Unity的渲染路径有三种:前向渲染路径、延迟渲染路径和顶点照明渲染路径。每个摄像机可以通过修改Camera组件内的Rendering Path决定该摄像机对光照的渲染路径。如果没有设置,摄像机会遵从Unity设置的渲染路径(即前向渲染路径)。

Pass内部通过标签(Tags {"LightMode"="ForwardBase"})决定使用的渲染路径。(比如“ForwardBase”、“FowardAdd”就是前向渲染路径)所有标签如下:

表一

前向渲染路径

传统、常用的渲染路径。首先利用深度缓冲来决定一个片元是否可见,如果可见就更新颜色缓冲区的颜色值,然后再遍历该模型的下一个片元。当然计算结果仅仅是一个光源的影响结果,如果有多个光源则要再次重复上述步骤。因此,假设场景有N个物体,每个物体受M个光照影响,渲染整个场景就需要N*M个Pass。(然后每个Pass又要渲染该物体的所有片元)。所以如果有大量逐像素光照,需要执行的数目也会很多。

对此,Unity中的前向渲染路径有3种:逐顶点处理、逐像素处理、球谐函数。我们可以在Light组件的Render Mode调整处理方式:

1.设置成Not Important以逐顶点或球谐函数处理;

2.设置成Important的和场景中最亮的平行光按逐像素处理;

3.上述规则得到的逐像素光源数量小于Quality Settings的数量,会有更多光源以逐像素的方式渲染。

前向渲染又有两种Pass:Base PassAdditional Pass

Base Pass:计算一个逐像素的平行光以及所有逐顶点和SH光源,可以实现光照纹理、环境光、自发光和平行光的阴影;一个Base Pass只会调用一次。

Additional Pass:计算其它影响该物体的逐像素光源(每个光源执行一次Pass),默认情况下不支持阴影;每个逐像素光源会执行一次Additional Pass。

对于前向渲染来说,一个Unity Shader通常会定义一个Base Pass和一个Additional Pass。

前向渲染可以使用的内置光照变量为:

表二

前向渲染可以使用的内置光照函数为:

表三

顶点照明渲染路径

以逐顶点的方式来计算光照。因为前面的前向渲染路径也可以计算这个,所以不是很有用。

延迟渲染路径

是一种更为古老的渲染方法。它也是两个Pass。第一个Pass不进行任何光照计算,只是计算那些片元是可见的,如果可见就把相关信息存储到G缓冲区中。在第一个Pass执行完毕后进行第二个Pass,利用G缓冲区的各个片元信息进行真正的光照计算。如果说前向渲染路径是一计算可见就进行光照计算,延迟渲染路径就是计算可见先存储到缓冲区中,最后统一进行光照计算。

综上可得,延迟渲染就2个Pass,与场景中包含的光源数目没有关系,可以很好的解决前向渲染路径中包含大量实时光源时性能下降的问题。而且延迟渲染路径中每个光源都可以按逐像素的方式处理。但是,它也有着缺点:

1.不支持真正的抗锯齿功能;

2.无法处理半透明物体;

3.显卡要支持MRT等要求。

延迟渲染路径可访问的内置变量和函数:

_LightColor:光源颜色(float4)

_LightMatrix0:从世界空间到光源空间的变换矩阵(float4x4)

Unity的光源类型与实践

光源属性有5个:位置方向颜色强度衰减

光源类型有四个(面光源暂不介绍):

平行光

作为太阳这种角色在场景中出现,可以照亮的范围没有限制(所以嗯调位置一点用没有),也没有衰减(光照强度不会随距离发生改变)

点光源

照亮空间时以光源为圆心的一个球体,光向所有方向延伸。球体的半径可以通过Range属性调整,光源颜色和强度可以在Light组件内调整。存在衰减,球心处光照强度最强,边界最弱,值为0.

聚光灯

照亮空间时以光源为圆锥体最顶端(即尖端),向下投射出的圆锥体,光向特定方向延伸。锥形区域半径由Range属性决定,张开角度由Spot Angle属性决定。存在衰减,锥形顶点处光照强度最强,边界处强度为0.

前向渲染路径实践

Shader "Custom/ForwardRendering"
{
    Properties
    {
        _Diffuse ("Diffuse",Color)=(1,1,1,1)
        _Specular ("Specular",Color)=(1,1,1,1)
        _Gloss ("Gloss", Range(8.0,256))=20
        _Atten ("Atten",Range(0,256))=1.0
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        //Base Pass
        Pass{
            Tags {"LightMode"="ForwardBase"}
            //设置Base Pass
            CGPROGRAM

            #pragma multi_compile_fwdbase
            //使用该指令可以保证我们使用光照衰减等光照变量可以被正确赋值(和Tag一样一定要写)。
            #pragma vertex vert
            #pragma fragment frag

            #include "Lighting.cginc"

            fixed4 _Diffuse;
            fixed4 _Specular;
            float _Gloss;
            fixed _Atten;
            //衰减值

            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 ambient=UNITY_LIGHTMODEL_AMBIENT.xyz;
                //如上文,环境光只需计算一次,所以后面的Additional Pass就不会计算
                fixed3 worldNormal=normalize(i.worldNormal);
                fixed3 worldLightDir=normalize(_WorldSpaceLightPos0.xyz);
                fixed3 diffuse=_LightColor0.rgb*_Diffuse.rgb*saturate(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);
                return fixed4(ambient+(diffuse+specular)*_Atten,1.0);
            }
            ENDCG
            }
        Pass{
            Tags {"LightMode"="ForwardAdd"}
            //Additional Pass的标签
            Blend One One
            //将该Pass光照计算结果和Base Pass结果混合
            //如果没有Blend命令,Additional Pass会把Base Pass直接覆盖
            CGPROGRAM
            #pragma multi_compile_fwdadd
            #pragma vertex vert
            #pragma fragment frag

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

            fixed4 _Diffuse;
            fixed4 _Specular;
            float _Gloss;
            fixed _Atten;

            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*saturate(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"
}

光线衰减纹理采样原因

光线衰减的计算往往涉及开根号、除法等计算量相对较大的操作。因此Unity选择了使用一张纹理作为查找表以在片元着色器中得到光源的衰减。纹理名称为**_LightTexture0**,坐标(0,0)是与光源位置重合的点,(1,1)是和光源距离最远的。

为了得到纹理定点的相关数值,我们要得到该点在光源空间的位置:

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

将世界空间的顶点坐标和转换矩阵相乘得到

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

这里将光源空间的顶点距离的平方进行采样(没有使用距离值是因为可以避免开方操作)。然后使用宏UNITY_ATTEN_CHANNEL得到衰减纹理中衰减值所在分量,以得到最终衰减值。

也可以使用公式自行计算光源衰减,比如(这是线性衰减):

float distance=length(_WorldSpaceLightPos0.xyz-i.worldPosition.xyz);

atten=1.0/distance;

Unity的阴影与实践

在实时渲染中,会使用Shadow Map技术,它会吧摄像头位置放在与光源重合的位置上,这样场景中该光源的阴影区域就是摄像头看不到的地方。

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

计算深度信息的方法可以通过把摄像头挂在光源的位置,然后按正常渲染流程调用Base Pass和Additional Pass进行,但是这样会浪费性能:我们只要深度信息就行了,而不需要光源的相关计算。最好的办法是额外使用一个Pass来计算,也就是Tags{"LightMode"="ShadowCaster"}的Pass。Unity会先把摄像机挂在光源的位置,然后找到LightMode是ShadowCaster的Pass。找不到就去Fallback指定的Shader里继续找(比如“Specular”)。

传统的阴影映射纹理的实现中,我们先在正常Pass中把顶点位置变换到光源空间,然后使用xy分量对阴影映射纹理进行采样,得到阴影映射纹理中该位置的深度信息。如果该深度值小于该顶点的深度值,就说明该点位于阴影中。但屏幕空间的阴影映射技术中,Unity会调用Tags{"LightMode"="ShadowCaster"}的Pass来得到可投射阴影的光源阴影映射纹理和摄像机的深度纹理。然后根据上述得到屏幕空间的阴影图。如果摄像机的深度图中表面深度大于转换到阴影映射纹理的深度值,说明该表面虽然可见,但是在该光源的阴影中。这样阴影图就包含了屏幕空间中所有有阴影的区域。如果我们想要一个物体接受来自其他物体的阴影,首先需要把表面坐标从模型空间变换到屏幕空间,然后使用该坐标对阴影图进行采样即可。

综上,一个物体接收其它物体的阴影和一个物体向其它物体投射阴影是两个过程:

一个物体接受其它物体的阴影:必须在Shader里对阴影映射纹理进行采样,把采样结果和最后光照结果相乘。

一个物体向其它物体投射阴影:把该物体加入到光源的阴影映射纹理计算中,从而让其它物体在对阴影映射纹理采样时得到该物体的相关信息。这个过程由“ShadowCaster”的Pass实现。

不透明物体的阴影实践

让物体投射阴影

在物体的Mesh Render组件内,我们可以通过设置Cast Shadows(是否投射)和Receive Shadows(是否接收)属性选择让它是否投射或接收阴影。其中Cast Shadows默认情况下会剔除物体背面计算光源的阴影映射纹理。但设置为Two Sided时可以允许对物体所有面计算阴影信息。

投射阴影可以直接使用上面的前向渲染路径Shader,接收阴影的代码在FallBack内,所以不需要我们写,Unity会自行在FallBack找到。

让物体接收阴影

但是由于上述代码没有对阴影进行任何处理,因此不会显示别的地方投射来的阴影。因此修改上述代码如下:

Shader "Custom/Shadow"
{
    Properties
    {
        _Diffuse ("Diffuse",Color)=(1,1,1,1)
        _Specular ("Specular",Color)=(1,1,1,1)
        _Gloss ("Gloss", Range(8.0,256))=20
        _Atten ("Atten",Range(0,256))=1.0
    }
    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 "AutoLight.cginc"
            //计算阴影的宏在这个文件内

            fixed4 _Diffuse;
            fixed4 _Specular;
            float _Gloss;
            fixed _Atten;

            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 ambient=UNITY_LIGHTMODEL_AMBIENT.xyz;
                fixed3 worldNormal=normalize(i.worldNormal);
                fixed3 worldLightDir=normalize(_WorldSpaceLightPos0.xyz);
                fixed3 diffuse=_LightColor0.rgb*_Diffuse.rgb*saturate(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 shadow=SHADOW_ATTENUATION(i);
                //计算阴影值
                return fixed4(ambient+(diffuse+specular)*_Atten,1.0);
            }
            ENDCG
            }
        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;
            fixed _Atten;

            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*saturate(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"
}

SHADOW_COORDS():声明一个用于对阴影纹理采样的坐标,参数需要是下一个可用的插值寄存器的索引值

**TRANSFER_SHADOW()**:用于在顶点着色器中计算上一步中声明的阴影纹理坐标

**SHADOW_ATTENUATION()**:计算阴影值

以上宏的计算都会使用上下文的变量,(比如v.vertex,a.pos等等)因此我们自定义的变量一定要和这些宏中使用的变量名相匹配(a2v顶点坐标变量一定是vertex,结构体a2v一定是v)

接下来只需要把shadow和漫反射以及高光反射的颜色相乘。

统一管理光照衰减和阴影

实际上,光照衰减和阴影对物体最终的渲染结果的影响本质上是相同的——我们都是把光照衰减因子和阴影值及光照结果相乘得到的渲染结果。因此,我们可以利用内置的UNITY_LIGHT_ATTENUATION来实现。

Shader "Custom/AttenuationAndShadowUseBuildInFunctions" 
{ 
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 "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; 
TRANSFER_SHADOW(o); 
return o; 
} 
fixed4 frag(v2f i):SV_Target{ 
fixed3 ambient=UNITY_LIGHTMODEL_AMBIENT.xyz; 
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(atten, i, i.worldPos); 
return fixed4((diffuse + specular) * atten, 1.0); 
} 
ENDCG 
} 
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; 
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(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(atten,i,i.worldPos)**:声明第一个参数,并将光照衰减和阴影值相乘后的结果存储到第一个参数中。

其中第二个参数是结构体v2f,这个参数会传递给SHADOW_ATTENUATION计算阴影值。

第三个参数是世界空间的坐标,用于计算光源空间下的坐标,再对光照衰减纹理采样。

UNITY_LIGHT_ATTENUATION的使用使得我们不需要在Base Pass里单独处理阴影,也不需要在Additional Pass内单独处理光照衰减。两个Pass的代码得以统一了。

透明度物体的阴影

透明度测试实践

对于不透明物体而言,使用FallBack里的VertexLit就可以完美得到正确的阴影,但对于透明物体则不然。之前提到透明物体的方法有透明度测试和透明度混合,这里使用透明度测试,在其基础上完成阴影的实现:

hader "Unity Shader Book/Chapter 9/AlphaTestWithShadow"
{
    Properties{
    _Color("Main 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 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);
            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(atten,i,i.worldPos);
            return fixed4(ambient+diffuse*atten,1.0);
        }
        ENDCG
    }
}
        Fallback "Transparent/Cutout/VertexLit"
}

如果我们在Fallback内直接使用VertexLit的话,阴影结果可能不正确(已经被剔除且不会显示的片元依旧出现了阴影),其原因是VertexLit内的Pass没有进行任何透明度测试的计算。我们可以自己写一个,也可以把Fallback改成Transparent/Cutout/VertexLit。这个Fallback要求我们提供一个名字叫做**_Cutoff**的属性作为透明度测试的阈值。

透明度混合实践

相比于透明度测试,透明度混合的阴影实现更为复杂。因为所有内置UnityShader都没有包含阴影投射的Pass。这意味着半透明物体不会对其它物体投射阴影,本身也不会接收阴影。复杂的问题在于透明度混合关闭了深度写入,从而影响了阴影的生成。总体来说,要产生正确的阴影,就要在每个光源空间下仍然严格按照从后往前的顺序进行渲染,这会使得阴影处理变得很复杂。因此,半透明物体不会有阴影生成。

Shader "Custom/AlphaBlendWithShadow"
{
    Properties
    {
        _Color ("Main 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"}

        Cull Front
        ZWrite Off
        Blend SrcAlpha OneMinusSrcAlpha
        CGPROGRAM

        #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=UnityObjectToClipPos(v.vertex);
            o.worldNormal=UnityObjectToWorldNormal(v.normal);
            o.worldPos=mul(unity_ObjectToWorld,v.vertex).xyz;
            o.uv=TRANSFORM_TEX(v.texcoord,_MainTex);
            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(atten,i,i.worldPos);
            return fixed4(ambient+diffuse,texColor.a*_AlphaScale);
        }
        ENDCG
    }
    Pass{
        Tags{"LightMode"="ForwardBase"}

        Cull Back
        ZWrite Off
        Blend SrcAlpha OneMinusSrcAlpha
        CGPROGRAM

        #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=UnityObjectToClipPos(v.vertex);
            o.worldNormal=UnityObjectToWorldNormal(v.normal);
            o.worldPos=mul(unity_ObjectToWorld,v.vertex).xyz;
            o.uv=TRANSFORM_TEX(v.texcoord,_MainTex);
            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(atten,i,i.worldPos);
            return fixed4(ambient+diffuse*atten,texColor.a*_AlphaScale);
        }
        ENDCG
    }
}
        Fallback "VertexLit"
}

然而我们可以通过把Fallback设置为VertexLit、DIffuse这些不透明物体使用的Unity Shader,将该物体作为不透明物体生成阴影。