Bootstrap

Unity中的light map

使用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的使用

如果你觉得我的文章有帮助,欢迎关注我的微信公众号(大龄社畜的游戏开发之路
在这里插入图片描述

悦读

道可道,非常道;名可名,非常名。 无名,天地之始,有名,万物之母。 故常无欲,以观其妙,常有欲,以观其徼。 此两者,同出而异名,同谓之玄,玄之又玄,众妙之门。

;