UnityShader源码2017---学习笔记与自我拓展028-2

这篇来梳理一下ShadeVertexLights

float3 ShadeVertexLights ( float4 vertex, float3 normal)
{
return ShadeVertexLightsFull (vertex, normal, 4 , false);
}

所以着重看一下ShadeVertexLightsFull

// Used in Vertex pass: Calculates diffuse lighting from lightCount lights. Specifying true to spotLight is more expensive
// to calculate but lights are treated as spot lights otherwise they are treated as point lights.
float3 ShadeVertexLightsFull (float4 vertex, float3 normal, int lightCount, bool spotLight)
{
    float3 viewpos = UnityObjectToViewPos (vertex);
    float3 viewN = normalize (mul ((float3x3)UNITY_MATRIX_IT_MV, normal));

    float3 lightColor = UNITY_LIGHTMODEL_AMBIENT.xyz;
    for (int i = 0; i < lightCount; i++) {
        float3 toLight = unity_LightPosition[i].xyz - viewpos.xyz * unity_LightPosition[i].w;
        float lengthSq = dot(toLight, toLight);

        // don't produce NaNs if some vertex position overlaps with the light
        lengthSq = max(lengthSq, 0.000001);

        toLight *= rsqrt(lengthSq);

        float atten = 1.0 / (1.0 + lengthSq * unity_LightAtten[i].z);
        if (spotLight)
        {
            float rho = max (0, dot(toLight, unity_SpotDirection[i].xyz));
            float spotAtt = (rho - unity_LightAtten[i].x) * unity_LightAtten[i].y;
            atten *= saturate(spotAtt);
        }

        float diff = max (0, dot (viewN, toLight));
        lightColor += unity_LightColor[i].rgb * (diff * atten);
    }
    return lightColor;
}

先看一下注释

// Used in Vertex pass: Calculates diffuse lighting from lightCount lights.
// Specifying true to spotLight is more expensive
// to calculate but lights are treated as spot lights otherwise they are treated as point lights.

用在Vertex Pass中,也就是说LightMode=Vertex,用于计算漫反射光

特别要注意的是,如果参数中spotLight为true,会比false时多一些性能的消耗

但是true的时候,却能处理spot light,如果为false,只能处理点光源了

先看一下前两行

float3 viewpos = UnityObjectToViewPos (vertex);
float3 viewN = normalize ( mul (( float3x3 ) UNITY_MATRIX_IT_MV , normal));

viewpos的计算中可以看出这个函数传进来的vertex是objectspace空间的顶点坐标(正常情况下,官方用vertex表示object  space下的,用pos表示worldspace 下的),看一下UnityObjectToViewPos这个函数

// Tranforms position from object to camera space
inline float3 UnityObjectToViewPos ( in float3 pos )
{
return mul ( UNITY_MATRIX_V , mul ( unity_ObjectToWorld , float4 (pos, 1.0 ))).xyz;
}
inline float3 UnityObjectToViewPos ( float4 pos) // overload for float4; avoids "implicit truncation" warning for existing shaders
{
return UnityObjectToViewPos (pos.xyz);
}

刚说完pos表示world space下的。。。接着就打脸。。。辣鸡unity

viewN就是把object space下的normal转换到view space下,但是为什么用逆转置矩阵,请看这里

接下来就是lightColor的计算。

首先,先获取了AMBIENT的颜色。(float3 lightColor = UNITY_LIGHTMODEL_AMBIENT.xyz;)

for循环里我们只看一次的循环,这里我们只通过源码去猜想一些unity引擎传过来的值,不做测试去证明了

(刚才测试了一下,因为ShadeVertexLightsFull 里传入的是4,也就是说只能处理4个光源,测试的结果是不管灯光是什么类型的,在vertex的lightmode下,unity会根据强度和距离将前四个light传入unity_LightPosition这个数组中)

继续看一下toLight的计算(toLight其实就是LightDir)

float3 toLight = unity_LightPosition [i].xyz - viewpos.xyz * unity_LightPosition [i].w;

这里看出的是unity_LightPosition里存储的是view Space下的light 的信息.那么toLight的计算就很明确了

如果light的类型是直线光源的话,那么unity_LightPosition的w分量是0,xyz分量存储的是直线光的方向。

如果light的类型是非直线光源的话,那么unity_LightPosition的w分量是1,xyz分量存储的是光源的position。

这样toLight所表示的意思就很明确了,就是得到了lightDir。


float lengthSq = dot (toLight, toLight);
lengthSq = max (lengthSq, 0.000001 );

这里就不多解释了,获取大豆toLight向量的长度的平方,用于后来的标准化。


toLight *= rsqrt (lengthSq);

标准化toLight向量


float atten = 1.0 / ( 1.0 + lengthSq * unity_LightAtten [i].z);

这是去计算atten,也就是衰减。实在受不了了,去找找关于unity_LightAtten这个的介绍


这里针对上图,说一下自己的理解。

这四个玩意儿,都是长度为8的数组,也就是说ShadeVertexLightsFull 里的lightCout传入的最大值也就是8.

关于unity_LightPosition,我上面说的也是对的,view Space空间下的,如果是直线光源的话存放的是(-dir,0),不是直线光源的话存放(pos,1)

然后看一下unity_LightAtten里存放的数据是什么:

x分量存放----如果是spot light 存放 cos(spotAngle/2),如果不是spot light,存放-1

y分量存放----如果是spot light 存放 1/cos(spotAngle/4), 如果不是spot light,存放-1

z分量存放----attenuation的平方,也就是衰减的平方。这里就不去查,关于衰减,直线光源的衰减为0

w分量存放---light的range的平方,就是灯光范围的平方。

再来看一下unity_SpotDirection

如果是spotlight的话,那么就存放spot light 的position,如不是spotlight,值为(0,0,1,0)


回到源码,atten的计算,如果是直线光源的话unity_LightAtten的z存放的是0,那么atten=1。如果不是直线光源的话就是获得的相应光源的atten(没记错的话是unity会给一张light texture去存放衰减,这个后面整理shadow的时候在细琢磨吧)

先绕开spotlight的情况判断,看下面的

float diff = max ( 0 , dot (viewN, toLight));
lightColor += unity_LightColor [i].rgb * (diff * atten);

很明显的,在view space下做的lambert的计算

回到spotlight的情况

if (spotLight)
{
float rho = max ( 0 , dot (toLight, unity_SpotDirection [i].xyz));
float spotAtt = (rho - unity_LightAtten [i].x) * unity_LightAtten [i].y;
atten *= saturate (spotAtt);
}

上面已经知道了unity_SpotDirection里存放的值的情况(存放spot light的viewspace下的position,但是这里我还是存有疑问,如果存放的是position的话,unity_LightPosition不是已经存放了么,更何况人家unity_SpotDirection的名字是Direction,所以我觉得这里文档应该是疏忽了,应该存放的是spot light direction而不是spot light position)。。

这里就涉及到了spotlight的数学计算了

我的短肋,看一下文章里写的。



从这里开始,就是我不负责任的推断了

Phi是整个spot light的照射范围,最下面的公式中用到了Cos(Phi/2),也就是在unity_LightAtten里存放的x分量的值。

Theta是spotlight照射过程中,中心亮度不衰减的部分。最下面的公式中用到了Cos(Theta/2),再看一下unity_LightAtten的y分量,y存放的是1/cos(spotAngle/4),也就是说是cos(spotAngle/2/2)的倒数,也就是说Theta=spotAngle/2,这个意思就很明确了,在unity中spotlight的光中心不衰减的部分在参数中并没有可以调节的参数,而是固定为spotangle的一半。

回到源码

float rho = max ( 0 , dot (toLight, unity_SpotDirection [i].xyz));

这里计算的rho其实就是cos(alpha),然后

float spotAtt = (rho - unity_LightAtten [i].x) * unity_LightAtten [i].y;

结合上面给出的公式,其实就是计算的spotlight从中心到边缘的衰减。但是公式是阉割过的。

这样spotlight的从中心到边缘的衰减就算近似的计算好了。

然后在与物体到光源距离的衰减做一下运算

atten *= saturate (spotAtt);

这样整个衰减的计算就完成了

关于公式是怎么出来的,只能去找spot light 设计资料,以及相应的数学资料了。太难了,略。




猜你喜欢

转载自blog.csdn.net/u012871784/article/details/81020246