Unity Shader 学习笔记(13) 混合光源、光的衰减
参考书籍:《Unity Shader 入门精要》
3D数学 学习笔记(8) 光照
【常见问题】dot(lightCoord, lightCoord).rr和光源衰减的相关问题
混合光源的前向渲染
一个BasePass处理平行光、四个点光源调用四次Additional Pass,四个点光源的顺序是依靠重要度排序的(光源强度、颜色、距离远近)。注意:只处理了逐像素光照,即光源渲染模式如果为Not Imoportant,则不会对物体产生影响。
使用Phong光照模型。定义了Base Pass 和 Additional Pass来处理多个光源。
Base Pass 代码如下。Unity会把最亮的平行光给BasePass处理,其他平行光交给AdditionalPass处理。
Pass {
Tags { "LightMode"="ForwardBase" }
CGPROGRAM
#pragma multi_compile_fwdbase // 保证使用光照衰减等光照变量可以正确被赋值
...
fixed4 frag(v2f i) : SV_Target {
...
// 环境光,只在BasePass处理一次够了。
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
// 漫反射,_LightColor0是平行光颜色和强度相乘后的结果。
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * max(0, dot(worldNormal, worldLightDir));
...
// 镜面高光。
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(worldNormal, halfDir)), _Gloss);
// 衰减值,平行光为1.0。
fixed atten = 1.0;
return fixed4(ambient + (diffuse + specular) * atten, 1.0);
}
...
}
Additional Pass代码如下。处理其他光源。
Pass {
Tags { "LightMode"="ForwardAdd" }
// 开启光照混合,与在帧缓存中的光照叠加
Blend One One
CGPROGRAM
// 获取正确光照变量
#pragma multi_compile_fwdadd
...
fixed4 frag(v2f i) : SV_Target {
fixed3 worldNormal = normalize(i.worldNormal);
#ifdef USING_DIRECTIONAL_LIGHT
// 如果是平行光,直接获取光源方向
fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
#else
// 点光或聚光,光源方向要依赖视角方向
fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz - i.worldPos.xyz);
#endif
...
// 漫反射和高光反射
// 计算衰减值。平行光为1。其他光照较复杂。
#ifdef USING_DIRECTIONAL_LIGHT
fixed atten = 1.0;
#else
#if defined (POINT)
float3 lightCoord = mul(unity_WorldToLight, float4(i.worldPos, 1)).xyz;
fixed atten = tex2D(_LightTexture0, dot(lightCoord, lightCoord).rr).UNITY_ATTEN_CHANNEL;
#elif defined (SPOT)
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;
#else
fixed atten = 1.0;
#endif
#endif
return fixed4((diffuse + specular) * atten, 1.0);
}
ENDCG
}
光照衰减
UNITY_ATTEN_CHANNEL
:可以得到衰减纹理中衰减值所在分量。
光照衰减纹理
需要预处理得到采样纹理,纹理的大小会影响衰减精度。类似渐变纹理,(0, 0)表示与光源重合时的衰减值,(1, 1)为离光源空间最大距离的衰减值。
Unity内部使用_LightTexture0
的纹理来计算光源衰减。利用_LightMatrix0
变换矩阵来计算点在光源空间的位置。
// 计算衰减纹理坐标值
float3 lightCoord = mul(_LightMatrix0, float4(i.worldPosition, 1)).xyz;
// 根据坐标对纹理采样,用dot点乘就是直接做距离的平方,`.rr`的意思就是取得到rgb值的r作为一个新的二维坐标即(r, r)。
fixed atten = tex2D(_LightTexture0, dot(lightCoord, lightCoord).rr).UNITY_ATTEN_CHANNEL;
数学公式计算衰减
使用的是线性衰减:
flaot distance = length(_WorldSpaceLightPos0.xyz) - i.worldPosition.xyz);
atten = 1.0 / distance;