15.不透明物体投射阴影

15.阴影-不透明物体阴影-让不透明物体投射阴影


15.1 知识点

感受 Fallback 的作用

多光源shader不能投射阴影和接受阴影

  1. 复制之前编写的多种光源综合实现的 ShaderLesson11_ForwardLighting.shader。命名为 Lesson15_ForwardLighting.shader
  2. 新建一个材质球,将其 Shader 设置为 Lesson15_ForwardLighting.shader
  3. 将该材质球赋值给较大的立方体,发现该立方体:
    • 不再投射阴影。
    • 不再接受阴影。


原因分析

  1. 不投射阴影的原因

    • 该 Shader 中没有 LightMode 为 ShaderCaster 的 Pass,无法参与光源的阴影映射纹理的计算。
  2. 不接受阴影的原因

    • 该 Shader 并没有对阴影映射相关纹理进行采样,也没有进行阴影相关颜色运算。

解决方案 给多光源 Shader 添加 Fallback “Specular”

  • Unity 会寻找 LightMode 为 ShaderCaster 的 Pass 来处理。如果该 Shader 没有该 Pass,会在其 Fallback 指定的 Shader 中寻找,直到找到为止。
  • 在该 Shader 最后加上 Fallback "Specular",即可让该立方体投射阴影。
Shader "Unlit/Lesson15_ForwardLighting"
{
    Properties
    {
        //...
    }
    SubShader
    {
        //...
    }
    
    //找不到LightMode为ShaderCaster的Pass 使用Specular的shader
    Fallback "Specular"
}

让物体投射阴影

主要步骤

让物体投射阴影

主要步骤

  1. 实现 LightMode(灯光模式)为 ShadowCaster(阴影投射)的 Pass(渲染通道),这样物体才能参与到光源的阴影映射纹理计算中。
  2. 使用以下内容实现阴影效果:
    • 编译指令
      #pragma multi_compile_shadowcaster
      告诉 Unity 编译器生成多个着色器变体,用于支持不同类型的阴影(如 SM、SSSM 等)。确保着色器在所有可能的阴影投射模式下正确渲染。
    • 内置文件
      #include "UnityCG.cginc"
      包含关键的阴影计算相关宏。
    • 三个关键宏
      1. V2F_SHADOW_CASTER
        • 用于定义顶点到片元着色器的阴影投射结构体数据。
        • 提供标准成员变量,用于在阴影投射路径中传递顶点数据到片元着色器。
      2. TRANSFER_SHADOW_CASTER_NORMALOFFSET
        • 用于在顶点着色器中计算并传递阴影投射所需变量:
          1. 将对象空间的顶点位置转换为裁剪空间的位置。
          2. 考虑法线偏移,减轻阴影失真问题(特别是自阴影)。
          3. 传递顶点的投影空间位置,用于后续的阴影计算。
      3. SHADOW_CASTER_FRAGMENT
        • 在片元着色器中使用,将深度值写入到阴影映射纹理中。
  3. 利用这些内容在Shader中实现代码

复制Lesson15_ForwardLighting.shader,改名叫。注释掉Fallback “Specular”,我们自己手动实现让物体投射阴影

Shader "Unlit/Lesson15_ForwardLighting"
{
    Properties
    {
        //...
    }
    SubShader
    {
        //...
    }
    
//    //找不到LightMode为ShaderCaster的Pass 使用Specular的shader
//    Fallback "Specular"
}

实现 LightMode(灯光模式) 为 ShadowCaster(阴影投射) 的 Pass(渲染通道),设置一些预编译指令

//阴影投影 Pass 主要是用来计算阴影映射纹理
Pass
{
    Tags
    {
        "LightMode" = "ShadowCaster"
    }
    CGPROGRAM
    #pragma vertex vert
    #pragma fragment frag

    ENDCG
}

投射阴影Pass中添加multi_compile_shadowcaster编译指令和引用UnityCG内置文件

//  该编译指令时告诉Unity编译器生成多个着色器变体
//  用于支持不同类型的阴影(SM,SSSM等等)
//  可以确保着色器能够在所有可能的阴影投射模式下正确渲染
//  比如有些低端移动设备不支持
#pragma multi_compile_shadowcaster
//  其中包含了关键的阴影计算相关的宏
#include "UnityCG.cginc"

声明v2f结构体,结构体内声明一个顶点到片元着色器阴影投射结构体数据宏,这个宏定义了一些标准的成员变量,相当于不用我们自己声明阴影效果的变量了

struct v2f
{
    //顶点到片元着色器阴影投射结构体数据宏
    //这个宏定义了一些标准的成员变量
    //这些变量用于在阴影投射路径中传递顶点数据到片元着色器
    //我们主要在结构体中使用
    V2F_SHADOW_CASTER;
};

顶点函数使用转移阴影投射器法线偏移宏

v2f vert(appdata_base v)
{
    v2f v2f;
    
    //转移阴影投射器法线偏移宏
    //用于在顶点着色器中计算和传递阴影投射所需的变量
    //主要做了
    //2-2-1.将对象空间的顶点位置转换为裁剪空间的位置
    //2-2-2.考虑法线偏移,以减轻阴影失真问题,尤其是在处理自阴影时
    //2-2-3.传递顶点的投影空间位置(光源视角下的坐标),用于后续的阴影计算
    //我们主要在顶点着色器中使用
    
    //这个宏会自动使用我们传进来的v 注意v这个参数不可以改名 否则识别不了
    TRANSFER_SHADOW_CASTER_NORMALOFFSET(v2f);
    
    return v2f;
}

片元函数使用阴影投射片元宏

float4 frag(v2f v2f):SV_Target
{
    //阴影投射片元宏
    //将深度值写入到阴影映射纹理中
    //我们主要在片元着色器中使用
    SHADOW_CASTER_FRAGMENT(v2f);
}

创建材质赋值shader并给立方体,可以看到我们自己写的Pass能让立方题投射阴影了

关于投射阴影相关代码的建议

  • 投射阴影相关的代码较为通用。
  • 建议直接通过 Fallback 调用 Unity 默认 Shader 中的相关代码,而不必自己实现这些 Shader 代码。

15.2 知识点代码

Lesson15_阴影_不透明物体阴影_让不透明物体投射阴影

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Lesson15_阴影_不透明物体阴影_让不透明物体投射阴影 : MonoBehaviour
{
    void Start()
    {
        #region 知识点一 感受Fallback的作用

        //我们复制之前编写的多种光源综合实现Shader Lesson11_ForwardLighting.shader
        //命名为Lesson15_ForwardLighting.shader
        //我们新建一个材质球 
        //将其的Shader设置为Lesson15_ForwardLighting.shader

        //并将该材质球赋值给较大的立方体使用
        //我们会发现该立方体不再投射阴影也不再接受阴影
        //1.不投射阴影的原因
        //  该Shader中没有LightMode为ShaderCaster的Pass,无法参与光源的阴影映射纹理的计算
        //2.不接收阴影的原因
        //  该Shader并没有对阴影映射相关纹理进行采样,没有进行阴影相关颜色运算

        //我们之前学习理论知识时提到过
        //Unity会寻找LightMode为ShaderCaster的Pass来进行处理,如果该Shader没有该Pass
        //会在它FallBack指定的Shader中寻找,直到找到为止

        //我们现在 在该Shader 最后加上FallBack "Specular"
        //便可以让该立方体投射阴影

        #endregion

        #region 知识点二 让物体投射阴影

        //物体向其它物体投射阴影的关键点是:
        //1. 需要实现 LightMode(灯光模式) 为 ShadowCaster(阴影投射) 的 Pass(渲染通道)
        //   这样该物体才能参与到光源的阴影映射纹理计算中

        //2. 一个编译指令,一个内置文件,三个关键宏
        //  编译指令:
        //  #pragma multi_compile_shadowcaster
        //  该编译指令时告诉Unity编译器生成多个着色器变体
        //  用于支持不同类型的阴影(SM,SSSM等等)
        //  可以确保着色器能够在所有可能的阴影投射模式下正确渲染

        //  内置文件:
        //  #include "UnityCG.cginc"
        //  其中包含了关键的阴影计算相关的宏

        //  三个关键宏:
        //  2-1.V2F_SHADOW_CASTER
        //    顶点到片元着色器阴影投射结构体数据宏
        //    这个宏定义了一些标准的成员变量
        //    这些变量用于在阴影投射路径中传递顶点数据到片元着色器
        //    我们主要在结构体中使用
        //  2-2.TRANSFER_SHADOW_CASTER_NORMALOFFSET
        //    转移阴影投射器法线偏移宏
        //    用于在顶点着色器中计算和传递阴影投射所需的变量
        //    主要做了
        //    2-2-1.将对象空间的顶点位置转换为裁剪空间的位置
        //    2-2-2.考虑法线偏移,以减轻阴影失真问题,尤其是在处理自阴影时
        //    2-2-3.传递顶点的投影空间位置,用于后续的阴影计算
        //    我们主要在顶点着色器中使用
        //  2-3.SHADOW_CASTER_FRAGMENT
        //    阴影投射片元宏
        //    将深度值写入到阴影映射纹理中
        //    我们主要在片元着色器中使用

        //3.利用这些内容在Shader中实现代码

        #endregion

        #region 知识点三 关于投射阴影相关的代码的建议

        //由于投射阴影相关的代码较为通用
        //因此建议大家不用自己去实现相关Shader代码
        //直接通过FallBack调用Unity中默认Shader中的相关代码即可

        #endregion
    }
}

Lesson15_OpaqueObjectCastShadows

Shader "Unlit/Lesson15_OpaqueObjectCastShadows"
{
    Properties
    {
        //材质的漫反射光照颜色
        _MainColor("MainColor", Color) = (1,1,1,1)
        //高光反射颜色
        _SpecularColor("SpecularColor", Color) = (1,1,1,1)
        //光泽度
        _SpecularNum("SpecularNum", Range(0, 20)) = 0.5
    }
    SubShader
    {
        //Bass Pass 基础渲染通道
        Pass
        {
            //设置我们的光照模式 ForwardBase这种向前渲染模式 主要是用来处理 不透明物体的 光照渲染的
            Tags
            {
                "LightMode"="ForwardBase"
            }

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            //用于帮助我们编译所有变体 并且保证衰减相关光照变量能够正确赋值到对应的内置变量中
            #pragma multi_compile_fwdbase

            //引用对应的内置文件 
            //主要是为了之后 的 比如内置结构体使用,内置变量使用
            #include "UnityCG.cginc"
            #include "Lighting.cginc"


            //材质的漫反射颜色
            fixed4 _MainColor;
            //对应属性当中的颜色和光泽度
            fixed4 _SpecularColor;
            float _SpecularNum;

            //顶点着色器传递给片元着色器的内容
            struct v2f
            {
                //裁剪空间的位置
                float4 pos:SV_POSITION;
                //基于世界坐标系下的顶点位置
                float3 wPos:TEXCOORD0;
                //基于世界坐标系下的法线
                float3 wNormal:NORMAL;
            };


            //得到兰伯特光照模型计算的颜色 (逐片元)
            fixed3 getLambertColor(in float3 wNormal)
            {
                //_WorldSpaceLightPos0.xyz 世界坐标系下光源方向 _WorldSpaceLightPos0是四维向量 只需要xyz即可
                //normalize 把向量归一化
                //lightDir 得到光源单位向量
                float3 lightDir = normalize(_WorldSpaceLightPos0.xyz);

                //_LightColor0光 照颜色
                //.rgb 代表只使用颜色进行计算 透明度不考虑 
                //color 计算出了兰伯特光照的漫反射颜色
                fixed3 color = _LightColor0.rgb * _MainColor.rgb * max(0, dot(wNormal, lightDir));

                return color;
            }

            //得到Blinn Phong式高光反射模型计算的颜色(逐片元)
            fixed3 getSpecularColor(in float3 wPos, in float3 wNormal)
            {
                //1.视角单位向量
                //用观察者的位置(摄像机的位置)减去世界空间下顶点坐标 得到的就是视角方向 并且进行单位化
                float3 viewDir = normalize(_WorldSpaceCameraPos.xyz - wPos);

                //2.光的反射单位向量
                //光的方向
                float3 lightDir = normalize(_WorldSpaceLightPos0.xyz);

                //半角方向向量
                float3 halfA = normalize(viewDir + lightDir);

                //color = 光源颜色 * 材质高光反射颜色 * pow( max(0, dot(视角单位向量, 光的反射单位向量)), 光泽度 )
                fixed3 color = _LightColor0.rgb * _SpecularColor.rgb * pow(max(0, dot(wNormal, halfA)), _SpecularNum);

                return color;
            }

            v2f vert(appdata_base appdata_base)
            {
                v2f v2f;

                //转换模型空间下的顶点到裁剪空间中
                v2f.pos = UnityObjectToClipPos(appdata_base.vertex);

                //转换模型空间下的法线到世界空间下
                v2f.wNormal = UnityObjectToWorldNormal(appdata_base.normal);

                //转换模型空间下的顶点到世界空间
                v2f.wPos = mul(unity_ObjectToWorld, appdata_base.vertex).xyz;

                return v2f;
            }

            fixed4 frag(v2f v2f) : SV_Target
            {
                //计算兰伯特光照颜色
                fixed3 lambertColor = getLambertColor(v2f.wNormal);

                //计算BlinnPhong式高光反射颜色
                fixed3 specularColor = getSpecularColor(v2f.wPos, v2f.wNormal);

                //衰减值
                //因为我们这是基础Pass 一般是处理场景中最亮的平行光 所以可以认为不存在衰减值的 衰减值默认是1
                //但是之后附加Pass的衰减值就会变化了
                fixed atten = 1;

                //物体表面光照颜色 = 环境光颜色 + 兰伯特光照模型所得颜色 + 布林Phong式高光反射光照模型所得颜色
                //衰减值 会和 漫反射颜色 + 高光反射颜色 后 再进行乘法运算   
                fixed3 blinnPhongColor = UNITY_LIGHTMODEL_AMBIENT.rgb + (lambertColor + specularColor) * atten;

                return fixed4(blinnPhongColor.rgb, 1);
            }
            ENDCG
        }


        //Additional Pass 附加渲染通道
        Pass
        {
            //设置我们的光照模式 ForwardAdd这种向前渲染模式 主要是用来处理 附加光照渲染的
            Tags
            {
                "LightMode"="ForwardAdd"
            }

            //线性减淡的效果 进行 光照颜色混合
            Blend One One


            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            //用于帮助我们编译所有变体 并且保证衰减相关光照变量能够正确赋值到对应的内置变量中
            #pragma multi_compile_fwdadd

            //引用对应的内置文件 
            //主要是为了之后 的 比如内置结构体使用,内置变量使用
            #include "UnityCG.cginc"
            #include "Lighting.cginc"
            #include "AutoLight.cginc"


            //材质的漫反射颜色
            fixed4 _MainColor;
            //对应属性当中的颜色和光泽度
            fixed4 _SpecularColor;
            float _SpecularNum;

            //顶点着色器传递给片元着色器的内容
            struct v2f
            {
                //裁剪空间的位置
                float4 pos:SV_POSITION;
                //基于世界坐标系下的顶点位置
                float3 wPos:TEXCOORD0;
                //基于世界坐标系下的法线
                float3 wNormal:NORMAL;
            };


            v2f vert(appdata_base appdata_base)
            {
                v2f v2f;

                //转换模型空间下的顶点到裁剪空间中
                v2f.pos = UnityObjectToClipPos(appdata_base.vertex);

                //转换模型空间下的法线到世界空间下
                v2f.wNormal = UnityObjectToWorldNormal(appdata_base.normal);

                //转换模型空间下的顶点到世界空间
                v2f.wPos = mul(unity_ObjectToWorld, appdata_base.vertex).xyz;

                return v2f;
            }

            fixed4 frag(v2f v2f) : SV_Target
            {
                //兰伯特漫反射
                //公式:漫反射颜色 = 光颜色 * 属性中颜色 * max(0, dot(世界坐标系下的法线, 世界坐标系下的光方向));

                //世界标准法线
                fixed3 worldNormal = normalize(v2f.wNormal);

                #if defined(_DIRECTIONAL_LIGHT)
                    //平行光 光的方向 其实就是它的位置
                    fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
                #else
                //点光源和聚光灯 光的方向 是 光的位置 - 顶点位置
                fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz - v2f.wPos);
                #endif

                // 漫反射颜色 = 光颜色 * 属性中颜色 * max(0, dot(世界坐标系下的法线, 世界坐标系下的光方向));
                fixed3 diffuse = _LightColor0.rgb * _MainColor.rgb * max(0, dot(worldNormal, worldLightDir));


                //BlinnPhong高光反射
                //公式:高光颜色 = 光颜色 * 属性中的高光颜色 * pow(max(0, dot(世界坐标系法线, 世界坐标系半角向量)), 光泽度);

                //视角方向 摄像机位置 - 顶点位置
                fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - v2f.wPos.xyz);

                //半角方向向量 光方向 + 视角方向 平行四边形法则
                fixed3 halfDir = normalize(worldLightDir + viewDir);

                // 高光颜色 = 光颜色 * 属性中的高光颜色 * pow(max(0, dot(世界坐标系法线, 世界坐标系半角向量)), 光泽度);
                fixed3 specular = _LightColor0.rgb * _SpecularColor.rgb * pow(
                    max(0, dot(worldNormal, halfDir)), _SpecularNum);


                //衰减值
                #ifdef USING_DIRECTIONAL_LIGHT
                    //平行光
                    fixed atten = 1;
                #else
                #if defined(POINT)
                        //点光源
                        //将世界坐标系下顶点转到光源空间下
                        float3 lightCoord = mul(unity_WorldToLight, float4(v2f.wPos, 1)).xyz;
                        //利用这个坐标得到距离的平方 然后再再光源纹理中映射得到衰减值
                        fixed atten = tex2D(_LightTexture0, dot(lightCoord,lightCoord).xx).UNITY_ATTEN_CHANNEL;
                #elif defined(SPOT)
                        //聚光灯   
                        //将世界坐标系下顶点转到光源空间下 聚光灯需要用w参与后续计算
                        float4 lightCoord = mul(unity_WorldToLight, float4(v2f.wPos, 1));
                        fixed atten = (lightCoord.z > 0) * //判断在聚光灯前面吗
                                      tex2D(_LightTexture0, lightCoord.xy / lightCoord.w + 0.5).w * //映射到大图中进行采样
                                      tex2D(_LightTextureB0, dot(lightCoord,lightCoord).xx).UNITY_ATTEN_CHANNEL; //距离的平方采样
                #else
                fixed atten = 1;
                #endif
                #endif

                // 衰减值 不准确的判断
                // #if defined(_DIRECTIONAL_LIGHT)
                //     //平行光
                //     fixed atten = 1;
                // #elif defined(_POINT_LIGHT)
                //     //点光源
                //     //将世界坐标系下顶点转到光源空间下
                //     float3 lightCoord = mul(unity_WorldToLight, float4(v2f.wPos, 1)).xyz;
                //     //利用这个坐标得到距离的平方 然后再再光源纹理中映射得到衰减值
                //     fixed atten = tex2D(_LightTexture0, dot(lightCoord,lightCoord).xx).UNITY_ATTEN_CHANNEL;
                // #elif defined(_SPOT_LIGHT)
                //     //聚光灯   
                //     //将世界坐标系下顶点转到光源空间下 聚光灯需要用w参与后续计算
                //     float4 lightCoord = mul(unity_WorldToLight, float4(v2f.wPos, 1));
                //     fixed atten = (lightCoord.z > 0) * //判断在聚光灯前面吗
                //                   tex2D(_LightTexture0, lightCoord.xy / lightCoord.w + 0.5).w * //映射到大图中进行采样
                //                   tex2D(_LightTextureB0, dot(lightCoord,lightCoord).xx).UNITY_ATTEN_CHANNEL; //距离的平方采样
                // #else
                //     fixed atten = 1;
                // #endif

                //在附加渲染通道中不需要在加上环境光UNITY_LIGHTMODEL_AMBIENT.rgb颜色了
                //因为它只需要计算一次 在基础渲染通道中已经计算了
                return fixed4((diffuse + specular) * atten, 1);
            }
            ENDCG
        }

        //阴影投影 Pass 主要是用来计算阴影映射纹理
        Pass
        {
            Tags
            {
                "LightMode" = "ShadowCaster"
            }
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            
            //  该编译指令时告诉Unity编译器生成多个着色器变体
            //  用于支持不同类型的阴影(SM,SSSM等等)
            //  可以确保着色器能够在所有可能的阴影投射模式下正确渲染
            //  比如有些低端移动设备不支持
            #pragma multi_compile_shadowcaster
            //  其中包含了关键的阴影计算相关的宏
            #include "UnityCG.cginc"

            struct v2f
            {
                //顶点到片元着色器阴影投射结构体数据宏
                //这个宏定义了一些标准的成员变量
                //这些变量用于在阴影投射路径中传递顶点数据到片元着色器
                //我们主要在结构体中使用
                V2F_SHADOW_CASTER;
            };

            v2f vert(appdata_base v)
            {
                v2f v2f;
                
                //转移阴影投射器法线偏移宏
                //用于在顶点着色器中计算和传递阴影投射所需的变量
                //主要做了
                //2-2-1.将对象空间的顶点位置转换为裁剪空间的位置
                //2-2-2.考虑法线偏移,以减轻阴影失真问题,尤其是在处理自阴影时
                //2-2-3.传递顶点的投影空间位置(光源视角下的坐标),用于后续的阴影计算
                //我们主要在顶点着色器中使用
                
                //这个宏会自动使用我们传进来的v 注意v这个参数不可以改名 否则识别不了
                TRANSFER_SHADOW_CASTER_NORMALOFFSET(v2f);
                
                return v2f;
            }

            float4 frag(v2f v2f):SV_Target
            {
                //阴影投射片元宏
                //将深度值写入到阴影映射纹理中
                //我们主要在片元着色器中使用
                SHADOW_CASTER_FRAGMENT(v2f);
            }
            ENDCG
        }
    }

    //    //找不到LightMode为ShaderCaster的Pass 使用Specular的shader
    //    Fallback "Specular"
}

Lesson15_ForwardLighting

Shader "Unlit/Lesson15_ForwardLighting"
{
    Properties
    {
        //材质的漫反射光照颜色
        _MainColor("MainColor", Color) = (1,1,1,1)
        //高光反射颜色
        _SpecularColor("SpecularColor", Color) = (1,1,1,1)
        //光泽度
        _SpecularNum("SpecularNum", Range(0, 20)) = 0.5
    }
    SubShader
    {
        //Bass Pass 基础渲染通道
        Pass
        {
            //设置我们的光照模式 ForwardBase这种向前渲染模式 主要是用来处理 不透明物体的 光照渲染的
            Tags
            {
                "LightMode"="ForwardBase"
            }

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            //用于帮助我们编译所有变体 并且保证衰减相关光照变量能够正确赋值到对应的内置变量中
            #pragma multi_compile_fwdbase

            //引用对应的内置文件 
            //主要是为了之后 的 比如内置结构体使用,内置变量使用
            #include "UnityCG.cginc"
            #include "Lighting.cginc"


            //材质的漫反射颜色
            fixed4 _MainColor;
            //对应属性当中的颜色和光泽度
            fixed4 _SpecularColor;
            float _SpecularNum;

            //顶点着色器传递给片元着色器的内容
            struct v2f
            {
                //裁剪空间的位置
                float4 pos:SV_POSITION;
                //基于世界坐标系下的顶点位置
                float3 wPos:TEXCOORD0;
                //基于世界坐标系下的法线
                float3 wNormal:NORMAL;
            };


            //得到兰伯特光照模型计算的颜色 (逐片元)
            fixed3 getLambertColor(in float3 wNormal)
            {
                //_WorldSpaceLightPos0.xyz 世界坐标系下光源方向 _WorldSpaceLightPos0是四维向量 只需要xyz即可
                //normalize 把向量归一化
                //lightDir 得到光源单位向量
                float3 lightDir = normalize(_WorldSpaceLightPos0.xyz);

                //_LightColor0光 照颜色
                //.rgb 代表只使用颜色进行计算 透明度不考虑 
                //color 计算出了兰伯特光照的漫反射颜色
                fixed3 color = _LightColor0.rgb * _MainColor.rgb * max(0, dot(wNormal, lightDir));

                return color;
            }

            //得到Blinn Phong式高光反射模型计算的颜色(逐片元)
            fixed3 getSpecularColor(in float3 wPos, in float3 wNormal)
            {
                //1.视角单位向量
                //用观察者的位置(摄像机的位置)减去世界空间下顶点坐标 得到的就是视角方向 并且进行单位化
                float3 viewDir = normalize(_WorldSpaceCameraPos.xyz - wPos);

                //2.光的反射单位向量
                //光的方向
                float3 lightDir = normalize(_WorldSpaceLightPos0.xyz);

                //半角方向向量
                float3 halfA = normalize(viewDir + lightDir);

                //color = 光源颜色 * 材质高光反射颜色 * pow( max(0, dot(视角单位向量, 光的反射单位向量)), 光泽度 )
                fixed3 color = _LightColor0.rgb * _SpecularColor.rgb * pow(max(0, dot(wNormal, halfA)), _SpecularNum);

                return color;
            }

            v2f vert(appdata_base appdata_base)
            {
                v2f v2f;

                //转换模型空间下的顶点到裁剪空间中
                v2f.pos = UnityObjectToClipPos(appdata_base.vertex);

                //转换模型空间下的法线到世界空间下
                v2f.wNormal = UnityObjectToWorldNormal(appdata_base.normal);

                //转换模型空间下的顶点到世界空间
                v2f.wPos = mul(unity_ObjectToWorld, appdata_base.vertex).xyz;

                return v2f;
            }

            fixed4 frag(v2f v2f) : SV_Target
            {
                //计算兰伯特光照颜色
                fixed3 lambertColor = getLambertColor(v2f.wNormal);

                //计算BlinnPhong式高光反射颜色
                fixed3 specularColor = getSpecularColor(v2f.wPos, v2f.wNormal);

                //衰减值
                //因为我们这是基础Pass 一般是处理场景中最亮的平行光 所以可以认为不存在衰减值的 衰减值默认是1
                //但是之后附加Pass的衰减值就会变化了
                fixed atten = 1;

                //物体表面光照颜色 = 环境光颜色 + 兰伯特光照模型所得颜色 + 布林Phong式高光反射光照模型所得颜色
                //衰减值 会和 漫反射颜色 + 高光反射颜色 后 再进行乘法运算   
                fixed3 blinnPhongColor = UNITY_LIGHTMODEL_AMBIENT.rgb + (lambertColor + specularColor) * atten;

                return fixed4(blinnPhongColor.rgb, 1);
            }
            ENDCG
        }


        //Additional Pass 附加渲染通道
        Pass
        {
            //设置我们的光照模式 ForwardAdd这种向前渲染模式 主要是用来处理 附加光照渲染的
            Tags
            {
                "LightMode"="ForwardAdd"
            }

            //线性减淡的效果 进行 光照颜色混合
            Blend One One


            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            //用于帮助我们编译所有变体 并且保证衰减相关光照变量能够正确赋值到对应的内置变量中
            #pragma multi_compile_fwdadd

            //引用对应的内置文件 
            //主要是为了之后 的 比如内置结构体使用,内置变量使用
            #include "UnityCG.cginc"
            #include "Lighting.cginc"
            #include "AutoLight.cginc"


            //材质的漫反射颜色
            fixed4 _MainColor;
            //对应属性当中的颜色和光泽度
            fixed4 _SpecularColor;
            float _SpecularNum;

            //顶点着色器传递给片元着色器的内容
            struct v2f
            {
                //裁剪空间的位置
                float4 pos:SV_POSITION;
                //基于世界坐标系下的顶点位置
                float3 wPos:TEXCOORD0;
                //基于世界坐标系下的法线
                float3 wNormal:NORMAL;
            };


            v2f vert(appdata_base appdata_base)
            {
                v2f v2f;

                //转换模型空间下的顶点到裁剪空间中
                v2f.pos = UnityObjectToClipPos(appdata_base.vertex);

                //转换模型空间下的法线到世界空间下
                v2f.wNormal = UnityObjectToWorldNormal(appdata_base.normal);

                //转换模型空间下的顶点到世界空间
                v2f.wPos = mul(unity_ObjectToWorld, appdata_base.vertex).xyz;

                return v2f;
            }

            fixed4 frag (v2f v2f) : SV_Target
            {
                //兰伯特漫反射
                //公式:漫反射颜色 = 光颜色 * 属性中颜色 * max(0, dot(世界坐标系下的法线, 世界坐标系下的光方向));
                
                //世界标准法线
                fixed3 worldNormal = normalize(v2f.wNormal);
                
                #if defined(_DIRECTIONAL_LIGHT)
                    //平行光 光的方向 其实就是它的位置
                    fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
                #else
                    //点光源和聚光灯 光的方向 是 光的位置 - 顶点位置
                    fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz - v2f.wPos);
                #endif
                
                // 漫反射颜色 = 光颜色 * 属性中颜色 * max(0, dot(世界坐标系下的法线, 世界坐标系下的光方向));
                fixed3 diffuse = _LightColor0.rgb * _MainColor.rgb * max(0, dot(worldNormal, worldLightDir));

                
                //BlinnPhong高光反射
                //公式:高光颜色 = 光颜色 * 属性中的高光颜色 * pow(max(0, dot(世界坐标系法线, 世界坐标系半角向量)), 光泽度);
                
                //视角方向 摄像机位置 - 顶点位置
                fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - v2f.wPos.xyz);
                
                //半角方向向量 光方向 + 视角方向 平行四边形法则
                fixed3 halfDir = normalize(worldLightDir + viewDir);
                
                // 高光颜色 = 光颜色 * 属性中的高光颜色 * pow(max(0, dot(世界坐标系法线, 世界坐标系半角向量)), 光泽度);
                fixed3 specular = _LightColor0.rgb * _SpecularColor.rgb * pow(max(0, dot(worldNormal, halfDir)), _SpecularNum);

                
                //衰减值
                #ifdef USING_DIRECTIONAL_LIGHT
                    //平行光
                    fixed atten = 1;
                #else
                    #if defined(POINT)
                        //点光源
                        //将世界坐标系下顶点转到光源空间下
                        float3 lightCoord = mul(unity_WorldToLight, float4(v2f.wPos, 1)).xyz;
                        //利用这个坐标得到距离的平方 然后再再光源纹理中映射得到衰减值
                        fixed atten = tex2D(_LightTexture0, dot(lightCoord,lightCoord).xx).UNITY_ATTEN_CHANNEL;
                    #elif defined(SPOT)
                        //聚光灯   
                        //将世界坐标系下顶点转到光源空间下 聚光灯需要用w参与后续计算
                        float4 lightCoord = mul(unity_WorldToLight, float4(v2f.wPos, 1));
                        fixed atten = (lightCoord.z > 0) * //判断在聚光灯前面吗
                                      tex2D(_LightTexture0, lightCoord.xy / lightCoord.w + 0.5).w * //映射到大图中进行采样
                                      tex2D(_LightTextureB0, dot(lightCoord,lightCoord).xx).UNITY_ATTEN_CHANNEL; //距离的平方采样
                    #else
                        fixed atten = 1;
                    #endif
                #endif

                // 衰减值 不准确的判断
                // #if defined(_DIRECTIONAL_LIGHT)
                //     //平行光
                //     fixed atten = 1;
                // #elif defined(_POINT_LIGHT)
                //     //点光源
                //     //将世界坐标系下顶点转到光源空间下
                //     float3 lightCoord = mul(unity_WorldToLight, float4(v2f.wPos, 1)).xyz;
                //     //利用这个坐标得到距离的平方 然后再再光源纹理中映射得到衰减值
                //     fixed atten = tex2D(_LightTexture0, dot(lightCoord,lightCoord).xx).UNITY_ATTEN_CHANNEL;
                // #elif defined(_SPOT_LIGHT)
                //     //聚光灯   
                //     //将世界坐标系下顶点转到光源空间下 聚光灯需要用w参与后续计算
                //     float4 lightCoord = mul(unity_WorldToLight, float4(v2f.wPos, 1));
                //     fixed atten = (lightCoord.z > 0) * //判断在聚光灯前面吗
                //                   tex2D(_LightTexture0, lightCoord.xy / lightCoord.w + 0.5).w * //映射到大图中进行采样
                //                   tex2D(_LightTextureB0, dot(lightCoord,lightCoord).xx).UNITY_ATTEN_CHANNEL; //距离的平方采样
                // #else
                //     fixed atten = 1;
                // #endif

                //在附加渲染通道中不需要在加上环境光UNITY_LIGHTMODEL_AMBIENT.rgb颜色了
                //因为它只需要计算一次 在基础渲染通道中已经计算了
                return fixed4((diffuse + specular)*atten, 1);
            }
            ENDCG
        }
    }
    
    //找不到LightMode为ShaderCaster的Pass 使用Specular的shader
    Fallback "Specular"
}


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 785293209@qq.com

×

喜欢就点赞,疼爱就打赏