烘焙的光照使用lightmap处理静态物体,使用light probe处理动态物体,但是它不能处理动态的光源。对于动态光源,Unity提供了实时全局光照的支持。我们可以在Window/Rendering/Lighting Settings中开启:
开启之后Unity就会实时计算lightmap和light probe。当然这和当前光源的模式有关。如果当前光源为baked,此时依旧看不到realtime lightmap:
当前光源为realtime时,可以一睹realtime lightmap的全貌:
另外从中可以看出,realtime lightmap的尺寸非常的小,图中只有44*38。为了完成对realtime lightmap的采样,unity在顶点数据中提供了TEXCOORD2作为其纹理坐标:
struct VertexData {
float4 vertex : POSITION;
float3 normal : NORMAL;
float4 tangent : TANGENT;
float2 uv : TEXCOORD0;
float2 uv1 : TEXCOORD1;
float2 uv2 : TEXCOORD2;
};
当启用realtime lightmap时,DYNAMICLIGHTMAP_ON
宏生效:
#if defined(DYNAMICLIGHTMAP_ON)
i.dynamicLightmapUV =
v.uv2 * unity_DynamicLightmapST.xy + unity_DynamicLightmapST.zw;
#endif
在fragment shader中得到具体uv后,开始采样:
float3 dynamicLightDiffuse = DecodeRealtimeLightmap(
UNITY_SAMPLE_TEX2D(unity_DynamicLightmap, i.dynamicLightmapUV)
);
LPPV一般用于体积较大的物体,体积较大的物体不同位置受到的GI不同,如果使用light probe,得到的效果会不准确:
而开启LPPV后效果如下:
当开启LPPV时,UNITY_LIGHT_PROBE_PROXY_VOLUME
宏会开启,但这个关键字是全局的,对于某个物体来说,是否使用LPPV还需要判断unity_ProbeVolumeParams
这个四维向量的x分量是否为1:
unity提供了内置的函数SHEvalLinearL0L1_SampleProbeVolume
来采样LPPV:
if (unity_ProbeVolumeParams.x == 1) {
indirectLight.diffuse = SHEvalLinearL0L1_SampleProbeVolume(
float4(i.normal, 1), i.worldPos
);
indirectLight.diffuse = max(0, indirectLight.diffuse);
}
SHEvalLinearL0L1_SampleProbeVolume
实现如下:
// normal should be normalized, w=1.0
half3 SHEvalLinearL0L1_SampleProbeVolume (half4 normal, float3 worldPos)
{
const float transformToLocal = unity_ProbeVolumeParams.y;
const float texelSizeX = unity_ProbeVolumeParams.z;
//The SH coefficients textures and probe occlusion are packed into 1 atlas.
//-------------------------
//| ShR | ShG | ShB | Occ |
//-------------------------
float3 position = (transformToLocal == 1.0f) ? mul(unity_ProbeVolumeWorldToObject, float4(worldPos, 1.0)).xyz : worldPos;
float3 texCoord = (position - unity_ProbeVolumeMin.xyz) * unity_ProbeVolumeSizeInv.xyz;
texCoord.x = texCoord.x * 0.25f;
// We need to compute proper X coordinate to sample.
// Clamp the coordinate otherwize we'll have leaking between RGB coefficients
float texCoordX = clamp(texCoord.x, 0.5f * texelSizeX, 0.25f - 0.5f * texelSizeX);
// sampler state comes from SHr (all SH textures share the same sampler)
texCoord.x = texCoordX;
half4 SHAr = UNITY_SAMPLE_TEX3D_SAMPLER(unity_ProbeVolumeSH, unity_ProbeVolumeSH, texCoord);
texCoord.x = texCoordX + 0.25f;
half4 SHAg = UNITY_SAMPLE_TEX3D_SAMPLER(unity_ProbeVolumeSH, unity_ProbeVolumeSH, texCoord);
texCoord.x = texCoordX + 0.5f;
half4 SHAb = UNITY_SAMPLE_TEX3D_SAMPLER(unity_ProbeVolumeSH, unity_ProbeVolumeSH, texCoord);
// Linear + constant polynomial terms
half3 x1;
x1.r = dot(SHAr, normal);
x1.g = dot(SHAg, normal);
x1.b = dot(SHAb, normal);
return x1;
}
unity_ProbeVolumeParams
是一个四维向量,x分量表示该物体是否启用LPPV,y分量为0表示在世界空间进行计算,为1表示在LPPV的模型空间进行计算,z分量表示采样的体积纹理在u方向上的纹素大小。unity_ProbeVolumeWorldToObject
定义了从世界空间转换到LPPV模型空间的变换矩阵。unity_ProbeVolumeSizeInv
是LPPV长宽高的倒数。 unity_ProbeVolumeMin
是LPPV左下角的x、y、z坐标。
该函数首先根据传入点的坐标计算出归一化的纹理坐标,由于这里球谐函数的系数和probe遮挡信息被打包到了不同分段中,即前1/4为红色分量,1/4到1/2为绿色分量,1/2到3/4为蓝色分段,因此需要对得到的纹理坐标进行压缩,乘以0.25,并且使用clamp函数限制纹理坐标采样的范围为第1个纹素到第1/4个纹理宽度的纹素之间,这样保证不会漏采样rgb分段的信息。
Unity原生支持LOD,它使用LOD Group这个component来进行控制:
这里的百分比表示物体的包围盒在屏幕空间中所占的比例,当比例下降到60%以下就会切换到LOD1,当比例低于10%时,就会被完全剔除。不过有时候会出现默认LOD的距离已经调节到100%了,但是距离太远或是太近的情况,这时可以在Quality Settings中调整LOD Bias解决:
如果fade mode设置成了cross fade,那么不同LOD过渡时两个LOD对应的几何体都会被渲染,我们可以使用Unity内置的UnityApplyDitherCrossFade
函数进行平滑过渡:
sampler2D unity_DitherMask;
void UnityApplyDitherCrossFade(float2 vpos)
{
vpos /= 4; // the dither mask texture is 4x4
float mask = tex2D(unity_DitherMask, vpos).a;
float sgn = unity_LODFade.x > 0 ? 1.0f : -1.0f;
clip(unity_LODFade.x - mask * sgn);
}
unity_DitherMask
是一个4x4的纹理,使用RenderDoc可以抓到它长啥样:
可以发现,这个texture只有在a通道上有值。vpos为屏幕空间中像素的坐标,x和y取值范围类似[0, screenWidth],[0, screenHeight]。除以4就是对取值范围进行缩放处理,换言之就是将unity_DitherMask
纹理进行放大,使其更明显。
unity_LODFade
是一个四维向量,它的x分量是一个介于[-1,1]的值,在平滑过渡的过程中,LOD0从1过渡到0,LOD1从-1过渡到0:
使用这种方式,得到的LOD过渡效果如下:
[1] Realtime GI, Probe Volumes, LOD Groups
[2] Unity3D的全局光照和阴影:下篇
[3] 标准管线下的unity_LODFade操作