文章目录
- 前言
- 一、衰减原理
- 1、使用一张黑白渐变贴图用于纹理采样
- 2、把模型从世界坐标转化为灯光坐标(即以灯光为原点的坐标系)
- 3、用转化后的模型坐标,对黑白渐变纹理进行纹理采样
- 4、最后,把采样后的结果与光照模型公式的结果相乘输出
- 二、光照衰减实现
- 1、Unity内部已经给我们提供了一张非线性黑白渐变的UV贴图
- 2、把模型从世界坐标转化到灯光坐标下(使用矩阵相乘实现转化的效果)
- 3、使用Unity自带的光照衰减贴图进行纹理采样
- 4、最终效果
- 测试代码
- 三、使用Unity自带的方法,实现光源的衰减效果
- 最终代码
前言
Unity中Shader的光照衰减
一、衰减原理
1、使用一张黑白渐变贴图用于纹理采样
2、把模型从世界坐标转化为灯光坐标(即以灯光为原点的坐标系)
3、用转化后的模型坐标,对黑白渐变纹理进行纹理采样
4、最后,把采样后的结果与光照模型公式的结果相乘输出
二、光照衰减实现
1、Unity内部已经给我们提供了一张非线性黑白渐变的UV贴图
这张UV贴图名字是固定的:_LightTexture0
注意:需要引入库 AutoLight.cginc
使用模型的uv进行采样,看看这张图大概的样子
fixed atten = tex2D(_LightTexture0,i.uv);
return atten;
把这个Shader的材质球给一个面片就可以看见这张渐变图
但是,这个测试效果并不是我们需要的
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)越小
则在纹理采样时,越靠近纹理的左边(白色)
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 OneCGPROGRAM#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 POINTreturn fixed4(0,1,0,1);#elif SPOTreturn 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自带的方法 实现光照衰减需要根据情况而定
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 OneCGPROGRAM#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 POINTreturn fixed4(0,1,0,1);#elif SPOTreturn 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}}
}