UnityShader16:点光源和聚光灯

一、点光源、聚光灯的特点

关于点光源和聚光灯的特点,可以参考下面的文章,只需要关心理论部分就ok了

  1. 点光源:https://blog.csdn.net/Jaihk662/article/details/106722949
  2. 聚光灯:https://blog.csdn.net/Jaihk662/article/details/106770741
  3. 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 的值为:\frac{(x, y, z,\frac{2z}{\cot{c}} )}{scale}

其中 scale 为缩放,\boldsymbol{c} 为聚光灯切光角(半张角),可以参考下图(图中的 c 其实是 \cot{c}

这样的话,lightCoord.xy / lightCoord.w + 0.5 值就为 (\frac{x{\cot{c}}}{2z} + \frac{1}{2}, \frac{y{\cot{c}}}{2z}+ \frac{1}{2})

这个公式就很有意思了,可以看出这又是 /2 + 1/2 的经典操作,也就是将原先 [-1, 1] 范围内置的 值映射到 [0, 1] 范围内,而这也正是纹理坐标的范围

而对于去掉映射计算后的值 (\frac{x{\cot{c}}}{z}, \frac{y{\cot{c}}}{z}) 正是当前片段到光源的 tan 值除以切光角 tan 值的结果,当这个值为 -1 或 1 时,说明当前片段刚好在投影范围的边界上,如果当前值为0,说明当前片段正对照射中心,若是超过了 [-1, 1] 的范围当然不受光照

那么拿到了这个被归入 [0, 1] 范围内的值后就可以从 cookie 中采样了,对应的 cookie 贴图如下:

一目了然

猜你喜欢

转载自blog.csdn.net/Jaihk662/article/details/112202944
今日推荐