目录
- [一、URP LOD 组件](#一、URP LOD 组件)
- 二、反射探针
-
- [1. 获取反射探针数据](#1. 获取反射探针数据)
- [2. 环境光照明 IBL](#2. 环境光照明 IBL)
- [3. 反射探针(Reflection Probes)](#3. 反射探针(Reflection Probes))
- [4. Box Projection 盒体投影](#4. Box Projection 盒体投影)
中文版:https://edu.uwa4d.com/lesson-detail/282/1314/0?isPreview=0
英文原版:https://catlikecoding.com/unity/tutorials/custom-srp/lod-and-reflections/
一、URP LOD 组件
1、LOD Group的使用
1. 首先该组件需要将子类模型置于该组件物体子节点下
2. 可在单个LOD中设置其level的模型,并可设置它的距离范围,即可在不同距离下显示不同的模型
若模型变化时间不符合预期,有如下几种方式调节:
- 重新继续Bound,Unity会根据三种LOD模型计算该模型的包围盒,从而在计算物体占屏幕
2. Project Settings > Quality > Level of Detail > LOD Bias, 该变量增大物体的评估高度,从而导致LOD切换时占比与实践物体占比不同。将 LOD Bias设为1,可以使组件阈值与实际大小同步。
- LOD过渡类型:
- Cross Fade(淡入淡出),Fade Transition Width可调节过渡区域占比
- Cross Fade(淡入淡出),Fade Transition Width可调节过渡区域占比
2、LOD切换原理
Cross Fade(淡入淡出)模式
当启用Cross Fade(淡入淡出)模式,相邻两个LOD对象会同时渲染出来,着色器将以某种方式进行混合。Unity通常使用屏幕抖动或者混合来实现Cross Fade。
在URP通用管线中,LOD现只用于SpeedTree7XXX.shader,即大面积树木的渲染,其余Shader并未使用,但并不代表不可以自定义。
我们可以通过UnityPerDraw
下的变量float4 unity_LODFade;
取得LOD信息
cpp
CBUFFER_START(UnityPerDraw)
float4 unity_LODFade;
// x is the fade value ranging within [0,1].
// y is x quantized into 16 levels
CBUFFER_END
- x分量存储过渡因子(逐渐远离消失的LOD对象,x分量从1变换到0;渐入的LOD对象,x分量从0变换到 -1 )
- y分量存储了相同的因子,只不过被量化为16步
即,若渐出LOD的x值为0.4,则渐入LOD的值为 -0.6
我们可以通过一个Noise图来决定使用哪一级LOD的选择。
通过如下内置函数,即可快速得到一个Noise图
float dither = InterleavedGradientNoise(positionCS.xy, 0);
使用屏幕抖动实现LOD混合
cpp
void ShadowCasterPassFragment (Varyings input) {
UNITY_SETUP_INSTANCE_ID(input);
ClipLOD(input.positionCS.xy, unity_LODFade.x);
...
}
void ClipLOD (float2 positionCS, float fade) {
#if defined(LOD_FADE_CROSSFADE)
float dither = InterleavedGradientNoise(positionCS.xy, 0);
clip(fade + (fade < 0.0 ? dither : -dither));
#endif
}
Animated Cross-Fading
启用Animated Cross-Fading后,不再通过距离去设置渐入渐出的比例,当物体组比例超过LOD阈值就通过动画快速交叉渐变。
默认的动画持续时间为半秒,可以通过设置static LODGroup.crossFadeAnimationDuration
来更改所有组的动画持续时间。然而,在unity2022中,当不在播放模式下,转换速度更快。
如果未设置Clip,并且Fade Transition Width不为0
则,当距离处于交叉切换之间,会使两个物体同时被渲染出来。
因此,如果未实现LOD交叉切换算法,请不要使用CrossFade选项。
LOD物体烘培
LOD0会被用于光照映射(Lightmapping)。其他LOD级别也会得到烘焙照明(Baked Light),但场景的其余部分只考虑LOD 0。你也可以决定只烘焙一些级别,让其他级别依靠光探针。
SpeedTree 模式
这种模式是专门针对SpeedTree树的,它使用自己的LOD系统来折叠树,并在3D模型和广告牌表示之间进行转换。
二、反射探针
1. 获取反射探针数据
- 如果未定义LightMap(烘培光照),则会使用球谐函数作为基础环境光颜色。此球谐函数即为环境球的球谐采样结果。
- 若想使用IBL作为镜面反射,需要添加反射探针标志。
cpp
perObjectData |= PerObjectData.ReflectionProbes;
2. 环境光照明 IBL
在Unity中,IBL环境光照贴图保存在UnityInput.hlsl
cpp
// Unity specific
TEXTURECUBE(unity_SpecCube0);
SAMPLER(samplerunity_SpecCube0);
TEXTURECUBE(unity_SpecCube1);
SAMPLER(samplerunity_SpecCube1);
在GlobalIllumination.hlsl中,使用函数
cpp
half3 CalculateIrradianceFromReflectionProbes(half3 reflectVector, float3 positionWS, half perceptualRoughness)
即可得到环境光照。函数中使用
cpp
half4 encodedIrradiance = half4(SAMPLE_TEXTURECUBE_LOD(unity_SpecCube0, samplerunity_SpecCube0, reflectVector, mip));
得到CubeMap Mipmap插值采样结果,并根据是否使用HDR解码。
cpp
#if defined(UNITY_USE_NATIVE_HDR)
irradiance += weightProbe0 * encodedIrradiance.rbg;
#else
irradiance += weightProbe0 * DecodeHDREnvironment(encodedIrradiance, unity_SpecCube0_HDR);
#endif // UNITY_USE_NATIVE_HDR
SAMPLE_TEXTURECUBE_LOD函数根据使用的API不同,实现各不相同
IBL在URP Shader中,通过如下函数得到包括环境光照在内的所有全局光照。
cpp
lightingData.giColor = GlobalIllumination(brdfData, brdfDataClearCoat, surfaceData.clearCoatMask,
inputData.bakedGI, aoFactor.indirectAmbientOcclusion, inputData.positionWS,
inputData.normalWS, inputData.viewDirectionWS);
cpp
half3 reflectVector = reflect(-viewDirectionWS, normalWS);
half NoV = saturate(dot(normalWS, viewDirectionWS));
half fresnelTerm = Pow4(1.0 - NoV);
half3 indirectDiffuse = bakedGI;
half3 indirectSpecular = GlossyEnvironmentReflection(reflectVector, positionWS, brdfData.perceptualRoughness, 1.0h);
half3 color = EnvironmentBRDF(brdfData, indirectDiffuse, indirectSpecular, fresnelTerm);
- bakedGI值为LightMap烘培光照数据,或者是环境球谐的采样结果。
- 通过GlossyEnvironmentReflection计算基于环境的镜面反射光照
- 最后通过EnvironmentBRDF函数获取混合结果(漫反射颜色 * 漫反射系数 + 镜面反射颜色 * 镜面反射系数)。
cpp
half3 EnvironmentBRDF(BRDFData brdfData, half3 indirectDiffuse, half3 indirectSpecular, half fresnelTerm)
{
half3 c = indirectDiffuse * brdfData.diffuse;
c += indirectSpecular * EnvironmentBRDFSpecular(brdfData, fresnelTerm);
return c;
}
3. 反射探针(Reflection Probes)
在场景中未使用反射探针时,场景中具有反射(包括镜面反射Specular和金属反射Metallic)的物体会使用天空贴图(包括天空面 Sky Plane / 天空盒 Sky Box / 天空球 Sky Dome)的信息来制作反射效果。
默认的 environment cube map 只包含天空盒。为了反射场景中的其他东西,我们必须通过GameObject / Light / reflection probe为其添加反射探针。这些探测器在它们的位置处看向场景,将场景渲染为立方体贴图。因此,只有在靠近探测器的表面上,反射才会显得或多或少正确。因此,通常需要在一个场景中放置多个探头。它们具有Importance和Box Size属性,可用于控制每个探针影响的区域。
探测器的Type默认设置为Baked,这意味着它只渲染一次,CubeMap在构建时渲染。你也可以将其设置为实时,这将使地图与动态场景保持同步。它像任何其他相机一样被渲染,使用我们的RP,对立方体地图的六个面分别渲染一次。所以实时反射探测器很昂贵。
每个对象只使用一个环境探针,但场景中可以有多个探针。因此,你可能不得不 split 对象以获得 acceptable(理想的) 的反射。例如,理想情况下,用于构建结构的立方体应该分成单独的内部和外部部分,这样每个部分都可以使用不同的反射探头。此外,这意味着GPU batching 会被反射探针破坏。不幸的是,网格球根本不能使用反射探针,所以渲染出来的总是天空盒。
MeshRenderer组件有一个Anchor Override(直接将带有Probe的物体拖上去就好),可以用来微调它们使用的探针,而不必担心盒子的大小和位置。还有一个Reflection Probes,默认设置为Blend Probes。我们的想法是,Unity允许在最好的两个反射探针之间进行混合。然而,这种模式与SRP批处理程序不兼容,所以Unity的其他rp不支持它,我们也不支持。如果你很好奇,我在2018年SRP教程的反射教程中解释了如何混合探针,但我希望这个功能在遗留管道被删除后消失。
我们将在将来研究其他反射技术。所以仅有的两个功能模式是Off,它总是使用天空盒子,和Simple,它选择最重要的探测器。其他的功能和Simple完全一样。
除此之外,反射探针还可以选择启用box projection mode。这将改变如何确定反射以更好地匹配其有限的影响区域,但SRP批处理程序也不支持这一点,因此我们也不支持它。
注 :物体在选择反射探针时,确定物体是否在探针包围盒内,如果在,则加入列表;再根据所有影响物体的反射探针的权重,只保留权重最大的一批(若有2个权重为5,3个权重为3,则只保留权重为5的探针),再根据交叉体积权重计算保留的反射探针的系数。
特别的,当大包围盒反射探针完全覆盖小包围盒反射探针,且物体处于小包围盒反射探针内,则默认最小包围盒探针为最重要探针。
解码探针
最后,我们必须确保正确地解释来自CubeMap的数据。它可以是HDR或LDR,其强度也可以调节。这些设置是通过unity_SpecCube0_HDR矢量提供的,它在UnityPerDraw缓冲区中的unity_ProbesOcclusion之后。
cpp
// Reflection Probe 0 block feature
// HDR environment map decode instructions
real4 unity_SpecCube0_HDR;
real4 unity_SpecCube1_HDR;
解码函数:
cpp
float3 SampleEnvironment (Surface surfaceWS, BRDF brdf) {
...
return DecodeHDREnvironment(environment, unity_SpecCube0_HDR);
}
4. Box Projection 盒体投影
引自:技术美术杂谈 反射探针(Reflection Probe)
在未开启盒体投影时,反射贴图的图像通常是通过从无限远处投射的。
开启盒体投影允许我们使用探针包围盒尺寸Probe Size 、探针包围盒偏移 Probe Offset 以及镜头与反射物体的距离作为参数,控制反射探针所生成的反射贴图的尺寸和效果。
通常情况下,Unity默认开启反射探针的盒体投影支持。如果想自定义不同画面质量是否支持盒体投影效果,可以在编辑面板 Edit →项目设置 Project Settings →图像设置 Graphics →画面分级设置 Tier Settings 中取消勾选使用默认设置 Use Defaults ,并进行自定义设置。