US3S5L5——聚光灯衰减计算
US3S5L5——聚光灯衰减计算
聚光灯默认Cookie
灯光组件中有一个 Cookie 参数,是用来关联光照遮罩图片的
对于平行光和点光源,默认是不会提供任何光照遮罩信息的
但是对于聚光灯来说,Unity 会默认为它提供一个 Cookie 光照遮罩,主要是用于模拟聚光灯的区域性
而此时 光照纹理中
-
_LightTexture0
:存储的是 Cookie 纹理信息 -
_LightTextureB0
:存储的是光照纹理信息,里面包含衰减值
因此
- 获取聚光灯衰减值时,需要从
_LightTextureB0
中进行采样 - 获取遮罩范围相关数据时,需要从
_LightTexture0
中进行采样
聚光灯衰减计算
-
将顶点从世界空间转换到光源空间
1
float4 lightCoord = mul(unity_WorldToLight, float4(i.worldPos, 1));
注意:这里我们转换后和点光源不同的是,点光源只会获取其中的
xyz
,而聚光灯会获取其中的 xyzw
这是因为在聚光灯光源空间下的w
值有特殊含义,会参与后续的范围计算 -
利用光源空间下的坐标信息,通过3个步骤去获取聚光灯的衰减信息
1
2
3fixed 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 坐标,即这一步的规则和 点光源 规则一致,直接根据距离光源原点距离的平方从光照纹理中获取衰减值,需要注意的是:
- 聚光灯的光照衰减纹理为
_LightTextureB0
- 点乘
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
,相当于不受聚光灯光照影响
也就是说这一步的主要作用,是用来决定顶点是否受到聚光灯光照的影响
-
第二步 - 取出 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
坐标对应的最大横截面上的坐标,这里具体的数学推导不展开
-
-
因此我们总结一下,看似复杂的聚光灯光照衰减计算方式,其实就是由 “cookie遮罩值” 和距离衰减共同决定的
- 第一步:判断是否能有机会照到光,看得到为
1
,看不到为0
,(lightCoord.z > 0)
- 第二步:缩放平移,映射到遮罩纹理采样 根据遮罩纹理的信息决定衰减叠加:
tex2D(_LightTexture0, lightCoord.xy / lightCoord.w + 0.5).w
- 第三步:从光照衰减纹理中取出按距离得到的衰减值:
tex2D(_LightTextureB0, dot(lightCoord, lightCoord).rr).UNITY_ATTEN_CHANNEL
这三个步骤得到的值最终会相乘,得到最终的光照衰减值