light map的烘焙原理的核心算法是在VS里面利用贴图坐标变换到[-1,1]作为输出的顶点坐标。
实时渲染的顶点变换中, pos * WVP之后, 顶点坐标就变换到屏幕空间了[-1, 1](实际上还需要透视除法);
如果VertexShader里直接把纹理坐标做为变换结果输出(注意从[0,1]变换到[-1,1]), 那么相当于直接变换到了纹理坐标系, 这时在PixelShader里还是像原来那样计算光照, 输出的结果就可以拿来做lightmap了。
一个典型的Phong光照模型下的球渲染图:
其VS为:
VS_OUTPUT vs_main( VS_INPUT Input ) { VS_OUTPUT Output = (VS_OUTPUT)0; Output.Position = mul( Input.Position, matViewProjection ); Output.Texcoord = Input.Texcoord; float3 fvObjectPosition = mul( Input.Position, matView ); Output.ViewDirection = fvEyePosition - fvObjectPosition; Output.LightDirection = fvLightPosition - fvObjectPosition; Output.Normal = mul( Input.Normal, matView ); return( Output ); }
为了烘焙light map,可以变换UV坐标至[-1,1]作为position的x与y坐标,注意还要将w赋值为1,避免driver的透视除法使得x与y的值发生变化:
VS_OUTPUT vs_main( VS_INPUT Input ) { VS_OUTPUT Output = (VS_OUTPUT)0; Output.Position.xy = Input.Texcoord * float2(2, -2) + float2(-1, 1);//[0,1]->[-1,1] here seem is incorrect ! Output.Position.w = 1;//avoid perspective drive change x,y's value Output.Texcoord = Input.Texcoord; float3 fvObjectPosition = mul( Input.Position, matView ); Output.ViewDirection = fvEyePosition - fvObjectPosition; Output.LightDirection = fvLightPosition - fvObjectPosition; Output.Normal = mul( Input.Normal, matView ); return( Output ); }
渲染得到结果:
只保存明暗light信息:
实际上,上文中是只有一盏灯光的结果,如果有多盏灯光,那么就应该针对每个灯光进行烘焙,最粗糙的做法是得到多张light map,进行blend合并出最终结果。
上帝为我关上了窗,我绝不洗洗睡!