Unity中Shader的光照衰减

文章目录

  • 前言
  • 一、衰减原理
    • 1、使用一张黑白渐变贴图用于纹理采样
    • 2、把模型从世界坐标转化为灯光坐标(即以灯光为原点的坐标系)
    • 3、用转化后的模型坐标,对黑白渐变纹理进行纹理采样
    • 4、最后,把采样后的结果与光照模型公式的结果相乘输出
  • 二、光照衰减实现
    • 1、Unity内部已经给我们提供了一张非线性黑白渐变的UV贴图
    • 2、把模型从世界坐标转化到灯光坐标下(使用矩阵相乘实现转化的效果)
    • 3、使用Unity自带的光照衰减贴图进行纹理采样
    • 4、最终效果
    • 测试代码
  • 三、使用Unity自带的方法,实现光源的衰减效果
    • 最终代码


前言

Unity中Shader的光照衰减


一、衰减原理

1、使用一张黑白渐变贴图用于纹理采样

2、把模型从世界坐标转化为灯光坐标(即以灯光为原点的坐标系)

3、用转化后的模型坐标,对黑白渐变纹理进行纹理采样

4、最后,把采样后的结果与光照模型公式的结果相乘输出

Unity中Shader的光照衰减_第1张图片


二、光照衰减实现

1、Unity内部已经给我们提供了一张非线性黑白渐变的UV贴图

这张UV贴图名字是固定的:_LightTexture0
注意:需要引入库 AutoLight.cginc

使用模型的uv进行采样,看看这张图大概的样子
fixed atten = tex2D(_LightTexture0,i.uv);
return atten;

把这个Shader的材质球给一个面片就可以看见这张渐变图
Unity中Shader的光照衰减_第2张图片

但是,这个测试效果并不是我们需要的
Unity中Shader的光照衰减_第3张图片

2、把模型从世界坐标转化到灯光坐标下(使用矩阵相乘实现转化的效果)

1.在 v2f 中定义一个 float3 类型的 TEXCOORD,来存放顶点坐标转化到世界坐标之后坐标信息

float3 worldPos : TEXCOORD2;

2.在顶点着色器中,把模型顶点的本地坐标转化为世界坐标(使用了unity_ObjectToWorld矩阵)

o.worldPos = mul(unity_ObjectToWorld,v.vertex);

3.把模型顶点从世界坐标转化为灯光坐标(使用了unity_WorldToLight矩阵)

//因为转化时使用的是4行的矩阵,所以 要把模型的顶点坐标增加一个w = 1,使坐标转化准确
float3 lightCoord = mul(unity_WorldToLight,float4(i.worldPos,1)).xyz;

在这里,我们输出一下lightCoord的灰度图看一下效果

return lightCoord.x;

3、使用Unity自带的光照衰减贴图进行纹理采样

fixed atten = tex2D(_LightTexture0,dot(lightCoord,lightCoord));

注意:这里的纹理采样不能直接使用lightCoord
可以这样理解,我们需要的效果是灯光靠近模型之后
越近,采样越靠uv的左边,灯光越亮(白色)
那么我们就可以使用lightCoord的点积来给光照衰减uv进行纹理采样
不使用模长的原因:向量点积计算量比计算模长计算量小

可以由下图理解,当 a 模长越小,dot(a,a)越小
则在纹理采样时,越靠近纹理的左边(白色)
Unity中Shader的光照衰减_第4张图片

5.最后,使用纹理采样后的结果和光的颜色相乘来模拟光照衰减

fixed4 LightColor = _LightColor0 * atten;

4、最终效果

测试代码

Shader "MyShader/P1_5_5"
{
    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"
            //使用光照衰减贴图,需要引入 AutoLight.cginc 库
            #include "AutoLight.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;
                //定义一个三维向量,用于存放模型顶点 从本地坐标 转化为 世界坐标
                float3 worldPos : TEXCOORD2;
            };

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

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

                //把模型顶点从世界坐标转化为灯光坐标
                //unity_WorldToLight
                //从世界空间转换到灯光空间下,等同于旧版的_LightMatrix0
                //因为转化时使用的是4行的矩阵,所以 要把模型的顶点坐标增加一个w = 1,使坐标转化准确
                float3 lightCoord = mul(unity_WorldToLight,float4(i.worldPos,1)).xyz;
                //return lightCoord.x;
                //使用Unity自带的光照衰减贴图进行纹理采样
                fixed atten = tex2D(_LightTexture0,dot(lightCoord,lightCoord));
                
                
                //获取主平行光的颜色
                fixed4 LightColor = _LightColor0 * atten;
                //获取顶点法线坐标(让其归一化)
                fixed3 N = normalize(i.worldNormal);
                //获取反射点指向光源的向量(因为内置了获取的方法,所以不用向量减法来计算)
                fixed3 L = _WorldSpaceLightPos0;
                //因为计算点光源时不需要考虑环境光,所以在Lambert光照模型中删除环境光的影响
                fixed4 Diffuse = LightColor * max(0,dot(N,L));
                
                return Diffuse;
                
            }
            ENDCG
        }
    }
}

效果:


三、使用Unity自带的方法,实现光源的衰减效果

自己写光照衰减 和 使用Unity自带的方法 实现光照衰减需要根据情况而定

Unity中Shader的光照衰减_第5张图片
destName:out用于存放衰减值得变量
input:用于控制阴影的变量(目前用不上,传入0)
worldPos:模型的世界坐标

UNITY_LIGHT_ATTENUATION(atten,0,i.worldPos)

最终代码

Shader "MyShader/P1_5_5"
{
    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"
            //使用光照衰减贴图,需要引入 AutoLight.cginc 库
            #include "AutoLight.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;
                //定义一个三维向量,用于存放模型顶点 从本地坐标 转化为 世界坐标
                float3 worldPos : TEXCOORD2;
            };

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

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

                //把模型顶点从世界坐标转化为灯光坐标
                //unity_WorldToLight
                //从世界空间转换到灯光空间下,等同于旧版的_LightMatrix0
                //因为转化时使用的是4行的矩阵,所以 要把模型的顶点坐标增加一个w = 1,使坐标转化准确
                //float3 lightCoord = mul(unity_WorldToLight,float4(i.worldPos,1)).xyz;
                //return lightCoord.x;
                //使用Unity自带的光照衰减贴图进行纹理采样
                //fixed atten = tex2D(_LightTexture0,dot(lightCoord,lightCoord));
                

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

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