使用light map,可以将静态光源的信息(颜色,阴影,方向等)存储到texture上,渲染静态的物体时,无需进行多个light pass,直接从texture中进行采样计算即可。使用light map可以用较低的成本实现间接光照和全局光照。在Unity中开启light map,首先将需要的光源mode设置为Baked,然后在Lighting Settings里开启Baked Global Illumination:
烘培之后的light map可以在Lighting Settings进行预览:
采样light map的uv存储在第二套纹理坐标中,即TEXCOORD1。我们可以使用如下的方式对light map进行采样:
struct VertexData {
float4 vertex : POSITION;
float3 normal : NORMAL;
float4 tangent : TANGENT;
float2 uv : TEXCOORD0;
float2 uv1 : TEXCOORD1;
};
struct Interpolators {
float4 pos : SV_POSITION;
float4 uv : TEXCOORD0;
float3 normal : TEXCOORD1;
float3 tangent : TEXCOORD2;
float2 lightmapUV : TEXCOORD3;
SHADOW_COORDS(4)
};
Interpolators MyVertexProgram (VertexData v) {
...
i.lightmapUV = v.uv1 * unity_LightmapST.xy + unity_LightmapST.zw;
return i;
}
FragmentOutput MyFragmentProgram (Interpolators i) {
...
UnityIndirect indirectLight;
indirectLight.diffuse = DecodeLightmap(UNITY_SAMPLE_TEX2D(unity_Lightmap, i.lightmapUV));
indirectLight.specular = 0;
...
}
我们采样light map来得到间接光照的diffuse信息。没有specular信息是因为specular与相机的视角有关,而在烘焙light map时是无法得知相机运行时视角的。
在烘焙light map时,Unity会在shader中寻找"LightMode" = "Meta"的pass,来让烘焙物体表面的Albedo和Emission,使其参与到间接光照中,例如下图是没有meta pass时场景的效果:
使用meta pass后:
可以看出,绿色的地板通过间接光照,使场景中其他物体也染上了绿色,这是符合真实世界的效果。可以参考Unity内置的UnityMetaPass.cginc来编写这个pass:
float4 UnityMetaVertexPosition(float4 vertex, float2 uv1, float2 uv2, float4 lightmapST, float4 dynlightmapST)
{
#if !defined(EDITOR_VISUALIZATION)
if (unity_MetaVertexControl.x)
{
vertex.xy = uv1 * lightmapST.xy + lightmapST.zw;
// OpenGL right now needs to actually use incoming vertex position,
// so use it in a very dummy way
vertex.z = vertex.z > 0 ? 1.0e-4f : 0.0f;
}
if (unity_MetaVertexControl.y)
{
vertex.xy = uv2 * dynlightmapST.xy + dynlightmapST.zw;
// OpenGL right now needs to actually use incoming vertex position,
// so use it in a very dummy way
vertex.z = vertex.z > 0 ? 1.0e-4f : 0.0f;
}
return mul(UNITY_MATRIX_VP, float4(vertex.xyz, 1.0));
#else
...
#endif
}
half4 UnityMetaFragment (UnityMetaInput IN)
{
half4 res = 0;
#if !defined(EDITOR_VISUALIZATION)
if (unity_MetaFragmentControl.x)
{
res = half4(IN.Albedo,1);
// d3d9 shader compiler doesn't like NaNs and infinity.
unity_OneOverOutputBoost = saturate(unity_OneOverOutputBoost);
// Apply Albedo Boost from LightmapSettings.
res.rgb = clamp(pow(res.rgb, unity_OneOverOutputBoost), 0, unity_MaxOutputValue);
}
if (unity_MetaFragmentControl.y)
{
half3 emission;
if (unity_UseLinearSpace)
emission = IN.Emission;
else
emission = GammaToLinearSpace(IN.Emission);
res = half4(emission, 1.0);
}
#else
...
#endif
return res;
}
unity_MetaFragmentControl内置变量表示当前输出的是albedo还是emission,x分量是albedo,y分量是emission。
为了让间接光照也支持normal map,在烘焙light map时可以设置将光源的方向信息也一并进行烘焙。同样也是在Lighting Settings里进行设置:
Unity会额外生成一张记录方向信息的light map:
有了方向信息之后,我们可以使用如下的方式对light map进行采样:
float4 lightmapDirection = UNITY_SAMPLE_TEX2D_SAMPLER(unity_LightmapInd, unity_Lightmap, i.lightmapUV);
indirectLight.diffuse = DecodeDirectionalLightmap(indirectLight.diffuse, lightmapDirection, i.normal);
DecodeDirectionalLightmap
是Unity提供的内置API,使用半兰伯特漫反射模型计算diffuse:
inline half3 DecodeDirectionalLightmap (half3 color, fixed4 dirTex, half3 normalWorld)
{
// In directional (non-specular) mode Enlighten bakes dominant light direction
// in a way, that using it for half Lambert and then dividing by a "rebalancing coefficient"
// gives a result close to plain diffuse response lightmaps, but normalmapped.
// Note that dir is not unit length on purpose. Its length is "directionality", like
// for the directional specular lightmaps.
half halfLambert = dot(normalWorld, dirTex.xyz - 0.5) + 0.5;
return color * halfLambert / max(1e-4h, dirTex.w);
}
dirTex为分量在[0, 1]之间的向量,减去0.5之后恰好就是[-0.5,0.5]之间,这样就不用在计算点积之后再去乘0.5了,最后加上0.5使halfLambert的值在[0, 1]之间。
最后来看看是否使用方向信息light map渲染效果的差异:
Reference
[1] Static Lighting
[2] 这个坑你遇到过吗——烘焙自发光metapass的使用
如果你觉得我的文章有帮助,欢迎关注我的微信公众号(大龄社畜的游戏开发之路)