这篇来梳理一下ShadeVertexLights
所以着重看一下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;
}
先看一下注释
用在Vertex Pass中,也就是说LightMode=Vertex,用于计算漫反射光
特别要注意的是,如果参数中spotLight为true,会比false时多一些性能的消耗
但是true的时候,却能处理spot light,如果为false,只能处理点光源了
先看一下前两行
viewpos的计算中可以看出这个函数传进来的vertex是objectspace空间的顶点坐标(正常情况下,官方用vertex表示object space下的,用pos表示worldspace 下的),看一下UnityObjectToViewPos这个函数
刚说完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)
这里看出的是unity_LightPosition里存储的是view Space下的light 的信息.那么toLight的计算就很明确了
如果light的类型是直线光源的话,那么unity_LightPosition的w分量是0,xyz分量存储的是直线光的方向。
如果light的类型是非直线光源的话,那么unity_LightPosition的w分量是1,xyz分量存储的是光源的position。
这样toLight所表示的意思就很明确了,就是得到了lightDir。
这里就不多解释了,获取大豆toLight向量的长度的平方,用于后来的标准化。
标准化toLight向量
这是去计算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的情况判断,看下面的
很明显的,在view space下做的lambert的计算
回到spotlight的情况
上面已经知道了unity_SpotDirection里存放的值的情况(存放spot light的viewspace下的position,但是这里我还是存有疑问,如果存放的是position的话,unity_LightPosition不是已经存放了么,更何况人家unity_SpotDirection的名字是Direction,所以我觉得这里文档应该是疏忽了,应该存放的是spot light direction而不是spot light position)。。
我的短肋,看一下文章里写的。
从这里开始,就是我不负责任的推断了
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的一半。
回到源码
这里计算的rho其实就是cos(alpha),然后
结合上面给出的公式,其实就是计算的spotlight从中心到边缘的衰减。但是公式是阉割过的。
这样spotlight的从中心到边缘的衰减就算近似的计算好了。
然后在与物体到光源距离的衰减做一下运算
这样整个衰减的计算就完成了
关于公式是怎么出来的,只能去找spot light 设计资料,以及相应的数学资料了。太难了,略。