——内容源自唐老狮的shader课程
目录
1.光源类型
2.判断光源类型
2.1.在哪判断
2.2.如何判断
3.光照衰减
3.1.基本概念
3.2.unity中的光照衰减
3.3.光源空间变换矩阵
4.点光源衰减计算
5.聚光灯衰减计算
5.2.聚光灯衰减计算
6.应用
7.如有疏漏,还请指出
1.光源类型
平行光:没有位置,没有衰减,只有方向,颜色,强度参与计算
点光源:五个属性都需要考虑
聚光灯:五个属性都需要考虑,并且因为他的范围(由Range和Spot Angle共同决定)特殊,所以需要进行更复杂的运算
面光源:在烘培时使用
2.判断光源类型
2.1.在哪判断
我们一般在Additional Pass中判断光源类型以分别处理各部分的逻辑,因为不同光源的处理不一样
2.2.如何判断
unity提供了三个宏:
- _DIRECTIONAL_LIGHT(平行光)
- _POINT_LIGHT(点光源)
- _SPOT_LIGHT(聚光灯)
//在CG中使用条件语句来分别处理
#if defined(_DIRECTIONAL_LIGHT)
平行光逻辑
#elif defined(_POINT_LIGHT)
点光源逻辑
#elif defined(_SPOT_LIGHT)
聚光灯逻辑
#else
其他逻辑
#endif
unity底层会根据该条件编译指令,生成多个 Shader Variants(着色器变体),这些变体共享相同的核心代码,但会根据条件选择执行不同的代码块。
Shader Variants是在编写Shader时根据不同的配置(如条件编译指令下不同的条件)生成的多个版本的Shader
3.光照衰减
3.1.基本概念
通常是指渲染过程中考虑光线在传播过程中的减弱效应。
一般常见的光照衰减计算方式有:线性衰减和平方衰减,后者更符合现实
3.2.unity中的光照衰减
为了提高性能,我们一般不会直接通过数学公式计算衰减,而是使用一张纹理作为查找表,在片元着色器中计算逐像素光照的衰减。
为此,unity准备了一个内置的纹理类型的变量 _LightTexture0,unity会在内部计算好相关数据并存储到这个变量中。其上的纹理颜色值,表明了光源空间中不同位置的点对应的衰减值。
起点(0,0)位置,表明和光源重合的点的衰减值
终点(1,1)位置,表明光源空间中离光源最远的点的衰减值
一般我们会直接从_LightTexture0纹理中进行纹理采样,利用其中的UNITY_ATTEN_CHANNE宏来获得衰减值所在的分量,即:
tex2D(_LightTexture0,,对应纹理坐标).UNITY_ATTEN_CHANNEL
要注意的是:如果光源存在cookie(灯光遮罩),那么衰减查找纹理就变成_LightTextureB0
3.3.光源空间变换矩阵
由于我们要从光照纹理中取得对应的衰减数据,因此我们需要将顶点坐标从世界空间变换到光源空间,然后再去获取衰减数据,使用如下公式即可:
mul(unity_WorldToLight,世界空间下顶点坐标)
4.点光源衰减计算
1.将顶点从世界空间下转换到光源空间,并且只取xyz分量,也就是说lightCoord是一个float3的变量。需要了解的是,unity_WorldToLight计算所得的是一个规范在0~1之间的值,这个值可视为与光源的距离
2.利用该光源空间的坐标来计算离光源的距离,并利用距离参数,从衰减纹理中采样
fixed atten = tex2D(_LightTexture0,dot(lightCoord,lightCoord).xx).UNITY_ATTEN_CHANNEL
纹理坐标使用distance ^ 2是为了符合现实。ligthCoord是光源空间下顶点位置
5.聚光灯衰减计算
5.1.聚光灯的cookie(灯光遮罩)
unity会默认为聚光灯弄个cookie,这玩意主要是用来模拟聚光灯的区域性,就像打手电一样
此时,_LightTexture0 存储的变为cookie纹理信息,_LightTextureB0 存储的才是光照纹理信息,也就是说衰减值要从这里面取
5.2.聚光灯衰减计算
1.将顶点从世界空间转换到光源空间,并且这里的lightCoord是一个float4变量,也就是取了w
2.利用光源空间下的坐标信息与一些数据获取聚光灯的衰减信息,公式为(好长):
fixed atten = (lightCoord.z > 0) *
tex2D(_LightTexture0, lightCoord.xy / lightCoord.w + 0.5).w *
tex2D(_LightTextureB0, dot(lightCoord, lightCoord).xx).UNITY_ATTEN_CHANNEL
第一行:判断物体是不是在聚光灯的背面,如果在背面返回值是0,即不受聚光灯影响
第二行:后半部分就是把xy变到01区间内,xy/w能变到-0.5~0.5之间,加个0.5就正好,然后对遮罩纹理采样获取遮罩衰减值(就是那个w)。为什么要这么变换可以画个图根据相似三角形推导一下
第三行:正常取出基于距离的光照衰减值,需要了解的是,dot不会计算w
6.应用
1.为BasePass添加编译指令 #pragma multi_compile_fwdbase
为AdditionalPass添加编译指令 #pragma multi_compile_fwdadd
这些编译指令会帮我们编译对应Pass中所有变体,并且附加通道的编译指令还可以确保在附加渲染通道中能访问到正确的光照变量
2.在附加通道中加入混合命令 Blend One One,即线性简单效果
3.根据条件判断语句,基于不同的光照类型计算光的方向和衰减值。方向是因为点光源和聚光灯需要用光源位置减去顶点位置,平行光不用
4.计算最终颜色,附加通道中的最后颜色不需要再加环境光,只需要将满反射颜色和高光反射颜色相加然后乘以衰减值即可
//这是附加通道
Pass
{
Tags { "LightMode" = "ForwardAdd" }
Blend One One
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile_fwdadd
#include "Lighting.cginc"
#include "UnityCG.cginc"
#include "AutoLight.cginc"
fixed4 _DiffuseColor;
fixed4 _SpecularColor;
float _Gloss;
struct v2f
{
float4 pos : SV_POSITION;
float3 wNormal : NORMAL;
float3 wPos : TEXCOORD0;
};
v2f vert(appdata_full v)
{
v2f data;
data.pos = UnityObjectToClipPos(v.vertex);
data.wNormal = UnityObjectToWorldNormal(v.normal);
float3 wPos = mul(unity_ObjectToWorld, v.vertex).xyz;
data.wPos = wPos;
return data;
}
fixed4 frag(v2f f) : SV_TARGET
{
float3 wNormal = normalize(f.wNormal);
float3 wLightDir = float3(0, 0, 0);
#if defined(_DIRECTIONAL_LIGHT)
wLightDir = normalize(_WorldSpaceLightPos0);
#else
// 点光源和聚光灯的灯光方向需要用 位置 减去 顶点位置
wLightDir = normalize(_WorldSpaceLightPos0.xyz - f.wPos);
#endif
float3 wViewDir = normalize(_WorldSpaceCameraPos - f.wPos).xyz;
float3 halfAngle = normalize(wLightDir + wViewDir);
fixed3 lambertColor = _LightColor0.rgb * _DiffuseColor.rgb * max(0, dot(wNormal, wLightDir));
fixed3 blinn_PhongColor = _LightColor0.rgb * _SpecularColor.rgb * pow(max(0, dot(wNormal, halfAngle)), _Gloss);
//这个条件判断不止一种写法,还有别的写法
#if defined(_DIRECTIONAL_LIGHT)
float atten = 1;
#else
#if defined(_POINT_LIGHT)
//注意由世界坐标向空间左边转换时,将wPos作为四维向量转换并只取xyz(因为w没用)
float3 lightCoord = mul(unity_WorldToLight, float4(f.wPos, 1)).xyz;
float atten = tex2D(_LightTexture0, dot(lightCoord, lightCoord).xx).UNITY_ATTEN_CHANNEL;
#elif defined(_SPOT_LIGHT)
//这里w就有用了
float4 lightCoord = mul(unity_WorldToLight, float4(f.wPos, 1));
//1.判断能否照到; 2.映射到最大平面; 3.距离的平方采样;
float atten = (lightCoord.z > 0) * tex2D(_LightTexture0, lightCoord.xy / lightCoord.w + 0.5).w * tex2D(_LightTextureB0, dot(lightCoord, lightCoord).rr).UNITY_ATTEN_CHANNEL;
#else
float atten = 1;
#endif
#endif
//附加渲染通道中不需要加 环境光颜色,原因是再基础通道中就已经计算了
//衰减值 乘以 兰伯特光照和布林芳高光的颜色之和
fixed3 finalColor = (lambertColor + blinn_PhongColor) * atten;
return fixed4(finalColor, 1);
}
ENDCG
}