11.多种光源综合实现

  1. 11.多种光源-综合实现
    1. 11.1 知识点
      1. 重要知识回顾
        1. 前向渲染路径中如何处理光源
        2. 在Shader当中如何判断多种光源
        3. 点光源衰减值计算
        4. 聚光灯衰减值计算
      2. 前向渲染路径中处理多种光源的综合实现
        1. 主要步骤
        2. 新建Shader文件并删除无用代码
        3. 复制Shader开发入门中的Blinn-Phong光照模型的逐片元光照 Lesson20_BlinnPhong_Pixel.shader。其中已存在的Pass,就是我们的BasePass(基础渲染通道)
        4. 基础渲染通道加上一个编译指令#pragma multi_compile_fwdbase,该指令可以保证我们在Shader中使用光照衰减等光照等变量可以被正确赋值,并且会帮助我们编译 BasePass 中所有变体
        5. 在基础渲染通道中的片元函数感受衰减值的作用,因为BasePass一般是处理场景中最亮的平行光,所以可以认为不存在衰减值的,衰减值默认是1。但是之后AdditionalPass的衰减值就会变化了
        6. 复制BasePass(基础渲染通道),基于它来修改我们的Additional Pass(附加渲染通道)
        7. 附加渲染通道LightMode 改为 ForwardAdd
        8. 附加渲染通道加入混合命令Blend One One 表示开启混合 线性减淡效果
        9. 附加渲染通道删除编译指令multi_compile_fwdbase,加入编译指令#pragma multi_compile_fwdadd。该指令保证我们在附加渲染通道中能访问到正确的光照变量,并且会帮助我们编译Additional Pass中所有变体
        10. 附加渲染通道删除getLambertColor函数和getSpecularColor函数,等一下自己重新在片元函数手写加深印象,并且会有一些修改
        11. 修改片元函数相关代码,基于不同的光照类型来计算兰伯特,BlinnPhong和衰减值
        12. 创建材质,创建多种光源可以查看到多光源shader可以收到不同类型光源叠加的效果。导入Shader开发入门的纹理_遮罩纹理_综合实现的shader,发现只能收到平行光
      3. 可能会出现无法准确判断光源类型的问题
        1. 主要问题
        2. 修改解决方案
        3. 附加渲染通道中添加引用AutoLight内置文件。我们将使用其中的光源类型判断宏,来判断光源类型。
        4. 修改光源类型判断宏相关代码。将光源类型判断宏判断逻辑进行修改,否则在处理聚光灯等光源时无法正确判断类型导致渲染效果错误
        5. 可以看到聚光灯被正确识别了
      4. 总结
    2. 11.2 知识点代码
      1. Lesson11_多种光源_综合实现.cs
      2. Lesson11_ForwardLighting.shader
      3. Lesson11_Mask_Texture_Comprehensive.shader

11.多种光源-综合实现


11.1 知识点

重要知识回顾

前向渲染路径中如何处理光源

  • 两个Pass:
    • Base Pass(基础渲染通道)
    • Additional Pass(附加渲染通道)

在Shader当中如何判断多种光源

#if defined(_DIRECTIONAL_LIGHT)
  // 平行光逻辑
#elif defined (_POINT_LIGHT)
  // 点光源逻辑
#elif defined (_SPOT_LIGHT)
  // 聚光灯逻辑
#else
  // 其他逻辑
#endif

点光源衰减值计算

float3 lightCoord = mul(unity_WorldToLight, float4(worldPos, 1)).xyz;
fixed atten = tex2D(_LightTexture0, dot(lightCoord, lightCoord).xx).UNITY_ATTEN_CHANNEL;

聚光灯衰减值计算

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; // 第三部分

前向渲染路径中处理多种光源的综合实现

主要步骤

  1. 新建一个Shader文件,删除其中无用代码。
  2. 复制Shader开发入门中的Blinn-Phong光照模型的逐片元光照Lesson20_BlinnPhong_Pixel.shader
  3. 已存在的Pass即为Base Pass(基础渲染通道)。添加编译指令#pragma multi_compile_fwdbase,确保光照衰减等变量被正确赋值并编译所有变体。
  4. 复制Base Pass并修改为Additional Pass(附加渲染通道)。
  5. 修改LightModeForwardAdd
  6. 添加混合命令Blend One One,开启线性减淡效果。
  7. 添加编译指令#pragma multi_compile_fwdadd,确保附加渲染通道变量正确赋值并编译所有变体。
  8. 修改相关代码,计算光照方向与衰减值。

新建Shader文件并删除无用代码

Shader "Unlit/Lesson11_ForwardLighting"
{
}

复制Shader开发入门中的Blinn-Phong光照模型的逐片元光照 Lesson20_BlinnPhong_Pixel.shader。其中已存在的Pass,就是我们的BasePass(基础渲染通道)

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

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            //引用对应的内置文件 
            //主要是为了之后 的 比如内置结构体使用,内置变量使用
            #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);
                
                //物体表面光照颜色 = 环境光颜色 + 兰伯特光照模型所得颜色 + 布林Phong式高光反射光照模型所得颜色
                fixed3 blinnPhongColor = UNITY_LIGHTMODEL_AMBIENT.rgb + lambertColor + specularColor;

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

基础渲染通道加上一个编译指令#pragma multi_compile_fwdbase,该指令可以保证我们在Shader中使用光照衰减等光照等变量可以被正确赋值,并且会帮助我们编译 BasePass 中所有变体

CGPROGRAM

#pragma vertex vert
#pragma fragment frag

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

    //...
ENDCG

在基础渲染通道中的片元函数感受衰减值的作用,因为BasePass一般是处理场景中最亮的平行光,所以可以认为不存在衰减值的,衰减值默认是1。但是之后AdditionalPass的衰减值就会变化了

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

复制BasePass(基础渲染通道),基于它来修改我们的Additional Pass(附加渲染通道)

SubShader
{
    //Bass Pass 基础渲染通道
    Pass
    {
        //...
    }
    

    //Additional Pass 附加渲染通道
    Pass
    {
        //...
    }

}

附加渲染通道LightMode 改为 ForwardAdd

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

附加渲染通道加入混合命令Blend One One 表示开启混合 线性减淡效果

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

附加渲染通道删除编译指令multi_compile_fwdbase,加入编译指令#pragma multi_compile_fwdadd。该指令保证我们在附加渲染通道中能访问到正确的光照变量,并且会帮助我们编译Additional Pass中所有变体

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

附加渲染通道删除getLambertColor函数和getSpecularColor函数,等一下自己重新在片元函数手写加深印象,并且会有一些修改

修改片元函数相关代码,基于不同的光照类型来计算兰伯特,BlinnPhong和衰减值

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

    
    //衰减值
    #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);
}

创建材质,创建多种光源可以查看到多光源shader可以收到不同类型光源叠加的效果。导入Shader开发入门的纹理_遮罩纹理_综合实现的shader,发现只能收到平行光

可能会出现无法准确判断光源类型的问题

主要问题

目前我们采用的宏判断,可能会出现无法准确判断光源类型的问题,导致探照灯在平面上显示时会出现问题。因此我们采用AutoLight内置文件中的宏来进行判断。需要对代码做出修改。

修改解决方案

  1. 引用AutoLight.cginc内置文件。
  2. 使用其光源类型判断宏替代现有判断逻辑。
  3. 修改代码确保渲染效果正确。

附加渲染通道中添加引用AutoLight内置文件。我们将使用其中的光源类型判断宏,来判断光源类型。

#include "AutoLight.cginc"

修改光源类型判断宏相关代码。将光源类型判断宏判断逻辑进行修改,否则在处理聚光灯等光源时无法正确判断类型导致渲染效果错误

#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

可以看到聚光灯被正确识别了

总结

  • 需要理解光源类型判断宏的逻辑。
  • 注意附加通道的混合与光衰减值计算。
  • 可扩展模型到动态范围。

11.2 知识点代码

Lesson11_多种光源_综合实现.cs

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

public class Lesson11_多种光源_综合实现 : MonoBehaviour
{
    void Start()
    {
        #region 重要知识回顾

        #region 1.前向渲染路径中如何处理光源

        //两个Pass
        //Base Pass(基础渲染通道)
        //Additional Pass(附加渲染通道)

        #endregion

        #region 2.在Shader当中如何判断多种光源

        //#if defined(_DIRECTIONAL_LIGHT)
        //  平行光逻辑
        //#elif defined (_POINT_LIGHT)
        //  点光源逻辑
        //#elif defined (_SPOT_LIGHT)
        //  聚光灯逻辑
        //#else
        //  其他逻辑
        //#endif

        #endregion

        #region 3.点光源衰减值计算

        // float3 lightCoord = mul(unity_WorldToLight, float4(worldPos, 1)).xyz;
        // fixed atten = tex2D(_LightTexture0, dot(lightCoord, lightCoord).xx).UNITY_ATTEN_CHANNEL;

        #endregion

        #region 4.聚光灯衰减值计算

        // 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; //第三步

        #endregion

        #endregion

        #region 知识点一 前向渲染路径中处理多种光源的综合实现

        //主要步骤:
        //1.新建一个Shader文件,删除其中无用代码

        //2.复制Shader开发入门中的Blinn-Phong光照模型的逐片元光照 Lesson20_BlinnPhong_Pixel.shader

        //3.其中已存在的Pass,就是我们的BasePass(基础渲染通道)
        //  我们需要为它加上一个编译指令#pragma multi_compile_fwdbase	
        //  该指令可以保证我们在Shader中使用光照衰减等光照等变量可以被正确赋值
        //  并且会帮助我们编译 BasePass 中所有变体

        //4.复制BasePass,基于它来修改我们的Additional Pass(附加渲染通道)

        //5.LightMode 改为 ForwardAdd

        //6.加入混合命令Blend One One 表示开启混合 线性减淡效果

        //7.加入编译指令#pragma multi_compile_fwdadd
        //  该指令保证我们在附加渲染通道中能访问到正确的光照变量
        //  并且会帮助我们编译Additional Pass中所有变体

        //8.修改相关代码,基于不同的光照类型来计算衰减值
        //  8-1:光的方向计算方向修改
        //  8-2:基于不同光照类型计算衰减值

        #endregion

        #region 知识点二 可能会出现无法准确判断光源类型的问题

        // 主要问题:
        // 目前我们采用的宏判断,可能会出现无法准确判断光源类型的问题
        // 导致探照灯在平面上显示时会出现问题,因此我们采用AutoLight内置文件中的宏来进行判断
        //
        // 需要对代码做出如下修改:
        // 1.添加引用一个内置文件
        // 2.修改光源类型判断宏相关代码

        #endregion
    }
}

Lesson11_ForwardLighting.shader

Shader "Unlit/Lesson11_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
        }
    }
}

Lesson11_Mask_Texture_Comprehensive.shader

Shader "Unlit/Lesson11_Mask_Texture_Comprehensive"
{
    Properties
    {
        _MainColor("MainColor", Color) = (1,1,1,1)
        _MainTex("MainTex", 2D) = ""{}
        _BumpMap("BumpMap", 2D) = ""{}
        _BumpScale("BumpScale", Range(0,1)) = 1

        _SpecularMask("SpecularMask", 2D) = ""{}//高光遮罩纹理
        _SpecularScale("SpecularScale", Float) = 1 //遮罩系数

        _SpecularColor("SpecularColor", Color) = (1,1,1,1)
        _SpecularNum("SpecularNum", Range(8,256)) = 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; //凹凸程度

            sampler2D _SpecularMask; //高光遮罩纹理
            float4 _SpecularMask_ST; //高光遮罩纹理的缩放和平移
            float _SpecularScale; //遮罩系数

            float4 _SpecularColor; //高光颜色
            fixed _SpecularNum; //光泽度

            struct v2f
            {
                float4 pos:SV_POSITION;
                //float2 uvTex:TEXCOORD0;
                //float2 uvBump:TEXCOORD1;
                //我们可以单独的声明两个float2的成员用于记录 颜色和法线纹理的uv坐标
                //也可以直接声明一个float4的成员 xy用于记录颜色纹理的uv,zw用于记录法线纹理的uv
                float4 uv:TEXCOORD0;
                //光的方向 相对于切线空间下的
                float3 lightDir:TEXCOORD1;
                //视角的方向 相对于切线空间下的
                float3 viewDir:TEXCOORD2;
            };


            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;

                //在顶点着色器当中 得到 模型空间到切线空间的 转换矩阵
                //切线、副切线、法线
                //计算副切线 计算叉乘结果后 垂直与切线和法线的向量有两条 通过乘以 切线当中的w,就可以确定是哪一条
                float3 binormal = cross(normalize(appdata_full.tangent), normalize(appdata_full.normal)) * appdata_full.
                    tangent.w;
                //转换矩阵
                float3x3 rotation = float3x3(appdata_full.tangent.xyz,
                                             binormal,
                                             appdata_full.normal);
                //模型空间下的光的方向
                //data.lightDir = ObjSpaceLightDir(v.vertex);
                //乘以模型空间到切线空间的转换矩阵 就可以得到切线空间下的 光的方向了
                v2f.lightDir = mul(rotation, ObjSpaceLightDir(appdata_full.vertex));

                //模型空间下的视角的方向
                //data.viewDir = ObjSpaceViewDir(v.vertex);
                v2f.viewDir = mul(rotation, ObjSpaceViewDir(appdata_full.vertex));

                return v2f;
            }

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

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

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

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

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

                //兰伯特
                fixed3 lambertColor = _LightColor0.rgb * albedo.rgb
                    * max(0, dot(tangentNormal, normalize(v2f.lightDir)));

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

                //1.从纹理中取出对应的遮罩掩码值(颜色的RGB值都可以使用)
                // fixed maskNum = tex2D(_SpecularMask, v2f.uv.xy).r;

                //2.用该掩码值和遮罩系数(我们自己定义的)相乘得到遮罩值
                // fixed specularMaskNum = maskNum * _SpecularScale;
                fixed specularMaskNum = tex2D(_SpecularMask, v2f.uv.xy).r * _SpecularScale;
                
                //3.用该遮罩值和高光反射计算出来的颜色相乘 * specularMaskNum
                //高光反射
                fixed3 specularColor = _LightColor0.rgb * _SpecularColor.rgb
                    * pow(max(0, dot(tangentNormal, halfA)), _SpecularNum) * specularMaskNum;

                //布林方
                fixed3 color = UNITY_LIGHTMODEL_AMBIENT.rgb * albedo + lambertColor + specularColor;

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


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

×

喜欢就点赞,疼爱就打赏