US3S5L5——聚光灯衰减计算

聚光灯默认Cookie

灯光组件中有一个 Cookie 参数,是用来关联光照遮罩图片的
对于平行光和点光源,默认是不会提供任何光照遮罩信息的
但是对于聚光灯来说,Unity 会默认为它提供一个 Cookie 光照遮罩,主要是用于模拟聚光灯的区域性

而此时 光照纹理中

  • _LightTexture0​:存储的是 Cookie 纹理信息
  • _LightTextureB0​:存储的是光照纹理信息,里面包含衰减值

因此

  1. 获取聚光灯衰减值时,需要从 _LightTextureB0​ 中进行采样
  2. 获取遮罩范围相关数据时,需要从 _LightTexture0​ 中进行采样

聚光灯衰减计算

  1. 将顶点从世界空间转换到光源空间

    1
    float4 lightCoord = mul(unity_WorldToLight, float4(i.worldPos, 1));

    注意:这里我们转换后和点光源不同的是,点光源只会获取其中的 xyz,而聚光灯会获取其中的 xyzw
    这是因为在聚光灯光源空间下的 w​ 值有特殊含义,会参与后续的范围计算

  2. 利用光源空间下的坐标信息,通过3个步骤去获取聚光灯的衰减信息

    1
    2
    3
    fixed atten = (lightCoord.z > 0) *                                                 //第一步,通过z分量判断目标点是否在聚光灯背面
    tex2D(_LightTexture0, lightCoord.xy / lightCoord.w + 0.5).w * //第二步,将坐标进行变换后从cookie纹理内取出遮罩衰减值
    tex2D(_LightTextureB0, dot(lightCoord, lightCoord).rr).UNITY_ATTEN_CHANNEL; //第三步,根据坐标从光照衰减纹理内取出距离衰减值

    这三个步骤得到的值最终会相乘,得到最终的衰减消息

    • 首先分析和点光源相同的部分 —— 第三步:获取距离衰减值

      1
      tex2D(_LightTextureB0, dot(lightCoord, lightCoord).rr).UNITY_ATTEN_CHANNEL;

      这里的 rr​ 和 点光源使用的 xx​ 是等价的,目的都是构建一个 float2​ 代表 uv 坐标,即 (distance2,distance2)(distance^2, distance^2)

      这一步的规则和 点光源 规则一致,直接根据距离光源原点距离的平方从光照纹理中获取衰减值,需要注意的是:

      1. 聚光灯的光照衰减纹理为 _LightTextureB0
      2. 点乘 dot​ 函数只会计算 xyz​,w​ 不会计算
    • 接下来分析用于进行范围判断的部分 —— 第一、二步

      • 第一步 - 判断是否在背面:(lightCoord.z > 0)

        CG 语法中没有显示的 bool​ 类型,一般情况下 0​ 表示 false​,1​ 表示 true​,
        也就是说 lightCoord.z > 0​ 的返回值,条件满足时为 1​,条件不满足为 0

        这里的 z​ 分量代表的其实是 目标点 相对于 聚光灯照射面 距离
        如果 lightCoord.z <= 0​ 证明在聚光灯照射方向的背面,就不应该受到聚光灯的影响
        lightCoord.z <= 0​,就得到 0​,于是,后面的值即使相乘也是 0​,相当于不受聚光灯光照影响

        image

        也就是说这一步的主要作用,是用来决定顶点是否受到聚光灯光照的影响

      • 第二步 - 取出 cookie 纹理衰减值:tex2D(_LightTexture0, lightCoord.xy / lightCoord.w + 0.5).w

        我们以前在进行 纹理采样 时都会进行一个 先缩放 后 平移 的操作,
        比如:uv = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
        而第二步中的 lightCoord.xy / lightCoord.w + 0.5​ 其实也是在做这样的一个操作

        这样做的主要目的是因为:我们需要把 uv 坐标映射到 0~1 的范围内再从 cookie 纹理中采样,
        因为 lightCoord.xy / lightCoord.w​ 进行缩放后,x, y 的取值范围是 -0.5~0.5 之间
        因此再加上 0.5 后,x, y 的取值范围就是 0~1 之间,便可以进行正确的纹理采样了

        lightCoord.xy / lightCoord.w​ 是因为聚光灯有很多横截面,我们需要把各横截面通过缩放的形式映射到最大的面上进行采样
        lightCoord.xy / lightCoord.w​ 就可以得到 lightCoord​ 坐标对应的最大横截面上的坐标,这里具体的数学推导不展开

        image

因此我们总结一下,看似复杂的聚光灯光照衰减计算方式,其实就是由 “cookie遮罩值”距离衰减共同决定的

  • 第一步:判断是否能有机会照到光,看得到为 1​,看不到为 0​,(lightCoord.z > 0)
  • 第二步:缩放平移,映射到遮罩纹理采样 根据遮罩纹理的信息决定衰减叠加:tex2D(_LightTexture0, lightCoord.xy / lightCoord.w + 0.5).w
  • 第三步:从光照衰减纹理中取出按距离得到的衰减值:tex2D(_LightTextureB0, dot(lightCoord, lightCoord).rr).UNITY_ATTEN_CHANNEL

这三个步骤得到的值最终会相乘,得到最终的光照衰减值