20.标准漫反射

  1. 20.标准光照着色器-标准漫反射
    1. 20.1 知识点
      1. 什么是标准漫反射 Shader
      2. 制作常用漫反射 Shader
        1. 主要步骤
        2. 新建shader,取名为Lesson20_BumpedDiffuse凹凸漫反射
        3. 复制 Shader开发入门中 Lesson30_Bump_Texture_World_High_Performance.shader,粘贴到新建文件中
        4. 加入渲染标签Tags { “RenderType”=”Opaque” “Queue”=”Geometry”},渲染类型设置为不透明的、渲染队列设置为几何队列(不透明的几何体通常使用该队列)
        5. 删除高光反射相关属性,片元函数还有用到_SpecularColor和_SpecularNum的等一下删
        6. 添加multi_compile_fwdbase指令和AutoLight内置文件
        7. v2f结构体添加阴影坐标宏SHADOW_COORDS
        8. 顶点函数添加坐标转换宏TRANSFER_SHADOW
        9. 片元函数删除高光相关,加上计算光照衰减和阴影衰减的宏UNITY_LIGHT_ATTENUATION,最后计算时乘上衰减值
        10. 加入附加渲染通道,复制基础渲染通道。把LightMode改成ForwardAdd。添加Blend One One让线性减淡的效果 进行 光照颜色混合。把预编译指令改成multi_compile_fwdadd_fullshadows让Unity生成多个包括支持和不支持阴影的Shader变体,从而为额外的逐像素光源计算阴影,并传递给Shader。之前需要我们手动考虑不同光源的情况,现在学了UNITY_LIGHT_ATTENUATION宏会在内部帮我们计算。
        11. 注意附加渲染通道的参数名要改成v,否则可能出现Shader error in ‘Unlit/Lesson20_BumpedDiffuse’: undeclared identifier ‘v’ at line 290 (on d3d11)这样的报错。为了统一期间基础通道也改一下参数名为v
        12. 加入FallBac “Diffuse”,这Unity自带的漫反射Shader,里面实现了对应投射阴影的Pass
        13. 创建材质,可以看到可以接收和投放多光源阴影。我们成功实现了常见的带法线、漫反射、有阴影且能收到多光源影响的常用shader
    2. 20.2 知识点代码
      1. Lesson20_标准光照着色器_标准漫反射.cs
      2. Lesson20_BumpedDiffuse.shader

20.标准光照着色器-标准漫反射


20.1 知识点

什么是标准漫反射 Shader

学习了光源和阴影的主要知识点后(包括多光源、阴影、光照衰减等),已经可以在 Shader 中处理光和阴影的效果了。

现在结合所学知识,可以实现一个标准的漫反射 Shader:

  • 基于 Phong 光照模型(去掉高光反射)
  • 带有法线(通过世界空间计算,全局效果更准确)
  • 支持多光源和阴影

所谓“标准”,其实只是一个常用 Shader 而已。

制作常用漫反射 Shader

主要步骤

  1. 新建一个 Shader 文件,命名为 Lesson20_BumpedDiffuse
  2. 从 Shader开发入门中 的Lesson30_Bump_Texture_World_High_Performance.shader,粘贴到新建文件中。
  3. 添加渲染标签 Tags { "RenderType"="Opaque" "Queue"="Geometry" },设置渲染类型为不透明,渲染队列为几何队列。
  4. 删除高光反射相关代码。
  5. 添加阴影和衰减相关代码。
  6. 添加附加渲染通道。
  7. 添加 FallBack "Diffuse"

新建shader,取名为Lesson20_BumpedDiffuse凹凸漫反射

Shader "Unlit/Lesson20_BumpedDiffuse"
{
    
}

复制 Shader开发入门中 Lesson30_Bump_Texture_World_High_Performance.shader,粘贴到新建文件中

Shader "Unlit/Lesson20_BumpedDiffuse"
{
    Properties
    {
        _MainColor("MainColor", Color) = (1,1,1,1)//  漫反射颜色 
        _MainTex("MainTex", 2D) = ""{}//  单张纹理
        _BumpMap("BumpMap", 2D) = ""{}//  法线纹理
        _BumpScale("BumpScale", Range(0,1)) = 1//  凹凸程度
        _SpecularColor("SpecularColor", Color) = (1,1,1,1)//  高光反射颜色
        _SpecularNum("SpecularNum", Range(0,20)) = 18//  光泽度
    }
    SubShader
    {
        Pass
        {
            Tags
            {
                "LightMode"="ForwardBase"
            }
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

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

            float4 _MainColor; //漫反射颜色
            sampler2D _MainTex; //颜色纹理
            float4 _MainTex_ST; //颜色纹理的缩放和平移
            sampler2D _BumpMap; //法线纹理
            float4 _BumpMap_ST; //法线纹理的缩放和平移
            float _BumpScale; //凹凸程度
            float4 _SpecularColor; //高光颜色
            fixed _SpecularNum; //光泽度

            struct v2f
            {
                //裁剪空间下坐标
                float4 pos:SV_POSITION;

                //纹理uv

                //我们可以单独的声明两个float2的成员用于记录 颜色和法线纹理的uv坐标
                //float2 uvTex:TEXCOORD0;//颜色纹理
                //float2 uvBump:TEXCOORD1;//法线纹理

                //也可以直接声明一个float4的成员 xy用于记录颜色纹理的uv,zw用于记录法线纹理的uv
                float4 uv:TEXCOORD0; //纹理变量


                //顶点相对于世界坐标的位置 主要用于 之后的 视角方向的计算
                //float3 worldPos:TEXCOORD1;

                //切线 到 世界空间的 变换矩阵
                //float3x3 rotation:TEXCOORD2;

                //代表我们切线空间到世界空间的 变换矩阵的3行 三个变量的w存世界坐标
                float4 TtoW0:TEXCOORD1;
                float4 TtoW1:TEXCOORD2;
                float4 TtoW2:TEXCOORD3;
            };

            v2f vert(appdata_full appdata_full)
            {
                v2f v2f;

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

                //计算纹理的缩放偏移
                v2f.uv.xy = appdata_full.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
                v2f.uv.zw = appdata_full.texcoord.xy * _BumpMap_ST.xy + _BumpMap_ST.zw;

                //得到世界空间下的 顶点位置 用于之后在片元函数中计算视角方向(世界空间下的)
                //v2f.worldPos = mul(unity_ObjectToWorld, appdata_full.vertex);
                float3 worldPos = mul(unity_ObjectToWorld, appdata_full.vertex);

                //把模型空间下的法线、切线转换到世界空间下
                float3 worldNormal = UnityObjectToWorldNormal(appdata_full.normal);
                float3 worldTangent = UnityObjectToWorldDir(appdata_full.tangent);

                //计算副切线 计算叉乘结果后 垂直与切线和法线的向量有两条 通过乘以 切线当中的w,就可以确定是哪一条
                //cross是叉乘 appdata_full.tangent.w代表叉乘结果方向
                float3 worldBinormal = cross(normalize(worldTangent), normalize(worldNormal)) * appdata_full.tangent.w;

                //这个就是我们 切线空间到世界空间的 转换矩阵
                //       |    |     |
                //      切线 副切线 法线
                //       |    |     |
                //v2f.rotation = float3x3( worldTangent.x, worldBinormal.x,  worldNormal.x,
                //                          worldTangent.y, worldBinormal.y,  worldNormal.y,
                //                          worldTangent.z, worldBinormal.z,  worldNormal.z);
                v2f.TtoW0 = float4(worldTangent.x, worldBinormal.x, worldNormal.x, worldPos.x);
                v2f.TtoW1 = float4(worldTangent.y, worldBinormal.y, worldNormal.y, worldPos.y);
                v2f.TtoW2 = float4(worldTangent.z, worldBinormal.z, worldNormal.z, worldPos.z);

                return v2f;
            }

            fixed4 frag(v2f v2f) : SV_Target
            {
                //世界空间下光的方向
                fixed3 lightDir = normalize(_WorldSpaceLightPos0.xyz);

                //世界空间下视角方向
                // fixed3 viewDir = normalize(UnityWorldSpaceViewDir(v2f.worldPos));
                float3 worldPos = float3(v2f.TtoW0.w, v2f.TtoW1.w, v2f.TtoW2.w);
                fixed3 viewDir = normalize(UnityWorldSpaceViewDir(worldPos));

                //纹理采样函数tex2D
                //通过纹理采样函数 取出法线纹理贴图当中的数据
                float4 packedNormal = tex2D(_BumpMap, v2f.uv.zw);

                //利用内置的UnpackNormal函数对法线信息进行逆运算以及可能的解压
                //将我们取出来的法线数据 进行逆运算并且可能会进行解压缩的运算,最终得到切线空间下的法线数据
                float3 tangentNormal = UnpackNormal(packedNormal);

                //乘以凹凸程度的系数
                tangentNormal.xy *= _BumpScale;
                tangentNormal.z = sqrt(1.0 - saturate(dot(tangentNormal.xy, tangentNormal.xy)));


                //把计算完毕后的切线空间下的法线转换到世界空间下
                // float3 worldNormal = mul(v2f.rotation, tangentNormal);

                //以下两种写法等价 但是可以一步到位
                //float3x3 rotation = float3x3(v2f.TtoW0.xyz, v2f.TtoW1.xyz, v2f.TtoW2.xyz );
                //float3 worldNormal = mul(rotation, tangentNormal);
                //本质 就是在进行矩阵运算
                float3 worldNormal = float3(dot(v2f.TtoW0.xyz, tangentNormal), dot(v2f.TtoW1.xyz, tangentNormal),
            dot(v2f.TtoW2.xyz, tangentNormal));


                //接下来就来处理 带颜色纹理的 布林方光照模型计算

                //颜色纹理和漫反射颜色的 叠加
                fixed3 albedo = tex2D(_MainTex, v2f.uv.xy) * _MainColor.rgb;

                //兰伯特漫反射颜色 = 光的颜色 * 漫反射材质的颜色 * max(0, dot(世界坐标系下的法线, 光的方向))
                fixed3 lambertColor = _LightColor0.rgb * albedo.rgb * max(0, dot(worldNormal, normalize(lightDir)));

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

                //高光反射的颜色 = 光的颜色 * 高光反射材质的颜色 * pow(max(0, dot(世界坐标系下的法线, 半角向量)), 光泽度)
                fixed3 specularColor = _LightColor0.rgb * _SpecularColor.rgb * pow(
                    max(0, dot(worldNormal, halfA)), _SpecularNum);

                //布林方光照颜色 = 环境光颜色 + 兰伯特漫反射颜色 + 高光反射的颜色
                fixed3 color = UNITY_LIGHTMODEL_AMBIENT.rgb * albedo + lambertColor + specularColor;

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

加入渲染标签Tags { “RenderType”=”Opaque” “Queue”=”Geometry”},渲染类型设置为不透明的、渲染队列设置为几何队列(不透明的几何体通常使用该队列)

Shader "Unlit/Lesson20_BumpedDiffuse"
{
    Properties
    {
        //...
    }
    SubShader
    {
        //  加入渲染标签Tags { "RenderType"="Opaque" "Queue"="Geometry"}
        //  渲染类型设置为不透明的、渲染队列设置为几何队列(不透明的几何体通常使用该队列)
        Tags
        {
            "RenderType"="Opaque" "Queue"="Geometry"
        }
        Pass
        {
            //...
        }
    }
}

删除高光反射相关属性,片元函数还有用到_SpecularColor和_SpecularNum的等一下删

Shader "Unlit/Lesson20_BumpedDiffuse"
{
    Properties
    {
        //...
        // _SpecularColor("SpecularColor", Color) = (1,1,1,1)//  高光反射颜色
        // _SpecularNum("SpecularNum", Range(0,20)) = 18//  光泽度
    }
    SubShader
    {
        //  加入渲染标签Tags { "RenderType"="Opaque" "Queue"="Geometry"}
        //  渲染类型设置为不透明的、渲染队列设置为几何队列(不透明的几何体通常使用该队列)
        Tags
        {
            "RenderType"="Opaque" "Queue"="Geometry"
        }
        Pass
        {
            //...
            // float4 _SpecularColor; //高光颜色
            // fixed _SpecularNum; //光泽度

            //...
        }
    }
}

添加multi_compile_fwdbase指令和AutoLight内置文件

#pragma vertex vert
#pragma fragment frag

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

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

//使用阴影宏内置文件
#include "AutoLight.cginc"

v2f结构体添加阴影坐标宏SHADOW_COORDS

struct v2f
{
    //裁剪空间下坐标
    float4 pos:SV_POSITION;

    //纹理uv

    //我们可以单独的声明两个float2的成员用于记录 颜色和法线纹理的uv坐标
    //float2 uvTex:TEXCOORD0;//颜色纹理
    //float2 uvBump:TEXCOORD1;//法线纹理

    //也可以直接声明一个float4的成员 xy用于记录颜色纹理的uv,zw用于记录法线纹理的uv
    float4 uv:TEXCOORD0; //纹理变量


    //顶点相对于世界坐标的位置 主要用于 之后的 视角方向的计算
    //float3 worldPos:TEXCOORD1;

    //切线 到 世界空间的 变换矩阵
    //float3x3 rotation:TEXCOORD2;

    //代表我们切线空间到世界空间的 变换矩阵的3行 三个变量的w存世界坐标
    float4 TtoW0:TEXCOORD1;
    float4 TtoW1:TEXCOORD2;
    float4 TtoW2:TEXCOORD3;


    //阴影坐标宏
    //n为下一个可用的插值寄存器的索引值(结构体前面有几个TEXCOORD就填几)
    //比如这里前面有四个
    SHADOW_COORDS(4)
};

顶点函数添加坐标转换宏TRANSFER_SHADOW

v2f vert(appdata_full appdata_full)
{
    v2f v2f;

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

    //计算纹理的缩放偏移
    v2f.uv.xy = appdata_full.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
    v2f.uv.zw = appdata_full.texcoord.xy * _BumpMap_ST.xy + _BumpMap_ST.zw;

    //得到世界空间下的 顶点位置 用于之后在片元函数中计算视角方向(世界空间下的)
    //v2f.worldPos = mul(unity_ObjectToWorld, appdata_full.vertex);
    float3 worldPos = mul(unity_ObjectToWorld, appdata_full.vertex);

    //把模型空间下的法线、切线转换到世界空间下
    float3 worldNormal = UnityObjectToWorldNormal(appdata_full.normal);
    float3 worldTangent = UnityObjectToWorldDir(appdata_full.tangent);

    //计算副切线 计算叉乘结果后 垂直与切线和法线的向量有两条 通过乘以 切线当中的w,就可以确定是哪一条
    //cross是叉乘 appdata_full.tangent.w代表叉乘结果方向
    float3 worldBinormal = cross(normalize(worldTangent), normalize(worldNormal)) * appdata_full.tangent.w;

    //这个就是我们 切线空间到世界空间的 转换矩阵
    //       |    |     |
    //      切线 副切线 法线
    //       |    |     |
    //v2f.rotation = float3x3( worldTangent.x, worldBinormal.x,  worldNormal.x,
    //                          worldTangent.y, worldBinormal.y,  worldNormal.y,
    //                          worldTangent.z, worldBinormal.z,  worldNormal.z);
    v2f.TtoW0 = float4(worldTangent.x, worldBinormal.x, worldNormal.x, worldPos.x);
    v2f.TtoW1 = float4(worldTangent.y, worldBinormal.y, worldNormal.y, worldPos.y);
    v2f.TtoW2 = float4(worldTangent.z, worldBinormal.z, worldNormal.z, worldPos.z);

    //坐标转换宏
    TRANSFER_SHADOW(v2f)

    return v2f;
}

片元函数删除高光相关,加上计算光照衰减和阴影衰减的宏UNITY_LIGHT_ATTENUATION,最后计算时乘上衰减值

fixed4 frag(v2f v2f) : SV_Target
    {
        //世界空间下光的方向
        fixed3 lightDir = normalize(_WorldSpaceLightPos0.xyz);

        //世界空间下视角方向
        // fixed3 viewDir = normalize(UnityWorldSpaceViewDir(v2f.worldPos));
        float3 worldPos = float3(v2f.TtoW0.w, v2f.TtoW1.w, v2f.TtoW2.w);
        fixed3 viewDir = normalize(UnityWorldSpaceViewDir(worldPos));

        //纹理采样函数tex2D
        //通过纹理采样函数 取出法线纹理贴图当中的数据
        float4 packedNormal = tex2D(_BumpMap, v2f.uv.zw);

        //利用内置的UnpackNormal函数对法线信息进行逆运算以及可能的解压
        //将我们取出来的法线数据 进行逆运算并且可能会进行解压缩的运算,最终得到切线空间下的法线数据
        float3 tangentNormal = UnpackNormal(packedNormal);

        //乘以凹凸程度的系数
        tangentNormal.xy *= _BumpScale;
        tangentNormal.z = sqrt(1.0 - saturate(dot(tangentNormal.xy, tangentNormal.xy)));


        //把计算完毕后的切线空间下的法线转换到世界空间下
        // float3 worldNormal = mul(v2f.rotation, tangentNormal);

        //以下两种写法等价 但是可以一步到位
        //float3x3 rotation = float3x3(v2f.TtoW0.xyz, v2f.TtoW1.xyz, v2f.TtoW2.xyz );
        //float3 worldNormal = mul(rotation, tangentNormal);
        //本质 就是在进行矩阵运算
        float3 worldNormal = float3(dot(v2f.TtoW0.xyz, tangentNormal), dot(v2f.TtoW1.xyz, tangentNormal),
                                    dot(v2f.TtoW2.xyz, tangentNormal));


        //接下来就来处理 带颜色纹理的 布林方光照模型计算

        //颜色纹理和漫反射颜色的 叠加
        fixed3 albedo = tex2D(_MainTex, v2f.uv.xy) * _MainColor.rgb;

        //兰伯特漫反射颜色 = 光的颜色 * 漫反射材质的颜色 * max(0, dot(世界坐标系下的法线, 光的方向))
        fixed3 lambertColor = _LightColor0.rgb * albedo.rgb * max(0, dot(worldNormal, normalize(lightDir)));

        //不使用高光了
        // //半角向量 = 视角方向 + 光的方向
        // float3 halfA = normalize(normalize(viewDir) + normalize(lightDir));
        //
        // //高光反射的颜色 = 光的颜色 * 高光反射材质的颜色 * pow(max(0, dot(世界坐标系下的法线, 半角向量)), 光泽度)
        // fixed3 specularColor = _LightColor0.rgb * _SpecularColor.rgb * pow(
        //     max(0, dot(worldNormal, halfA)), _SpecularNum);

        //计算光照衰减和阴影衰减的宏
        UNITY_LIGHT_ATTENUATION(atten, v2f, worldPos);

        // //布林方光照颜色 = 环境光颜色 + 兰伯特漫反射颜色 + 高光反射的颜色
        // fixed3 color = UNITY_LIGHTMODEL_AMBIENT.rgb * albedo + lambertColor + specularColor;

        //布林方光照颜色 = 环境光颜色 + 兰伯特漫反射颜色*衰减值
        fixed3 color = UNITY_LIGHTMODEL_AMBIENT.rgb * albedo + lambertColor*atten;

        return fixed4(color.rgb, 1);
}

加入附加渲染通道,复制基础渲染通道。把LightMode改成ForwardAdd。添加Blend One One让线性减淡的效果 进行 光照颜色混合。把预编译指令改成multi_compile_fwdadd_fullshadows让Unity生成多个包括支持和不支持阴影的Shader变体,从而为额外的逐像素光源计算阴影,并传递给Shader。之前需要我们手动考虑不同光源的情况,现在学了UNITY_LIGHT_ATTENUATION宏会在内部帮我们计算。

Shader "Unlit/Lesson20_BumpedDiffuse"
{
    Properties
    {
        //...
    }
    SubShader
    {
        //  加入渲染标签Tags { "RenderType"="Opaque" "Queue"="Geometry"}
        //  渲染类型设置为不透明的、渲染队列设置为几何队列(不透明的几何体通常使用该队列)
        Tags
        {
            "RenderType"="Opaque" "Queue"="Geometry"
        }
        
        //Base Pass
        Pass
        {
            //...
        }

        //Additional Pass
        Pass
        {
            //设置我们的光照模式 ForwardAdd这种向前渲染模式 主要是用来处理 附加光照渲染的
            Tags
            {
                "LightMode"="ForwardAdd"
            }
            
            //线性减淡的效果 进行 光照颜色混合
            Blend One One
            
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            //这个编译指令让Unity生成多个包括支持和不支持阴影的Shader变体
            //从而为额外的逐像素光源计算阴影,并传递给Shader了
            #pragma multi_compile_fwdadd_fullshadows

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

            //使用阴影宏内置文件
            #include "AutoLight.cginc"

            float4 _MainColor; //漫反射颜色
            sampler2D _MainTex; //颜色纹理
            float4 _MainTex_ST; //颜色纹理的缩放和平移
            sampler2D _BumpMap; //法线纹理
            float4 _BumpMap_ST; //法线纹理的缩放和平移
            float _BumpScale; //凹凸程度
            // float4 _SpecularColor; //高光颜色
            // fixed _SpecularNum; //光泽度

            struct v2f
            {
                //裁剪空间下坐标
                float4 pos:SV_POSITION;

                //纹理uv

                //我们可以单独的声明两个float2的成员用于记录 颜色和法线纹理的uv坐标
                //float2 uvTex:TEXCOORD0;//颜色纹理
                //float2 uvBump:TEXCOORD1;//法线纹理

                //也可以直接声明一个float4的成员 xy用于记录颜色纹理的uv,zw用于记录法线纹理的uv
                float4 uv:TEXCOORD0; //纹理变量


                //顶点相对于世界坐标的位置 主要用于 之后的 视角方向的计算
                //float3 worldPos:TEXCOORD1;

                //切线 到 世界空间的 变换矩阵
                //float3x3 rotation:TEXCOORD2;

                //代表我们切线空间到世界空间的 变换矩阵的3行 三个变量的w存世界坐标
                float4 TtoW0:TEXCOORD1;
                float4 TtoW1:TEXCOORD2;
                float4 TtoW2:TEXCOORD3;


                //阴影坐标宏
                //n为下一个可用的插值寄存器的索引值(结构体前面有几个TEXCOORD就填几)
                //比如这里前面有四个
                SHADOW_COORDS(4)
            };

            v2f vert(appdata_full appdata_full)
            {
                v2f v2f;

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

                //计算纹理的缩放偏移
                v2f.uv.xy = appdata_full.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
                v2f.uv.zw = appdata_full.texcoord.xy * _BumpMap_ST.xy + _BumpMap_ST.zw;

                //得到世界空间下的 顶点位置 用于之后在片元函数中计算视角方向(世界空间下的)
                //v2f.worldPos = mul(unity_ObjectToWorld, appdata_full.vertex);
                float3 worldPos = mul(unity_ObjectToWorld, appdata_full.vertex);

                //把模型空间下的法线、切线转换到世界空间下
                float3 worldNormal = UnityObjectToWorldNormal(appdata_full.normal);
                float3 worldTangent = UnityObjectToWorldDir(appdata_full.tangent);

                //计算副切线 计算叉乘结果后 垂直与切线和法线的向量有两条 通过乘以 切线当中的w,就可以确定是哪一条
                //cross是叉乘 appdata_full.tangent.w代表叉乘结果方向
                float3 worldBinormal = cross(normalize(worldTangent), normalize(worldNormal)) * appdata_full.tangent.w;

                //这个就是我们 切线空间到世界空间的 转换矩阵
                //       |    |     |
                //      切线 副切线 法线
                //       |    |     |
                //v2f.rotation = float3x3( worldTangent.x, worldBinormal.x,  worldNormal.x,
                //                          worldTangent.y, worldBinormal.y,  worldNormal.y,
                //                          worldTangent.z, worldBinormal.z,  worldNormal.z);
                v2f.TtoW0 = float4(worldTangent.x, worldBinormal.x, worldNormal.x, worldPos.x);
                v2f.TtoW1 = float4(worldTangent.y, worldBinormal.y, worldNormal.y, worldPos.y);
                v2f.TtoW2 = float4(worldTangent.z, worldBinormal.z, worldNormal.z, worldPos.z);

                //坐标转换宏
                TRANSFER_SHADOW(v2f)

                return v2f;
            }

            fixed4 frag(v2f v2f) : SV_Target
            {
                //世界空间下光的方向
                fixed3 lightDir = normalize(_WorldSpaceLightPos0.xyz);

                //世界空间下视角方向
                // fixed3 viewDir = normalize(UnityWorldSpaceViewDir(v2f.worldPos));
                float3 worldPos = float3(v2f.TtoW0.w, v2f.TtoW1.w, v2f.TtoW2.w);
                fixed3 viewDir = normalize(UnityWorldSpaceViewDir(worldPos));

                //纹理采样函数tex2D
                //通过纹理采样函数 取出法线纹理贴图当中的数据
                float4 packedNormal = tex2D(_BumpMap, v2f.uv.zw);

                //利用内置的UnpackNormal函数对法线信息进行逆运算以及可能的解压
                //将我们取出来的法线数据 进行逆运算并且可能会进行解压缩的运算,最终得到切线空间下的法线数据
                float3 tangentNormal = UnpackNormal(packedNormal);

                //乘以凹凸程度的系数
                tangentNormal.xy *= _BumpScale;
                tangentNormal.z = sqrt(1.0 - saturate(dot(tangentNormal.xy, tangentNormal.xy)));


                //把计算完毕后的切线空间下的法线转换到世界空间下
                // float3 worldNormal = mul(v2f.rotation, tangentNormal);

                //以下两种写法等价 但是可以一步到位
                //float3x3 rotation = float3x3(v2f.TtoW0.xyz, v2f.TtoW1.xyz, v2f.TtoW2.xyz );
                //float3 worldNormal = mul(rotation, tangentNormal);
                //本质 就是在进行矩阵运算
                float3 worldNormal = float3(dot(v2f.TtoW0.xyz, tangentNormal), dot(v2f.TtoW1.xyz, tangentNormal),
                                            dot(v2f.TtoW2.xyz, tangentNormal));


                //接下来就来处理 带颜色纹理的 布林方光照模型计算

                //颜色纹理和漫反射颜色的 叠加
                fixed3 albedo = tex2D(_MainTex, v2f.uv.xy) * _MainColor.rgb;

                //兰伯特漫反射颜色 = 光的颜色 * 漫反射材质的颜色 * max(0, dot(世界坐标系下的法线, 光的方向))
                fixed3 lambertColor = _LightColor0.rgb * albedo.rgb * max(0, dot(worldNormal, normalize(lightDir)));

                //不使用高光了
                // //半角向量 = 视角方向 + 光的方向
                // float3 halfA = normalize(normalize(viewDir) + normalize(lightDir));
                //
                // //高光反射的颜色 = 光的颜色 * 高光反射材质的颜色 * pow(max(0, dot(世界坐标系下的法线, 半角向量)), 光泽度)
                // fixed3 specularColor = _LightColor0.rgb * _SpecularColor.rgb * pow(
                //     max(0, dot(worldNormal, halfA)), _SpecularNum);

                //计算光照衰减和阴影衰减的宏
                UNITY_LIGHT_ATTENUATION(atten, v2f, worldPos);

                // //布林方光照颜色 = 环境光颜色 + 兰伯特漫反射颜色 + 高光反射的颜色
                // fixed3 color = UNITY_LIGHTMODEL_AMBIENT.rgb * albedo + lambertColor + specularColor;

                //布林方光照颜色 = 环境光颜色 + 兰伯特漫反射颜色*衰减值
                fixed3 color = UNITY_LIGHTMODEL_AMBIENT.rgb * albedo + lambertColor*atten;

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

注意附加渲染通道的参数名要改成v,否则可能出现Shader error in ‘Unlit/Lesson20_BumpedDiffuse’: undeclared identifier ‘v’ at line 290 (on d3d11)这样的报错。为了统一期间基础通道也改一下参数名为v

v2f vert(appdata_full v)
{
    v2f v2f;

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

    //计算纹理的缩放偏移
    v2f.uv.xy = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
    v2f.uv.zw = v.texcoord.xy * _BumpMap_ST.xy + _BumpMap_ST.zw;

    //得到世界空间下的 顶点位置 用于之后在片元函数中计算视角方向(世界空间下的)
    //v2f.worldPos = mul(unity_ObjectToWorld, appdata_full.vertex);
    float3 worldPos = mul(unity_ObjectToWorld, v.vertex);

    //把模型空间下的法线、切线转换到世界空间下
    float3 worldNormal = UnityObjectToWorldNormal(v.normal);
    float3 worldTangent = UnityObjectToWorldDir(v.tangent);

    //计算副切线 计算叉乘结果后 垂直与切线和法线的向量有两条 通过乘以 切线当中的w,就可以确定是哪一条
    //cross是叉乘 appdata_full.tangent.w代表叉乘结果方向
    float3 worldBinormal = cross(normalize(worldTangent), normalize(worldNormal)) * v.tangent.w;

    //这个就是我们 切线空间到世界空间的 转换矩阵
    //       |    |     |
    //      切线 副切线 法线
    //       |    |     |
    //v2f.rotation = float3x3( worldTangent.x, worldBinormal.x,  worldNormal.x,
    //                          worldTangent.y, worldBinormal.y,  worldNormal.y,
    //                          worldTangent.z, worldBinormal.z,  worldNormal.z);
    v2f.TtoW0 = float4(worldTangent.x, worldBinormal.x, worldNormal.x, worldPos.x);
    v2f.TtoW1 = float4(worldTangent.y, worldBinormal.y, worldNormal.y, worldPos.y);
    v2f.TtoW2 = float4(worldTangent.z, worldBinormal.z, worldNormal.z, worldPos.z);

    //坐标转换宏
    TRANSFER_SHADOW(v2f)

    return v2f;
}

加入FallBac “Diffuse”,这Unity自带的漫反射Shader,里面实现了对应投射阴影的Pass

//Unity自带的漫反射Shader,里面实现了对应投射阴影的Pass
Fallback "Diffuse"

创建材质,可以看到可以接收和投放多光源阴影。我们成功实现了常见的带法线、漫反射、有阴影且能收到多光源影响的常用shader


20.2 知识点代码

Lesson20_标准光照着色器_标准漫反射.cs

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

public class Lesson20_标准光照着色器_标准漫反射 : MonoBehaviour
{
    void Start()
    {
        #region 知识点一 什么是标准漫反射Shader?

        //目前我们已经完成了光源和阴影的主要知识点学习
        //我们学习了多光源、阴影、光照衰减等等知识
        //已经可以在Shader中处理光和阴影相关的效果了
        //那么我们将结合所学的知识实现一个标准的漫反射Shader
        //该Shader其实就是一个
        //带有法线(世界空间中计算-全局效果的表现更准确)的基于Phong光照模型(去掉高光反射)的
        //支持多光源和阴影的Shader

        //说是标准,其实就是一个常用Shader而已

        #endregion

        #region 知识点二 制作常用漫反射Shader

        //1.新建一个Shader,取名叫Lesson20_BumpedDiffuse(凹凸漫反射)
        //2.复制 Shader开发入门中 Lesson30_Bump_Texture_World_High_Performance.shader,粘贴到新建文件中
        //3.加入渲染标签Tags { "RenderType"="Opaque" "Queue"="Geometry"}
        //  渲染类型设置为不透明的、渲染队列设置为几何队列(不透明的几何体通常使用该队列)
        //4.删除高光反射相关代码
        //5.加入阴影、衰减相关代码
        //6.加入附加渲染通道
        //7.加入FallBac "Diffuse"

        #endregion
    }
}

Lesson20_BumpedDiffuse.shader

Shader "Unlit/Lesson20_BumpedDiffuse"
{
    Properties
    {
        _MainColor("MainColor", Color) = (1,1,1,1)//  漫反射颜色 
        _MainTex("MainTex", 2D) = ""{}//  单张纹理
        _BumpMap("BumpMap", 2D) = ""{}//  法线纹理
        _BumpScale("BumpScale", Range(0,1)) = 1//  凹凸程度
        //        _SpecularColor("SpecularColor", Color) = (1,1,1,1)//  高光反射颜色
        //        _SpecularNum("SpecularNum", Range(0,20)) = 18//  光泽度
    }
    SubShader
    {
        //  加入渲染标签Tags { "RenderType"="Opaque" "Queue"="Geometry"}
        //  渲染类型设置为不透明的、渲染队列设置为几何队列(不透明的几何体通常使用该队列)
        Tags
        {
            "RenderType"="Opaque" "Queue"="Geometry"
        }

        //Base Pass
        Pass
        {
            Tags
            {
                "LightMode"="ForwardBase"
            }
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

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

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

            //使用阴影宏内置文件
            #include "AutoLight.cginc"

            float4 _MainColor; //漫反射颜色
            sampler2D _MainTex; //颜色纹理
            float4 _MainTex_ST; //颜色纹理的缩放和平移
            sampler2D _BumpMap; //法线纹理
            float4 _BumpMap_ST; //法线纹理的缩放和平移
            float _BumpScale; //凹凸程度
            // float4 _SpecularColor; //高光颜色
            // fixed _SpecularNum; //光泽度

            struct v2f
            {
                //裁剪空间下坐标
                float4 pos:SV_POSITION;

                //纹理uv

                //我们可以单独的声明两个float2的成员用于记录 颜色和法线纹理的uv坐标
                //float2 uvTex:TEXCOORD0;//颜色纹理
                //float2 uvBump:TEXCOORD1;//法线纹理

                //也可以直接声明一个float4的成员 xy用于记录颜色纹理的uv,zw用于记录法线纹理的uv
                float4 uv:TEXCOORD0; //纹理变量


                //顶点相对于世界坐标的位置 主要用于 之后的 视角方向的计算
                //float3 worldPos:TEXCOORD1;

                //切线 到 世界空间的 变换矩阵
                //float3x3 rotation:TEXCOORD2;

                //代表我们切线空间到世界空间的 变换矩阵的3行 三个变量的w存世界坐标
                float4 TtoW0:TEXCOORD1;
                float4 TtoW1:TEXCOORD2;
                float4 TtoW2:TEXCOORD3;


                //阴影坐标宏
                //n为下一个可用的插值寄存器的索引值(结构体前面有几个TEXCOORD就填几)
                //比如这里前面有四个
                SHADOW_COORDS(4)
            };

            v2f vert(appdata_full v)
            {
                v2f v2f;

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

                //计算纹理的缩放偏移
                v2f.uv.xy = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
                v2f.uv.zw = v.texcoord.xy * _BumpMap_ST.xy + _BumpMap_ST.zw;

                //得到世界空间下的 顶点位置 用于之后在片元函数中计算视角方向(世界空间下的)
                //v2f.worldPos = mul(unity_ObjectToWorld, appdata_full.vertex);
                float3 worldPos = mul(unity_ObjectToWorld, v.vertex);

                //把模型空间下的法线、切线转换到世界空间下
                float3 worldNormal = UnityObjectToWorldNormal(v.normal);
                float3 worldTangent = UnityObjectToWorldDir(v.tangent);

                //计算副切线 计算叉乘结果后 垂直与切线和法线的向量有两条 通过乘以 切线当中的w,就可以确定是哪一条
                //cross是叉乘 appdata_full.tangent.w代表叉乘结果方向
                float3 worldBinormal = cross(normalize(worldTangent), normalize(worldNormal)) * v.tangent.w;

                //这个就是我们 切线空间到世界空间的 转换矩阵
                //       |    |     |
                //      切线 副切线 法线
                //       |    |     |
                //v2f.rotation = float3x3( worldTangent.x, worldBinormal.x,  worldNormal.x,
                //                          worldTangent.y, worldBinormal.y,  worldNormal.y,
                //                          worldTangent.z, worldBinormal.z,  worldNormal.z);
                v2f.TtoW0 = float4(worldTangent.x, worldBinormal.x, worldNormal.x, worldPos.x);
                v2f.TtoW1 = float4(worldTangent.y, worldBinormal.y, worldNormal.y, worldPos.y);
                v2f.TtoW2 = float4(worldTangent.z, worldBinormal.z, worldNormal.z, worldPos.z);

                //坐标转换宏
                TRANSFER_SHADOW(v2f)

                return v2f;
            }

            fixed4 frag(v2f v2f) : SV_Target
            {
                //世界空间下光的方向
                fixed3 lightDir = normalize(_WorldSpaceLightPos0.xyz);

                //世界空间下视角方向
                // fixed3 viewDir = normalize(UnityWorldSpaceViewDir(v2f.worldPos));
                float3 worldPos = float3(v2f.TtoW0.w, v2f.TtoW1.w, v2f.TtoW2.w);
                fixed3 viewDir = normalize(UnityWorldSpaceViewDir(worldPos));

                //纹理采样函数tex2D
                //通过纹理采样函数 取出法线纹理贴图当中的数据
                float4 packedNormal = tex2D(_BumpMap, v2f.uv.zw);

                //利用内置的UnpackNormal函数对法线信息进行逆运算以及可能的解压
                //将我们取出来的法线数据 进行逆运算并且可能会进行解压缩的运算,最终得到切线空间下的法线数据
                float3 tangentNormal = UnpackNormal(packedNormal);

                //乘以凹凸程度的系数
                tangentNormal.xy *= _BumpScale;
                tangentNormal.z = sqrt(1.0 - saturate(dot(tangentNormal.xy, tangentNormal.xy)));


                //把计算完毕后的切线空间下的法线转换到世界空间下
                // float3 worldNormal = mul(v2f.rotation, tangentNormal);

                //以下两种写法等价 但是可以一步到位
                //float3x3 rotation = float3x3(v2f.TtoW0.xyz, v2f.TtoW1.xyz, v2f.TtoW2.xyz );
                //float3 worldNormal = mul(rotation, tangentNormal);
                //本质 就是在进行矩阵运算
                float3 worldNormal = float3(dot(v2f.TtoW0.xyz, tangentNormal), dot(v2f.TtoW1.xyz, tangentNormal),
                                             dot(v2f.TtoW2.xyz, tangentNormal));


                //接下来就来处理 带颜色纹理的 布林方光照模型计算

                //颜色纹理和漫反射颜色的 叠加
                fixed3 albedo = tex2D(_MainTex, v2f.uv.xy) * _MainColor.rgb;

                //兰伯特漫反射颜色 = 光的颜色 * 漫反射材质的颜色 * max(0, dot(世界坐标系下的法线, 光的方向))
                fixed3 lambertColor = _LightColor0.rgb * albedo.rgb * max(0, dot(worldNormal, normalize(lightDir)));

                //不使用高光了
                // //半角向量 = 视角方向 + 光的方向
                // float3 halfA = normalize(normalize(viewDir) + normalize(lightDir));
                //
                // //高光反射的颜色 = 光的颜色 * 高光反射材质的颜色 * pow(max(0, dot(世界坐标系下的法线, 半角向量)), 光泽度)
                // fixed3 specularColor = _LightColor0.rgb * _SpecularColor.rgb * pow(
                //     max(0, dot(worldNormal, halfA)), _SpecularNum);

                //计算光照衰减和阴影衰减的宏
                UNITY_LIGHT_ATTENUATION(atten, v2f, worldPos);

                // //布林方光照颜色 = 环境光颜色 + 兰伯特漫反射颜色 + 高光反射的颜色
                // fixed3 color = UNITY_LIGHTMODEL_AMBIENT.rgb * albedo + lambertColor + specularColor;

                //布林方光照颜色 = 环境光颜色 + 兰伯特漫反射颜色*衰减值
                fixed3 color = UNITY_LIGHTMODEL_AMBIENT.rgb * albedo + lambertColor * atten;

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

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

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

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            //这个编译指令让Unity生成多个包括支持和不支持阴影的Shader变体
            //从而为额外的逐像素光源计算阴影,并传递给Shader了
            #pragma multi_compile_fwdadd_fullshadows

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

            //使用阴影宏内置文件
            #include "AutoLight.cginc"

            float4 _MainColor; //漫反射颜色
            sampler2D _MainTex; //颜色纹理
            float4 _MainTex_ST; //颜色纹理的缩放和平移
            sampler2D _BumpMap; //法线纹理
            float4 _BumpMap_ST; //法线纹理的缩放和平移
            float _BumpScale; //凹凸程度
            // float4 _SpecularColor; //高光颜色
            // fixed _SpecularNum; //光泽度

            struct v2f
            {
                //裁剪空间下坐标
                float4 pos:SV_POSITION;

                //纹理uv

                //我们可以单独的声明两个float2的成员用于记录 颜色和法线纹理的uv坐标
                //float2 uvTex:TEXCOORD0;//颜色纹理
                //float2 uvBump:TEXCOORD1;//法线纹理

                //也可以直接声明一个float4的成员 xy用于记录颜色纹理的uv,zw用于记录法线纹理的uv
                float4 uv:TEXCOORD0; //纹理变量


                //顶点相对于世界坐标的位置 主要用于 之后的 视角方向的计算
                //float3 worldPos:TEXCOORD1;

                //切线 到 世界空间的 变换矩阵
                //float3x3 rotation:TEXCOORD2;

                //代表我们切线空间到世界空间的 变换矩阵的3行 三个变量的w存世界坐标
                float4 TtoW0:TEXCOORD1;
                float4 TtoW1:TEXCOORD2;
                float4 TtoW2:TEXCOORD3;


                //阴影坐标宏
                //n为下一个可用的插值寄存器的索引值(结构体前面有几个TEXCOORD就填几)
                //比如这里前面有四个
                SHADOW_COORDS(4)
            };

            v2f vert(appdata_full v)
            {
                v2f v2f;

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

                //计算纹理的缩放偏移
                v2f.uv.xy = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
                v2f.uv.zw = v.texcoord.xy * _BumpMap_ST.xy + _BumpMap_ST.zw;

                //得到世界空间下的 顶点位置 用于之后在片元函数中计算视角方向(世界空间下的)
                //v2f.worldPos = mul(unity_ObjectToWorld, appdata_full.vertex);
                float3 worldPos = mul(unity_ObjectToWorld, v.vertex);

                //把模型空间下的法线、切线转换到世界空间下
                float3 worldNormal = UnityObjectToWorldNormal(v.normal);
                float3 worldTangent = UnityObjectToWorldDir(v.tangent);

                //计算副切线 计算叉乘结果后 垂直与切线和法线的向量有两条 通过乘以 切线当中的w,就可以确定是哪一条
                //cross是叉乘 appdata_full.tangent.w代表叉乘结果方向
                float3 worldBinormal = cross(normalize(worldTangent), normalize(worldNormal)) * v.tangent.w;

                //这个就是我们 切线空间到世界空间的 转换矩阵
                //       |    |     |
                //      切线 副切线 法线
                //       |    |     |
                //v2f.rotation = float3x3( worldTangent.x, worldBinormal.x,  worldNormal.x,
                //                          worldTangent.y, worldBinormal.y,  worldNormal.y,
                //                          worldTangent.z, worldBinormal.z,  worldNormal.z);
                v2f.TtoW0 = float4(worldTangent.x, worldBinormal.x, worldNormal.x, worldPos.x);
                v2f.TtoW1 = float4(worldTangent.y, worldBinormal.y, worldNormal.y, worldPos.y);
                v2f.TtoW2 = float4(worldTangent.z, worldBinormal.z, worldNormal.z, worldPos.z);

                //坐标转换宏
                TRANSFER_SHADOW(v2f)

                return v2f;
            }

            fixed4 frag(v2f v2f) : SV_Target
            {
                //世界空间下光的方向
                fixed3 lightDir = normalize(_WorldSpaceLightPos0.xyz);

                //世界空间下视角方向
                // fixed3 viewDir = normalize(UnityWorldSpaceViewDir(v2f.worldPos));
                float3 worldPos = float3(v2f.TtoW0.w, v2f.TtoW1.w, v2f.TtoW2.w);
                fixed3 viewDir = normalize(UnityWorldSpaceViewDir(worldPos));

                //纹理采样函数tex2D
                //通过纹理采样函数 取出法线纹理贴图当中的数据
                float4 packedNormal = tex2D(_BumpMap, v2f.uv.zw);

                //利用内置的UnpackNormal函数对法线信息进行逆运算以及可能的解压
                //将我们取出来的法线数据 进行逆运算并且可能会进行解压缩的运算,最终得到切线空间下的法线数据
                float3 tangentNormal = UnpackNormal(packedNormal);

                //乘以凹凸程度的系数
                tangentNormal.xy *= _BumpScale;
                tangentNormal.z = sqrt(1.0 - saturate(dot(tangentNormal.xy, tangentNormal.xy)));


                //把计算完毕后的切线空间下的法线转换到世界空间下
                // float3 worldNormal = mul(v2f.rotation, tangentNormal);

                //以下两种写法等价 但是可以一步到位
                //float3x3 rotation = float3x3(v2f.TtoW0.xyz, v2f.TtoW1.xyz, v2f.TtoW2.xyz );
                //float3 worldNormal = mul(rotation, tangentNormal);
                //本质 就是在进行矩阵运算
                float3 worldNormal = float3(dot(v2f.TtoW0.xyz, tangentNormal), dot(v2f.TtoW1.xyz, tangentNormal),
                                             dot(v2f.TtoW2.xyz, tangentNormal));


                //接下来就来处理 带颜色纹理的 布林方光照模型计算

                //颜色纹理和漫反射颜色的 叠加
                fixed3 albedo = tex2D(_MainTex, v2f.uv.xy) * _MainColor.rgb;

                //兰伯特漫反射颜色 = 光的颜色 * 漫反射材质的颜色 * max(0, dot(世界坐标系下的法线, 光的方向))
                fixed3 lambertColor = _LightColor0.rgb * albedo.rgb * max(0, dot(worldNormal, normalize(lightDir)));

                //不使用高光了
                // //半角向量 = 视角方向 + 光的方向
                // float3 halfA = normalize(normalize(viewDir) + normalize(lightDir));
                //
                // //高光反射的颜色 = 光的颜色 * 高光反射材质的颜色 * pow(max(0, dot(世界坐标系下的法线, 半角向量)), 光泽度)
                // fixed3 specularColor = _LightColor0.rgb * _SpecularColor.rgb * pow(
                //     max(0, dot(worldNormal, halfA)), _SpecularNum);

                //计算光照衰减和阴影衰减的宏
                UNITY_LIGHT_ATTENUATION(atten, v2f, worldPos);

                // //布林方光照颜色 = 环境光颜色 + 兰伯特漫反射颜色 + 高光反射的颜色
                // fixed3 color = UNITY_LIGHTMODEL_AMBIENT.rgb * albedo + lambertColor + specularColor;

                //布林方光照颜色 = 环境光颜色 + 兰伯特漫反射颜色*衰减值
                fixed3 color = UNITY_LIGHTMODEL_AMBIENT.rgb * albedo + lambertColor * atten;

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

    //Unity自带的漫反射Shader,里面实现了对应投射阴影的Pass
    Fallback "Diffuse"
}


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

×

喜欢就点赞,疼爱就打赏