unity shader入门精要_UnityShader入门精要-Uinty复杂光照

e8ce6c9292bf581b75a8b692e1163ad8.png

UnityShader入门精要-Uinty复杂光照

(此文为易于理解的演讲配合文档,设计到专业词汇和解释可能不当)

一 渲染路径

1.1 什么是渲染路径以及如何Unity(2018.4)设置

之前我们在shader中使用了一些系统帮我们定好的变量如,在计算高光时候需要用的光源方向(float4 WorldSpaceLightPos0),光源颜色( float4LightColor0)。

//光源方向
fixed3 wLightDir=normalize(_WorldSpaceLightPos0.xyz);
//高光=C(light) * m(Specular) * max(0,r.v)^m(gloss)
fixed3 specular=_LightColor0.xyz*_Specular.xyz*pow(saturate(dot(refDic,vDic)),_Gloss);

这些定义好的变量并不会任何时候都会正确的被赋值,那么系统是如何知道我们可能需要使用这些信息,从而预先准备好这些信息。就需要我们先定义好渲染路径,然后被系统识别,根据我们的选择对我们可能会用到的变量进行赋值。

默认设置:Editor -> ProjectSettting -> Graphice -> Tier Setting

5d97069c446acec79f4dbb2ffbea0adb.png

同时可用多个渲染路径,在需要的相机上设置即可

6da3a30f0dcca9c5d8d67780ad41af9c.png

1.2 几种渲染路径

以下从硬件要求从低到高,同时渲染质量由低到高进行介绍。

Legacy Vertex Lit:传统顶点照明

​ 顶点照明渲染路径是硬件要求最少,运算性能最高,效果最差的,不支持逐像素的效果,如阴影,法线映射等等。它也是前向渲染路径的一小部分。这个渲染路径在未来可能会被移除。下图是可使用的内置变量和内置方法。

244800ae62096c95be320767afa4c5c4.png

cd643dfebf8ea38043ec33d3387136dc.png

Forward : 前向渲染路径

​ 前向渲染:对于多个光源,最终结果是一步一步处理叠加出来的。如下所示((2个物体,1个平行光,2个逐像素点光源),一共需要进行2*(1+2)=6 次渲染,也就是有6个Pass计算,一个Pass可理解为一个步骤,即进行了6步叠加完成了一帧的渲染,每步更新颜色缓存和深度缓存。

beab2549ca9ac0994159b7c4b483c385.png

​ 这里提到了逐像素光源,其实是“按逐像素处理光照结果的光源”,按照处理光照的方式分类,有逐顶点处理,逐像素处理,球谐函数处理三种,逐像素对应光远谁设置的Important模式,逐顶点和球谐处理对应设置的Not Important

80de4c9fed7fe80362ff305487ca16c3.png

​ 标记为Import的光称为像素光,系统处理这种光会调用额外的渲染步骤Pass,Additional Pass,每个物体可能存在多个Pass,即一个物体可能被多个像素光源照亮。此外还有Base Pass一般每个物体只有一个Base Pass,一般由场景中最亮的平行光,所有顶点/球谐光的计算都在这一个Pass里,上述例子里前两个是Base Pass(两个物体,每个一个Base Pass),后面四个是Add Pass(每个物体被两盏像素光灯照亮)。

​ 被手动标记为Import的光越多,性能就越差,Unity的方案如下

​ A.将所有光源标记为Auto,并设置最大像素光数量,默认Pixel Light Count=4

​ B.最亮的那盏平行光一定是像素光,其他标记了important的光源在数量不超过设置里面Pixel Light Count的情况下是像素光,否则是顶点光。

​ C.对于第一盏最亮的平行光在第一个Bass Pass 计算,并计算阴影,然后选择4盏顶点光在也在第一个pass同时计算,对于其他的像素光每个多加一个额外的add pass,对于再剩下的那些顶点光则按照球谐光照的方式在一个bass pass计算。这里面可以认为超过了一定限制的光最后都变成了球谐光照 。

在Shader里标记Base Pass和Add Pass如下所示,以及两种Pass所能使用内置变量如下:

Shader "xxxx"
{
    SubShader
    {
      Pass
      {
        //最亮的平行光,以及最亮的4个顶点光[Not Important标记的光]应当在这里处理,剩余的光应以球谐函数处理
        Tags{"LightMode"="ForwardBase"}
        ......
      }
      Pass
      {
        //像素光[标记为Iportant的光]在这里被处理
        Tags{"LightMode"="ForwardAdd"}
        ......
      }
    }
}

522da1c70b1f591dd3e34931a6bb351a.png

d2c9d776187236b064db69bb59bea581.png

Deferred:延迟渲染

前向渲染的Pass数量跟光源数量相关,延迟渲染的Pass一般为两个:

第一个Pass:Geometry阶段,也有简称为G-Buffer阶段,不进行光照计算,而是将本帧所有的几何信息光栅化到G-buffer备用。包括位置,法线,贴图等。

这里也要求了显卡需要很好的支持MRT(Multi Render Target),可理解为需要多个渲染结果容器

bee410b832deb46c8b936828e6f353fd.png

第二个Pass:进行真正的光照计算,以G-buffer作为输入进行逐像素的光照计算,最后混合得到最终颜色,也有实现将混合步骤独立出来作为第三个Pass。

445c42e760cb79b8f1d66a3fde49c42e.png

最终结果

Unity支持遗留的延迟渲染(Legacy Deferred)也支持现代的延迟渲染(Deferred),两种差别很小,本文只讨论现代的版本,可用内置变量如下:

1e918ad89601eb407df9b308f13b7fd3.png

延迟渲染主要优点有:

1 光照的开销与场景复杂度无关。

2 Shader可以访问深度和其他像素信息。

3 每个象素对每个光源仅运行一次。也就是说,那些被遮挡的像素是不会被光照计算到的。

主要缺点有:

1 硬件要求高

2 较高的显存带宽占用

3 反锯齿的支持低(使用MSAA对GPU显存开销极大,如8倍MSAA,那就需要 G-buffer张数x原RT大小x8倍 这么多的显存)

4 对Alpha Blend支持较差

二 光源类型

Unity支持四种光源,分别是平行光,点光源,聚光灯,面光源(仅烘焙时有效,本文不予过多论述)

3b7a379f655f6df9a5a87057eb7583d2.png

平行光:位置无意义,只有方向,无范围,无衰减,常作为太阳

点光源:有位置,有范围(球形),有衰减

聚光灯:有位置,有范围(锥形),有衰减

面光源:不可实时运算,通常作为室内顶灯,室内基调氛围灯

三 光照衰减

3.1 衰减的实际意义:

如上面的示意图所示,除了平行光,点光源在球星范围内离中心越远,光照强度越小;在聚光灯的锥形范围内,越靠近边缘,光照强度越小。这个相对位置与光照强度的关系,叫做光照衰减因子。

在真实世界中可以这么理解,光照对物体的照亮程度,与照亮物体表面的光线密度有关,可以理解为即表面接收的光越多,我们看到的就越亮。光照强度衰减。也就是说,不能误认为“一束光线随着传播得越远,能量越低”,而是指在随着距离光源越远,单位面积内的光线密度越低,接收到的光束越少。

下图展示了在点光源或者聚光灯范围内,不同地方的光强(在计算机模拟中,不能在非常远的地方还有光能照亮,Unity 规定了这种光源的范围,范围边缘的光强为0,中心为1),以及在平行光照射下不同位置的光强:

5a4770eaec51281793bdc1f1847d18ea.png

事实上:平行光示模拟太阳光,太阳作为光源,发散的光也是球形发散的,但是由于太阳体积远远大于地球,太阳光传播到地球上,在小范围内观察,光线之间已经接近于平行,因此在一定范围内,我们把平行光当作各处密度相等,即各处没有衰减,也可以说衰减因此为常量1。但是如果处理光照的单位是以星球等超大体积单位了,太阳光需以球形光源看待。

fcfffe25c21d3c7b81f7505d6b8cb6c8.png

0708c8c5511dd87218573690794efff6.png

3.2 Unity中的光照衰减:

最终结果=原始结果*衰减因子(Attenuation,通常shader命名为atten)

平行光:atten=1.0

点光源和聚光灯计算atten:按照常规,我们需要利用到点光源的范围参数,最大强度值,聚光灯的锥形范围的角度,最远距离等灯光的参数,来计算空间内某点的衰减值,一种点光源衰减值计算方式如下,聚光灯计算更加复杂,Unity官方并未给出相关文档,本文也还未清楚底层的计算公式故不展开了。 例如

但是在Unity中,Unity采用张贴图来存储光源各处的衰减值,这张贴图是提前根据这些参数生成的,来防止每时每刻去做这些复杂计算,我们只需要知道某点的世界位置,并用这盏灯的转换矩阵转换位置(可以理解成当前处理的点(或像素)的空间位置相对于光源在哪里),再使用这个相对位置坐标去对应光源周围各处的衰减值(采样衰减贴图)。

计算点光源衰减:

//获得相对点光源的位置,即光源空间下的位置
float3 lightCoord = mul(unity_WorldToLight, float4(i.worldPos, 1)).xyz;
//lightCoord.lightCoord=|lightCoord|*|lightCoord|*cos(0)=|lightCorrd|^2=距离的平方
//根据(u,v)二维坐标对贴图采样,u=v=距离的平方, 取平方是为了防止开根号计算,贴图里存的也是距离的平方对应的atten
fixed atten = tex2D(_LightTexture0, dot(lightCoord, lightCoord).rr).UNITY_ATTEN_CHANNEL;

过程示意图

cf5654afc02efd21c4ed8187cf980048.png
示意图,数据不正确

计算聚光灯衰减:

聚光灯的衰减 背面衰减为0,正面由两个方向(距离中轴线方向和中轴线延伸方向)合成,即 $

199e341efed81e287f08e16a53e03606.png
//获得相对点光源的位置,即光源空间下的位置
float4 lightCoord = mul(unity_WorldToLight, float4(i.worldPos, 1));
//lightCoord.z 表示在聚光灯前方还是后方,lightCoord.z>0=1=可能被照亮,lightCoord.z<=0=0=后方,即不被照亮
float isFront=lightCoord.z > 0;
//lightCoord.xy / lightCoord.w表示偏移聚光灯中轴线多少,值域是[-0.5,0.5]到[0.5,0.5],故+0.5使其在一般uv值域即[0,0]到[1,1],采样衰减贴图_LightTexture0得到截面方向的衰减
float AttenXY=tex2D(_LightTexture0, lightCoord.xy / lightCoord.w + 0.5).w;
//lightCoord.lightCoord=|lightCoord|*|lightCoord|*cos(0)=|lightCorrd|^2=距离的平方,用距离平方采样衰减贴图_LightTextureB0得到中轴方向衰减
float AttZ=tex2D(_LightTextureB0, dot(lightCoord, lightCoord).rr).UNITY_ATTEN_CHANNEL;
//最后混合
fixed atten = isfront * AttenXY * AttZ;

过程示意图

317f9c990cf5e8fffaa104d7f38b77d3.png
示意图,数据不正确

四 Unity前向渲染Shader

4.1 Shader结构

Unity前向渲染Shader中处理前三种灯光,首先回顾一下前向渲染shader一般结构,最后会给出实例;

Shader "Forward Rendering"
{
    SubShader
    {
      Pass
      {
        //这个pass处理最亮的平行光,4个最亮的顶点[Not Important]光(点光源或者聚光灯),以及其他标记为[Not Important]的光(以球谐函数处理)
        Tags{"LightMode"="ForwardBase"}
        //保证在里使用的内置变量等被正确赋值
        #pragma multi_compile_fwdbase

        //这里可使用的各种类型的光是以自定义的方式处理的,即使说顶点光[Not Important]也可以在片元着色器中逐像素进行处理
        //顶点处理
        ......
        //片元处理
        ......
      }
      Pass
      {
        //这个Pass每有一个像素[Important]灯照亮这个物体,这个Pass调用一次
        Tags{"LightMode"="ForwardAdd"}
        //结果和之前算出来的结果叠加
        Blend One One
        //保证在里使用的内置变量等被正确赋值
              #pragma multi_compile_fwdadd
        //这里可使用的各种类型的光也是以自定义的方式处理的,即使说像素光[Important]也可以在顶点着色器着色器中处理,甚至不渲染
        //顶点处理
        ......
        //片元处理
        ......
      }
    }
}

4.2 实例

9471b032d1dc32d0cddea4e8e7ef3d02.png

渲染过程:两个平行光 以及点光源在Base Pass里边渲染,聚光灯在Add Pass种渲染并叠加到上一次的结果中

f93ff00d5e518495fdcb7112961bdfcc.png
//简略示意代码
Shader "Forward Rendering" {
    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 {
            // 计算最亮的平行光,4个最亮的顶点光,以及其他没被标记为Important的光源(以球谐函数方式处理)
            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;
                float3 vertexLight:TEXCOORD2;
            };

            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.vertexLight=float3(0,0,0);
                #ifdef LIGHTMAP_OFF

                #ifdef VERTEXLIGHT_ON
                //逐顶点计算的4个最亮的顶点光
                float3 vertexLight = Shade4PointLights (
                    unity_4LightPosX0, unity_4LightPosY0, unity_4LightPosZ0,
                    unity_LightColor[0].rgb, unity_LightColor[1].rgb, unity_LightColor[2].rgb, unity_LightColor[3].rgb,
                    unity_4LightAtten0, o.worldPos, o.worldNormal);
                o.vertexLight += vertexLight;
                #endif
                //其他所有光按照球谐函数处理
                o.vertexLight += ShadeSHPerVertex (o.worldNormal,UNITY_LIGHTMODEL_AMBIENT.xyz );

                #endif
                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 * 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;
                fixed3 DicLightColor=(diffuse + specular) * atten;

                //逐像素计算的球谐光(这里结果不对,待研究)
                //fixed3 SHLightPerPix=ShadeSHPerPixel(worldNormal,ambient,i.worldPos);
                return fixed4(ambient + DicLightColor+/*SHLightPerPix+*/i.vertexLight, 1.0);
            }
            ENDCG
        }

        Pass {
            // 这个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"
}

五 阴影

5.1 Unity阴影实现

我们都知道光线被不透明物体遮挡(不考虑光线多次反射),未被光照到的地方形成阴影

1acb5fa471a02ee1a729e20a2dab0401.png

在Unity中,使用的是一种叫做ShadowMap(阴影贴图)的技术,即使说如果光源位置有一台摄像机,那么摄像机看不到的地方就是阴影区域。在光源处产生周围环境的阴影贴图,即ShadowMap,这张贴图记录的是距离光源最近的表面(最近的表面的深度信息),也就说其实这张阴影贴图也是深度图,越白也就表示距离光源越近,越黑就越远。

方向光ShadowMap

平行光和聚光灯(有确切方向):生成一张原始的ShadowMap,并最后结合玩家摄像机视角的深度图,生成屏幕空间的ShadowMap(Tex2D),最后只要用屏幕位置采样这张图得到计算位置的阴影值,与计算机结果相乘即可,如下所示:

d46e8b6b0d84a734c644f8419501c60f.png

点光源ShadowMap

而点光源,由于光是朝各个方向的,Unity分别在点光源的上,下,左,右,前,后,6个方向生成原始ShadowMap,最后合成的ShadowMap是Cube类型,可以理解为在点光源观察得到的全景图,利用空间位置采样这张图同样得到阴影值,与结果相乘,如下所示:

bd07cae591db874f44fd3816dacbf6e4.png

5.2 Unity应用ShdowMap

那么在Unity实现阴影,有两个部分:

1.标记这个物体,使之能被记录到ShdowMap中去,即这个物体可以投射阴影

2.这个物体可以接受阴影,也就是能显示其他物体产生的阴影,即须比在物体最后的着色阶段,还必须乘以采样ShadowMap得到的阴影值

要实现第一部分,首先可以在BasePass或者AppPase中去更新自己的信息更新ShdowMap,但是这两个Pass一般会进行大量的计算,因此Unity规定了一个单独的Pass来进行投射阴影,标记为ShadowCaster,如果Shader里边没有定义这个Pass,那么会从FallBack“Specluar”中找,直到找到或者最终没有这个Pass,因此我们在Shader中一般只要设置了合适的FallBack,阴影投射就已经在里边了。

Shader "Forward Rendering"
{
    SubShader
    {
      Pass
      {
        Tags{"LightMode"="ShadowCaster"}
      }
      Pass
      {
        Tags{"LightMode"="ForwardBase"}
      }
      Pass
      {
        Tags{"LightMode"="ForwardAdd"}
      }
    }
  FallBack"Specular"
}

Unity会在进行前向渲染之前,执行所有的ShadowCaster,来生成ShowMap,然后在前向渲染中便能使用这张贴图,下图展示了引擎渲染的顺序

4b17b1c6177824be956c1463c86d848a.png

在ShadowCaster Pass中,如何去投射阴影并更新ShadowMap,Unity已用宏封装了这部分计算,Unity内置Shader如下,

// Pass to render object as a shadow caster
    Pass {
        Name "ShadowCaster"
        Tags { "LightMode" = "ShadowCaster" }
        CGPROGRAM
        #pragma vertex vert
        #pragma fragment frag
        #pragma target 2.0
        #pragma multi_compile_shadowcaster
        #pragma multi_compile_instancing // allow instanced shadow pass for most of the shaders
        #include "UnityCG.cginc"
        struct v2f {
            //实际上定义了一个三维向量容器
            V2F_SHADOW_CASTER;
            //存贮“顶点是否位于视域中”,来判断这个顶点是否输出到片段着色器,GPU Instancing所需
            UNITY_VERTEX_OUTPUT_STEREO
        };
        v2f vert( appdata_base v ){
            v2f o;
            //为顶点实例化一个ID,GPU Instancing所需
            UNITY_SETUP_INSTANCE_ID(v);
            //初始化GPU Instancing相关的量
            UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o);
            //阴影图是屏幕空间下的,先对表面坐标从模型空间变换到屏幕空间
            TRANSFER_SHADOW_CASTER_NORMALOFFSET(o)
            return o;
        }
        float4 frag( v2f i ) : SV_Target
        {
            //根据光源类型,写入ShaowMap(Tex2D或Cube)
            SHADOW_CASTER_FRAGMENT(i)
        }
        ENDCG
        }

实现第二部分,即采样阴影贴图,叠加颜色值,也是使用Unity封装的宏(方法进行处理即可),添加这部分宏在BasePass(只执行一次,故添加在这)即可

Shader "Unity Shaders Book/Chapter 9/Shadow" {
    Properties {
    }
    SubShader {
        Tags { "RenderType"="Opaque" }

        Pass {
            Tags { "LightMode"="ForwardBase" }
            CGPROGRAM
            #pragma multi_compile_fwdbase   
            #pragma vertex vert
            #pragma fragment frag
            #include "Lighting.cginc"
      //阴影相关的宏在这里引入
            #include "AutoLight.cginc"
            ......
            struct a2v {
        ......
            };
            struct v2f {
        ......
        //声明一个对阴影纹理采样的坐标
                SHADOW_COORDS(2)
            };
            v2f vert(a2v v) {
                v2f o;
        ......
                //计算上一步声明的阴影纹理坐标
                TRANSFER_SHADOW(o);
                return o;
            }
            fixed4 frag(v2f i) : SV_Target {
        ......
        //计算阴影值
                fixed shadow = SHADOW_ATTENUATION(i);
                return fixed4(ambient + (diffuse + specular) * atten * shadow, 1.0);
            }
            ENDCG
        }

        Pass {
            Tags { "LightMode"="ForwardAdd" }
      ......
            }
            ENDCG
        }
    }
    FallBack "Specular"
}

到这里我们发现,最后得到的阴影值和上一节得到的灯光衰减值,其实都是对最后结果的调整,而且都是Unity内置宏计算的,我们一般不能干预,那Unity其实已经有方法帮我们合并这两部分计算,如下:

1.同样先声明SHADOW_COORDS(2)

2.顶点着色器中计算上一步声明的阴影纹理坐标TRANSFER_SHADOW(o)

3.只是在片元中替换原来的SHADOW_ATTENUATION(i)为UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos)

// 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);

但是注意,由于使用宏,可以理解为是直接替换了部分代码,因此有一些特殊变量必须统一,如a2f结构体中顶点名为vertex,v2f结构体中顶点位置必须为pos,顶点着色器输入v2f结构体必须命名为v。

5.3 透明物体的阴影

到这里所有不透明物体的阴影处理已全部完成,那么透明物体呢。

首先透明情况有两种,透明度测试,和透明度混合

透明度测试

透明度测试只是将alpha值小于某个值的片元直接丢弃,其他的还是作为不透明物体的,即并不是真正的透明, 正常的ShadowCaster是投射了模型所有片元,因此投射出的阴影如实体一样,那么只要在ShadowCaster Pass中同样剔除这些片元即可,Unity也提供包含剔除的Fallback,注意MeshRender的CasterShadow设置为TwoSide

ec86661e7a7dc66b6e5112d8e85de95c.png
//FallBack "VertexLit"
FallBack "Transparent/Cutout/VertexLit"

48bbb757cc0e14f1d7ceedb5c184003a.png

透明度混合

透明度混合是真正的透明,以当前计算的透明度和之前渲染出来的图片进行混合,来得到透明效果。

由于半透明的Shader需要关闭深度写入,Unity处理的半透明物体不会投射阴影,也不会接受其他物体产生的阴影如下图所示

a62f819472b2c1c5954f40073a6d3545.png

但是我们可以强制让它产生阴影

1.设置Fallback为不透明物体使用的Fallback(不合理)

//FallBack "Transparent/VertexLit"
FallBack"Diffuse"

2.设置MeshRender的ReceiveShow为接受

400128f219aa616433a0249bf32784f7.png

103ea6184c8944340489a7d75096f577.png

参考文档:

《Unityshader入门精要》

unity里面聚光灯(SPOT)光照计算学习!_qq_28075869的博客-CSDN博客

unity shader base pass and additional pass

【Unity】Standard Shader实现分析

球谐光照(spherical harmonic lighting)解析

Deferred Shading(延迟渲染)

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值