Unity中Shader不同灯光类型的支持与区分

文章目录

  • 前言
  • 一、在开始之前做一些准备
    • 1、在上一篇文章的场景基础上,增加一个Unity默认的球体作为对照组
    • 2、创建一个点光源,用来看点光源的影响 对 Unity默认的Shader效果 和 我们实现的Shader效果 之间的不同
  • 二、点光源的适配
    • 把上一篇文章中 ForwardBase 的 Pass 复制粘贴 到 与 该Pass平行的程序块,然后再对其做之后点光源的灯光适配(因为点光源 和 聚光灯效果,是在ForwordAdd中实现的)
    • 按上面步骤修改后,小球变的 受点光源的影响 又受 主平行光的影响 ![请添加图片描述](https://img-blog.csdnimg.cn/ffaccd6cfcea4830b694372b23d36a5a.gif)
  • 三、不同灯光类型的支持与区分
    • 1、我们加入一个聚光灯
    • 2、使用内置的宏定义生成Shader变体来区分是什么类型的光照
    • 3、剔除无用的变体,节省性能
  • 最终测试代码:


前言

Unity中Shader不同灯光类型的支持与区分


一、在开始之前做一些准备

1、在上一篇文章的场景基础上,增加一个Unity默认的球体作为对照组

创建前:
Unity中Shader不同灯光类型的支持与区分_第1张图片
创建后:
Unity中Shader不同灯光类型的支持与区分_第2张图片

2、创建一个点光源,用来看点光源的影响 对 Unity默认的Shader效果 和 我们实现的Shader效果 之间的不同

二、点光源的适配

把上一篇文章中 ForwardBase 的 Pass 复制粘贴 到 与 该Pass平行的程序块,然后再对其做之后点光源的灯光适配(因为点光源 和 聚光灯效果,是在ForwordAdd中实现的)

Shader "MyShader/P1_5_4"
{
    Properties
    {
        //光照系数
        _DiffuseIntensity("Diffuse Intensity",float) = 1
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        
        Pass
        {
            Tags{"LightMode"="ForwardBase"}
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            
            #include "UnityCG.cginc"
            #include "Lighting.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
                //在应用程序阶段传入到顶点着色器中,时加入顶点法向量信息
                half3 normal:NORMAL;
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
                //定义一个3维向量,用于接受世界坐标顶点法向量信息
                half3 worldNormal:TEXCOORD1;
                
            };

            half _DiffuseIntensity;
            
            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                //把顶点法线本地坐标转化为世界坐标
                o.worldNormal = UnityObjectToWorldNormal(v.normal);
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                //Lambert光照模型的结果
                //Diffuse = Ambient + Kd * LightColor * max(0,dot(N,L))
                //使用 Unity 封装的参数 获取环境光色
                float Ambient = unity_AmbientSky;

                //在属性面板定义一个 可调节的参数 用来作为光照系数,调节效果的强弱
                half Kd = _DiffuseIntensity;

                //获取主平行光的颜色
                fixed4 LightColor = _LightColor0;

                //获取顶点法线坐标(让其归一化)
                fixed3 N = normalize(i.worldNormal);

                //获取反射点指向光源的向量(因为内置了获取的方法,所以不用向量减法来计算)
                fixed3 L = _WorldSpaceLightPos0;

                //使用Lambert公式计算出光照
                //fixed4 Diffuse = Ambient + (Kd * LightColor * dot(N,L));
                //因为 当 顶点法线 与 反射点指向光源的向量 垂直 或成钝角时,光照效果就该忽略不计
                //所以,这里使用 max(a,b)函数来限制 点积的结果范围
                fixed4 Diffuse = Ambient + Kd * LightColor * max(0,dot(N,L));
                
                return Diffuse;
            }
            ENDCG
        }
        Pass
        {
            Tags{"LightMode"="ForwardAdd"}
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            
            #include "UnityCG.cginc"
            #include "Lighting.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
                //在应用程序阶段传入到顶点着色器中,时加入顶点法向量信息
                half3 normal:NORMAL;
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
                //定义一个3维向量,用于接受世界坐标顶点法向量信息
                half3 worldNormal:TEXCOORD1;
                
            };

            half _DiffuseIntensity;
            
            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                //把顶点法线本地坐标转化为世界坐标
                o.worldNormal = UnityObjectToWorldNormal(v.normal);
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                //Lambert光照模型的结果
                //Diffuse = Ambient + Kd * LightColor * max(0,dot(N,L))
                //使用 Unity 封装的参数 获取环境光色
                float Ambient = unity_AmbientSky;

                //在属性面板定义一个 可调节的参数 用来作为光照系数,调节效果的强弱
                half Kd = _DiffuseIntensity;

                //获取主平行光的颜色
                fixed4 LightColor = _LightColor0;

                //获取顶点法线坐标(让其归一化)
                fixed3 N = normalize(i.worldNormal);

                //获取反射点指向光源的向量(因为内置了获取的方法,所以不用向量减法来计算)
                fixed3 L = _WorldSpaceLightPos0;

                //使用Lambert公式计算出光照
                //fixed4 Diffuse = Ambient + (Kd * LightColor * dot(N,L));
                //因为 当 顶点法线 与 反射点指向光源的向量 垂直 或成钝角时,光照效果就该忽略不计
                //所以,这里使用 max(a,b)函数来限制 点积的结果范围
                fixed4 Diffuse = Ambient + Kd * LightColor * max(0,dot(N,L));
                
                return Diffuse;
            }
            ENDCG
        }
    }
}

把复制后的光照模式改为ForwardAdd

Tags{“LightMode”=“ForwardAdd”}

修改后,点光源对我们的小球已经有了初步的影响

但是,会发现不受主平行光的影响了,所以需要进行修改

并且,由于默认的混合模式为 Blend One Zero。
渲染时,由于主平行光先渲染,点光源后渲染,所以颜色缓冲区会被后渲染的点光源覆盖。
所以修改ForwordAdd 的 Pass 中 混合模式为 Blend One One

Blend One One

因为计算点光源时不需要考虑环境光,所以在Lambert光照模型中删除环境光的影响

在ForwardAdd的Pass中的片元着色器中,把最后的输出结果修改为如下:

fixed4 Diffuse = LightColor * max(0,dot(N,L));

把片元着色器简化为:

fixed4 frag (v2f i) : SV_Target
            {
                //获取主平行光的颜色
                fixed4 LightColor = _LightColor0;
                //获取顶点法线坐标(让其归一化)
                fixed3 N = normalize(i.worldNormal);
                //获取反射点指向光源的向量(因为内置了获取的方法,所以不用向量减法来计算)
                fixed3 L = _WorldSpaceLightPos0;
                //因为计算点光源时不需要考虑环境光,所以在Lambert光照模型中删除环境光的影响
                fixed4 Diffuse = LightColor * max(0,dot(N,L));
                
                return Diffuse;
            }

按上面步骤修改后,小球变的 受点光源的影响 又受 主平行光的影响

三、不同灯光类型的支持与区分

1、我们加入一个聚光灯

Unity中Shader不同灯光类型的支持与区分_第3张图片
加入聚光灯后,会发现小球只渲染了聚光灯的效果
原因是:
目前场景只支持一盏逐像素灯,在 聚光灯 和 点光源 之间,谁的强度大,谁就变成逐像素灯

Unity中Shader不同灯光类型的支持与区分_第4张图片

2、使用内置的宏定义生成Shader变体来区分是什么类型的光照

#pragma multi_compile_fwdadd
定义在LightMode=ForwardAdd的Pass中,在此Pass中用来计算其它的逐像素光照.而此指令的作用是一次性生成Unity在ForwardAdd中需要的各种内置宏.
DIRECTIONAL DIRECTIONAL_COOKIE POINT POINT_COOKIE SPOT

  1. DIRECTIONAL :判断当前灯是否为平行灯.
  2. DIRECTIONAL_COOKIE :判断当前灯是否为Cookie平行灯.
  3. POINT :判断当前灯是否为点灯.
  4. POINT_COOKIE :判断当前灯是否为Cookie点灯.
  5. SPOT :判断当前灯是否为聚光灯.

在 ForwardAdd 的 Pass 中加入这条宏

#pragma multi_compile_fwdadd

然后,我看可以看见该Shader生成了6个变体
Unity中Shader不同灯光类型的支持与区分_第5张图片

Unity中Shader不同灯光类型的支持与区分_第6张图片

我们在片元着色器中,测试使用一下这些变体
1、当为点光源时,返回绿色

fixed4 frag (v2f i) : SV_Target
            {
                #if POINT
                return fixed4(0,1,0,1);
                #endif

                
                //获取主平行光的颜色
                fixed4 LightColor = _LightColor0;
                //获取顶点法线坐标(让其归一化)
                fixed3 N = normalize(i.worldNormal);
                //获取反射点指向光源的向量(因为内置了获取的方法,所以不用向量减法来计算)
                fixed3 L = _WorldSpaceLightPos0;
                //因为计算点光源时不需要考虑环境光,所以在Lambert光照模型中删除环境光的影响
                fixed4 Diffuse = LightColor * max(0,dot(N,L));
                
                return Diffuse;
                
            }

Unity中Shader不同灯光类型的支持与区分_第7张图片

2、当为点光源时,返回黑色

fixed4 frag (v2f i) : SV_Target
            {
                #if POINT
                return fixed4(0,1,0,1);
                #elif SPOT
                return 0;
                #endif

                
                //获取主平行光的颜色
                fixed4 LightColor = _LightColor0;
                //获取顶点法线坐标(让其归一化)
                fixed3 N = normalize(i.worldNormal);
                //获取反射点指向光源的向量(因为内置了获取的方法,所以不用向量减法来计算)
                fixed3 L = _WorldSpaceLightPos0;
                //因为计算点光源时不需要考虑环境光,所以在Lambert光照模型中删除环境光的影响
                fixed4 Diffuse = LightColor * max(0,dot(N,L));
                
                return Diffuse;
                
            }

Unity中Shader不同灯光类型的支持与区分_第8张图片

3、剔除无用的变体,节省性能

因为Shader变体的数量是一般是倍数增加,所以在设计时,就要尽量减少Shader的变体数量
Shader变体的数量,会直接影响 ShaderLab 的内存,打包到手机会影响到 Native 内存

法一:手动声明我们需要的变体
#pragma multi_compile POINT SPOT	

Unity中Shader不同灯光类型的支持与区分_第9张图片
Unity中Shader不同灯光类型的支持与区分_第10张图片

法二:剔除不需要的变体
#pragma skip_variants XXX01 XXX02...
剔除指定的变体,可同时剔除多个

#pragma skip_variants DIRECTIONAL POINT_COOKIE DIRECTIONAL_COOKIE

效果是一样的:
Unity中Shader不同灯光类型的支持与区分_第11张图片

最终测试代码:

Shader "MyShader/P1_5_4"
{
    Properties
    {
        //光照系数
        _DiffuseIntensity("Diffuse Intensity",float) = 1
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        
        Pass
        {
            Tags{"LightMode"="ForwardBase"}
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            
            #include "UnityCG.cginc"
            #include "Lighting.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
                //在应用程序阶段传入到顶点着色器中,时加入顶点法向量信息
                half3 normal:NORMAL;
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
                //定义一个3维向量,用于接受世界坐标顶点法向量信息
                half3 worldNormal:TEXCOORD1;
                
            };

            half _DiffuseIntensity;
            
            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                //把顶点法线本地坐标转化为世界坐标
                o.worldNormal = UnityObjectToWorldNormal(v.normal);
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                //Lambert光照模型的结果
                //Diffuse = Ambient + Kd * LightColor * max(0,dot(N,L))
                //使用 Unity 封装的参数 获取环境光色
                float Ambient = unity_AmbientSky;

                //在属性面板定义一个 可调节的参数 用来作为光照系数,调节效果的强弱
                half Kd = _DiffuseIntensity;

                //获取主平行光的颜色
                fixed4 LightColor = _LightColor0;

                //获取顶点法线坐标(让其归一化)
                fixed3 N = normalize(i.worldNormal);

                //获取反射点指向光源的向量(因为内置了获取的方法,所以不用向量减法来计算)
                fixed3 L = _WorldSpaceLightPos0;

                //使用Lambert公式计算出光照
                //fixed4 Diffuse = Ambient + (Kd * LightColor * dot(N,L));
                //因为 当 顶点法线 与 反射点指向光源的向量 垂直 或成钝角时,光照效果就该忽略不计
                //所以,这里使用 max(a,b)函数来限制 点积的结果范围
                fixed4 Diffuse = Ambient + Kd * LightColor * max(0,dot(N,L));
                
                return Diffuse;
            }
            ENDCG
        }
        Pass
        {
            Tags{"LightMode"="ForwardAdd"}
            Blend One One
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            //加入Unity自带的宏,用于区分不同的光照
            //只声明我们需要的变体
            //#pragma multi_compile POINT SPOT
            
            #pragma multi_compile_fwdadd
            //剔除我们不需要的变体
            #pragma skip_variants DIRECTIONAL POINT_COOKIE DIRECTIONAL_COOKIE
            #include "UnityCG.cginc"
            #include "Lighting.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
                //在应用程序阶段传入到顶点着色器中,时加入顶点法向量信息
                half3 normal:NORMAL;
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
                //定义一个3维向量,用于接受世界坐标顶点法向量信息
                half3 worldNormal:TEXCOORD1;
                
            };

            half _DiffuseIntensity;
            
            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                //把顶点法线本地坐标转化为世界坐标
                o.worldNormal = UnityObjectToWorldNormal(v.normal);
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                #if POINT
                return fixed4(0,1,0,1);
                #elif SPOT
                return 0;
                #endif

                
                //获取主平行光的颜色
                fixed4 LightColor = _LightColor0;
                //获取顶点法线坐标(让其归一化)
                fixed3 N = normalize(i.worldNormal);
                //获取反射点指向光源的向量(因为内置了获取的方法,所以不用向量减法来计算)
                fixed3 L = _WorldSpaceLightPos0;
                //因为计算点光源时不需要考虑环境光,所以在Lambert光照模型中删除环境光的影响
                fixed4 Diffuse = LightColor * max(0,dot(N,L));
                
                return Diffuse;
                
            }
            ENDCG
        }
    }
}

你可能感兴趣的:(Unity,unity,游戏引擎)