这篇集中梳理一下关于顶点光源的话题。这也是上篇留下的ShadeSH9等。
简单翻译一下:(如果有什么特殊的疑问请自己在unity里测试一下吧,比如这种问题:我所有的light都是not Important,那么最亮的那个直线光还是per-pixel么?等等。。)
1.只要被设置成Not Important 的light一律按照per-vertex和SH光照处理
2.最亮的直线光按照per-pixel处理
3.只要被设置长Important的light一律按照per-pixel处理
4.如果像素光的数量少于QualitytSetting里设定的数量的话,按照亮度的强弱在per-pixel里依次参与渲染计算。
Forward RenderingMode中
1.在Base Pass中处理一个像素直线光(最亮的那个,如果要问,我弄两了两个参数完全一样的直线光,并且都是Important的话,并且距离物体的距离也是一样的,那个参与BasePass里的计算呢?我想给出的回答是:“胸弟,不要为难自己,自己测试一下吧”)和所有的per-vertex/SH光源。
2.其他的像素光在additional passes里参与渲染计算,每个像素光都会产生一个pass。
接下来进入正题
首先看一下Shade4PointLights
这个方法的意图很明显,就是计算4个点光源。
这个方法的注释还是要看一下的
这个方法使用与Base pass中,用最多4个点光源于计算漫反射光之中,这最多4个的点光源的数据是用一种特殊的方式pack了一下,“unity粑粑果然黑盒”,意思很明确,unity的意思就是“粑粑已经把最多4个的点光源(如果不够四个,那估计就用黑色轻度为0的点光源填充)的数据给你准备好了,不要问粑粑怎么准备的,你拿去basepass里用就好了”,unity粑粑强硬又不失典雅。
这里多说一句,为啥是最多4个point light而不是5个或者6个?其实这个也不难猜测,float4嘛,我们处理的是三维空间的数据,引入齐次之后,。。。。,巴拉巴拉一堆,我也不知道怎说,索性就不说了吧。
看一下这个方法的声明
这个简单的说一下,如果想深入了解。请看一下《untiy3d ShaderLab实战详解》,建议购买正版支持一下作者(他是大前辈,他的书我拜读了至少3遍)。听说最近群里的管理员们一起商榷,打算出第三版。额。。。。扯远了
lightPosX这个是个float4,一下子就知道了它存储的是4个pointlight的position.x,同理,lightPosY,lightPosZ
lightColor0,lightColor1,lightColor2,lightColor3这四个无疑就是light的颜色的rgb值了。
lightAttenSq这个存储的就是平方衰减的值了
pos和normal就是物体的worldSpace下的顶点position和normal了
接下来可以一点一点吃方法体
因为每一个点关于的坐标是分开XYZ单独存储的
也就是说,float3(toLightX.x,toLightY.x,toLightZ.x)是第一盏点光源得到pos的direction vector
这里求的就是point light与vertex 的worldspace的距离的平方值。
接下来就是一起计算4个point light的NdotL
注释:rsqrt(x) 返回1/sqrt(x)
这里解释一下之前所有的骚气操作。
首先大家都直道 NdotL的计算
NdotL = dot(normalize(Normal),normalize(LightDir))
normalize(vec3) = vec3/(length(vec3))
length(vec3) = sqrt(dot(vec3,vec3))
dot(vec3,vec3) = vec3.x*vec3.x + vec3.y*vec3.y + vec3.z*vec3.z
然后我们折回来看一下源码,这里我们只看一个pointlight的计算
为了方便表述这里简单的定义下
float3 toLight = float3(toLightX.x,toLightY.x,toLightZ.x);
float lengthSq_Single= lengthSq.x;
float ndotl_single = ndotl.x;
首先我们拿到了未单位化(标准化)的light direction vector------toLight,然后拿到了距离的平方------lengthSq_Single,
然后我们只需要 toLight*rsqrt(lengthSq_Single)就拿到了单位化的light direction vector。然后在计算ndotl,计算之前要单位化normal
但是源码并不是这么做的
它先做了ndotl = toLight.x * normal.x + toLight.y*normal.y + tolight.z*normal.z;
然后
ndotl*rsqrt(ndotl_single)
= toLight.x*rsqrt(ndotl_single) * normal.x + toLight.y*rsqrt(ndotl_single) *normal.y + tolight.z*rsqrt(ndotl_single) *normal.z
= dot(normalize(toLight),normal)
最后的结果也是一样的。为啥要滞后计算这个单位化的操作
其实也很好理解,toLightX ,toLightY,toLightZ这三个都是个float4,如果单位化的话,这12个分量都要去与对应的length的倒数做乘法
所以官方采取了最后做,那么只需要对一个float4做一次就好。
然后,接着看后面的源码
这个就是简单的平方衰减的计算和diff的计算。从这里就知道了这四个point light采用的是lambert光照模型。
最后这里就是把灯光的颜色算进去而已
到这里就把Shade4PointLights 这个方法简单的剖析了一下,本来还想继续写写ShadeSH9这个。但是感觉还是重开一篇做做记录吧
下来就是简单的应用Shade4PointLights 的shader源码。
Shader "ShaderStore/Templates/New Unlit NoFog"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
}
SubShader
{
Tags { "RenderType"="Opaque" "LightMode"="ForwardBase"}
LOD 100
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
float3 normal : NORMAL;
};
struct v2f
{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
float3 shade4pointlights: TEXCOORD1;
};
sampler2D _MainTex;
float4 _MainTex_ST;
v2f vert (appdata v)
{
v2f o;
float4 posWorld = mul(unity_ObjectToWorld,v.vertex);
o.vertex = mul(UNITY_MATRIX_VP,posWorld);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
float3 normWorld = mul(v.normal,(float3x3)unity_WorldToObject);
o.shade4pointlights = Shade4PointLights(
unity_4LightPosX0,unity_4LightPosY0,unity_4LightPosZ0,
unity_LightColor[0].rgb,unity_LightColor[1].rgb,unity_LightColor[2].rgb,unity_LightColor[3].rgb,
unity_4LightAtten0,
posWorld.xyz,normWorld
);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
fixed4 col = tex2D(_MainTex, i.uv);
col.rgb *= i.shade4pointlights;
return col;
}
ENDCG
}
}
}