一、点光源、聚光灯的特点
关于点光源和聚光灯的特点,可以参考下面的文章,只需要关心理论部分就ok了
- 点光源:https://blog.csdn.net/Jaihk662/article/details/106722949
- 聚光灯:https://blog.csdn.net/Jaihk662/article/details/106770741
- Unity中的前向渲染:https://blog.csdn.net/Jaihk662/article/details/112055588
还是需要提前了解下的
二、在UnityShader中实现点光和聚光
代码在前向渲染这一章中就有,主要还是关于衰减的计算:
#ifdef USING_DIRECTIONAL_LIGHT
fixed atten = 1.0;
#else
#if defined (POINT)
//unity_WorldToLight在AutoLight.cginc文件中的特定宏下被定义,可以用于把点从世界空间变换到该光源的局部空间下
float3 lightCoord = mul(unity_WorldToLight, float4(i.wPos, 1)).xyz;
//UNITY_ATTEN_CHANNEL是衰减值所在的纹理通道,可以在内置的HLSLSupport.cginc文件中查看,一般PC和主机平台的话UNITY_ATTEN_CHANNEL是r通道,移动平台的话是a通道
fixed atten = tex2D(_LightTexture0, dot(lightCoord, lightCoord).rr).UNITY_ATTEN_CHANNEL;
#elif defined (SPOT)
float4 lightCoord = mul(unity_WorldToLight, float4(i.wPos, 1));
//若不在聚光灯的照射方向,就当然没有光照
fixed atten = (lightCoord.z > 0) * tex2D(_LightTextureB0, dot(lightCoord, lightCoord).rr).UNITY_ATTEN_CHANNEL;
//对于聚光灯,_LightTexture0存储的不再是基于距离的衰减纹理,而是一张基于张角范围的衰减纹理
atten *= tex2D(_LightTexture0, lightCoord.xy / lightCoord.w + 0.5).w;
#else
fixed atten = 1.0;
#endif
#endif
可以看出 Unity 中使用一张纹理作为查找表在片元着色器中计算逐光照的衰减,这样就可以避免数学计算的复杂度,但是不是特别直观,并且数据大多数是要预处理定死的
三、光的衰减
对于点光源,需要考虑距离衰减,而对于聚光灯,不但需要考虑距离衰减,还需要考虑张角衰减
1):距离衰减与距离衰减纹理:
前一章提到过,在点光源的情况下,基于到点光源中心距离的衰减纹理被存储在了 _LightTexture0 中,而在聚光灯的情况下,这张纹理被存储在了 _LightTextureB0 中
一般来讲,只需要考虑其对角线上的纹理颜色值就可以了,所以采样时 uv 坐标相同,也可以将衰减纹理理解为是一个一维纹理
2):投影纹理(cookie):
什么是 cookie 纹理,下面的例子一看就明白,这是一个“窗口”的投影纹理和它的应用
计算张角衰减时,需要用到它
3):关于张角衰减的计算
聚光灯是有一个发射方向的,当前片段到光源的方向与发射方向夹角越大光照就越弱,Unity 中,这个发射方向就是光源坐标系的 z 轴:
在上面的 shader 中,计算角度衰减的代码是这一句:
atten *= tex2D(_LightTexture0, lightCoord.xy / lightCoord.w + 0.5).w;
根据 Unity 源码,我们可以得到的 _LightTexture0 的值为:
其中 为缩放, 为聚光灯切光角(半张角),可以参考下图(图中的 其实是 )
这样的话,lightCoord.xy / lightCoord.w + 0.5 值就为
这个公式就很有意思了,可以看出这又是 /2 + 1/2 的经典操作,也就是将原先 [-1, 1] 范围内置的 值映射到 [0, 1] 范围内,而这也正是纹理坐标的范围
而对于去掉映射计算后的值 正是当前片段到光源的 tan 值除以切光角 tan 值的结果,当这个值为 -1 或 1 时,说明当前片段刚好在投影范围的边界上,如果当前值为0,说明当前片段正对照射中心,若是超过了 [-1, 1] 的范围当然不受光照
那么拿到了这个被归入 [0, 1] 范围内的值后就可以从 cookie 中采样了,对应的 cookie 贴图如下:
一目了然