Unity Shader Book

P193的9.2节最下面有这样的代码:

#ifdef USING_DIRECTIONAL_LIGHT
    fixed atten = 1.0;
#else
    float3 lightCoord = mul(_LightMatrix0, float4(i.worldPos, 1)).xyz;
    fixed atten = tex2D(_LightTexture0, dot(lightCoord, lightCoord).rr).UNITY_ATTEN_CHANNEL;
#endif
  • _LightMatrix0:这个变量在书的P184页表格和官方文档中均有说明,它在内置的AutoLight.cginc文件中的特定宏下被定义,可以用于把点从世界空间变换到该光源的局部空间下。因此,上面代码第一句“mul(_LightMatrix0, float4(i.worldPos, 1)).xyz”就是把顶点坐标变换到光源空间下的坐标,这点在书中也有解释。

  • _LightTexture0:和_LightMatrix0类似的,也是在内置的AutoLight.cginc文件中的特定宏下被定义,紧跟_LightMatrix0的定义。它是一张包含了光源衰减信息的衰减纹理,我们可以用第一句代码得到的坐标进一步处理得到衰减纹理的采样坐标。由于Unity没有官方解释_LightMatrix0具体的数值范围含义,我们可以推测得到(推测方法可以是利用假彩色一节的方法来把这个变量当成颜色输出)_LightMatrix0得到的坐标模范围会是[0, 1],即与光源重合处是(0, 0, 0), 在光源范围的边界处模值为1。而 dot(lightCoord, lightCoord).rr一句,首先是由点积得到光源的距离平方,这是一个标量,我们对这个变量进行.rr操作相当于构建了一个二维矢量,这个二维矢量每个分量的值都是这个标量值,由此得到一个二维采样坐标。这个操作是shader中常见的swizzling操作,Cg的官网上也有说明。我们随后使用这个二维坐标对光照衰减纹理_LightTexture0进行采样,得到衰减值。关于_LightTexture0后面会系统讲。

  • 这段代码不够规范:书上有说这一节的代码不能直接用到项目里,仅仅是用来阐述原理,但如果你想用这段代码直接发布项目的话,是需要修改的,因为当时写的时候不够规范。原因是这段代码的keyword判断条件有问题,导致_LightTexture0或_LightMatrix0可能并没有被定义。我们可以将原来的代码替换成下面的代码(新的代码也已经push到了github上):

    #ifdef USING_DIRECTIONAL_LIGHT
        fixed atten = 1.0;
    #else
        #if defined (POINT)
            // 把点坐标转换到点光源的坐标空间中,_LightMatrix0由引擎代码计算后传递到shader中,这里包含了对点光源范围的计算,具体可参考Unity引擎源码。经过_LightMatrix0变换后,在点光源中心处lightCoord为(0, 0, 0),在点光源的范围边缘处lightCoord模为1
            float3 lightCoord = mul(_LightMatrix0, float4(i.worldPos, 1)).xyz;
            // 使用点到光源中心距离的平方dot(lightCoord, lightCoord)构成二维采样坐标,对衰减纹理_LightTexture0采样。_LightTexture0纹理具体长什么样可以看后面的内容
            // 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)
            // 把点坐标转换到聚光灯的坐标空间中,_LightMatrix0由引擎代码计算后传递到shader中,这里面包含了对聚光灯的范围、角度的计算,具体可参考Unity引擎源码。经过_LightMatrix0变换后,在聚光灯光源中心处或聚光灯范围外的lightCoord为(0, 0, 0),在点光源的范围边缘处lightCoord模为1
            float4 lightCoord = mul(_LightMatrix0, float4(i.worldPos, 1));
            // 与点光源不同,由于聚光灯有更多的角度等要求,因此为了得到衰减值,除了需要对衰减纹理采样外,还需要对聚光灯的范围、张角和方向进行判断
            // 此时衰减纹理存储到了_LightTextureB0中,这张纹理和点光源中的_LightTexture0是等价的
            // 聚光灯的_LightTexture0存储的不再是基于距离的衰减纹理,而是一张基于张角范围的衰减纹理
            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
    

    上面更加精确地判断了keyword条件,保证只在正确的时候执行访问衰减纹理的代码。_LightTexture0和_LightMatrix0只在某些条件下会被定义,例如在开启了POINT、SPOT、POINT_COOKIE、DIRECTIONAL_COOKIE等,具体我们可以在AutoLight.cginc里面找到。但因为原来的代码里没有进行严格判断,在发布的时候由于Unity会严格编译Unity Shader根据不同的keyword生成对应的shader program,此时它就会发现我们的错误了。

更多关于keyword和shader variants的内容可以参见issue中作者的回复。

基于距离的衰减纹理

上面提到,在点光源的情况下,基于到点光源中心距离的衰减纹理被存储在了_LightTexture0中,而在聚光灯的情况下,这张纹理被存储在了_LightTextureB0中。对它们的采样都是使用光源空间到点到光源中心的距离平方来作为采样坐标。这张纹理可以摆放一张平面来输出查看(要注意输出通道UNITY_ATTEN_CHANNEL):
2016-09-29 00 05 25
可以看出,它相当于一张一维纹理,在坐标0处值为1,在坐标1处值为0,这意味着,当距离光源越近,atten值越接近于1。

基于张角的衰减纹理

前面说过,对于聚光灯来说,衰减不仅仅受距离影响,也受张角影响。再回顾下,聚光灯条件下,总的衰减代码如下:

fixed atten = (lightCoord.z > 0) * tex2D(_LightTexture0, lightCoord.xy / lightCoord.w + 0.5).w * tex2D(_LightTextureB0, dot(lightCoord, lightCoord).rr).UNITY_ATTEN_CHANNEL;

最后的tex2D(_LightTextureB0, dot(lightCoord, lightCoord).rr).UNITY_ATTEN_CHANNEL这句不再说明,和上面一样。最前面的(lightCoord.z > 0)是判断方向范围,因为聚光灯的张角范围小于180°,因此如果lightCoord.z <= 0的话它肯定不会被照亮,衰减值就直接是0。

比较难理解的是tex2D(_LightTexture0, lightCoord.xy / lightCoord.w + 0.5).w这句话。总体来讲,这句代码是在基于点的张角来计算衰减,如果和光源空间的中心轴(+z轴)刚好重合,那么这个衰减值应该是1,而在聚光灯的张角边缘处,这个值因为是0。由网友提供的Unity 4.3源码,我们可以知道聚光灯_LightMatrix0的计算(P.S. 由于下面源码不全,所以并不是完整的_LightMatrix0,从实验结果来看应该还做了一个粗略的范围判断,等下好了源码再补吧):

Light::GetMatrix (const Matrix4x4f* __restrict object2light, Matrix4x4f* __restrict outMatrix) const
{
    Matrix4x4f temp1, temp2, temp3;
    float scale;

    switch (m_AttenuationMode) {
    case kSpotCookie:
        // we want out.w = 2.0 * in.z / m_CotanHalfSpotAngle
        // c = m_CotanHalfSpotAngle
        // 1 0 0 0
        // 0 1 0 0
        // 0 0 1 0
        // 0 0 2/c 0
        // the "2" will be used to scale .xy for the cookie as in .xy/2 + 0.5 
        temp3.SetIdentity();
        temp3.Get(3,2) = 2.0f / m_CotanHalfSpotAngle;
        temp3.Get(3,3) = 0;

        scale = 1.0f / m_Range;
        temp1.SetScale (Vector3f(scale,scale,scale));

        // temp3 * temp1 * object2Light
        MultiplyMatrices4x4 (&temp3, &temp1, &temp2);
        MultiplyMatrices4x4 (&temp2, object2light, outMatrix);
        break;

看似难懂,但其实上面就是先把点的坐标变换到聚光灯坐标空间下(object2light矩阵),然后在乘以temp3 * temp1。我们把上面的公式拆开,就会发现一个点和上面的矩阵相乘后结果是:

_LightMatrix0 * (x, y, z, 1) = temp3 * temp1 * object2Light * (x, y, z, 1) = (x/scale, y/scale, z/scale, 2z/(scale * m_CotanHalfSpotAngle)

而在shader中,我们靠lightCoord.xy / lightCoord.w + 0.5得到的结果其实是:

uv = lightCoord.xy / lightCoord.w + 0.5 = (x * m_CotanHalfSpotAngle/(2 * z) + 0.5, y * m_CotanHalfSpotAngle/(2 * z) + 0.5)

我们以x分量为例,x * m_CotanHalfSpotAngle/(2 * z) + 0.5其实是把点的tan值除以半张角的tan值(即等于乘以cot值m_CotanHalfSpotAngle),由此得到张角比值,再通过缩放和平移,把张角的判断范围归一到[0, 1],如下图所示:
untitled

也就是说,在张角中心坐标值为0.5,在两侧分别为0和1。而这张基于张角的衰减纹理_LightTexture0是长这样的(它的w分量):

![2016-09-29 00 31 33](https://cloud.githubusercontent.com/assets/5907064/18922657/233b3a90-85dc-11e6-9e1b-7b1653b4313c.png) 由此就不难看出,在张角中心,即坐标0.5处衰减值为1,而在两侧是接近0的。
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值