UE | Mobile: Prepass Or Not? | Blurred code

UE | Mobile: Prepass Or Not?

2025/03/15

LastMod:2025/03/22

Categories: UE

Engine Version: 5.3.2

首先可以看看一篇其他人写的好博客。

参考:To z-prepass or not to z-prepass – Interplay of Light

为什么对Mask材质开PrePass有性能优势

Engine\Shaders\Private\DepthOnlyPixelShader.usf

虚幻渲染PrePass的时候用的,会根据材质生成一个 DepthOnlyPixelShader.usf,主要是给阴影和PrePass用的。 这个材质只计算Pixel Location 和 Clip相关的信息,不进行复杂的颜色计算,相当于一个轻量级的PS。

这样在PrePass的时候,就可以只跑这个轻量级的PS,可以获得完整的深度图。 然后在正式渲染的时候,就可以根据这个深度图进行early-z优化(开启Z Test Mode = Equal).

常见Bug: Mask材质开启PrePass可能导致IOS上闪烁的问题

如果Prepass的shader编译选项和Basepass的VS的编译选项不一致可能会导致Basepass有一些pixel无法正确通过early-z的测试,导致闪烁的问题。

IOS Mask材质开启prepass导致闪烁 - 知乎https://blog.csdn.net/weixin_39864682/article/details/111803055

Metal 2.1中的新关键字invariant - 知乎

Full Prez / Masked Prez

缺点:

纯Vulkan-API对dc不是很敏感,可以视性能评估的结果来决定。从鸣潮和原神的抓帧的结果来分析,Mobile上似乎是没有开prepass的。可能还是主要考虑到低端机的性能问题。

优点:

允许虚幻动态切换Prepass和不Prepass

虚幻在Mobile控制EarlyPass主要是如下的cvar控制:

该cvar是静态的,会作为宏定义在shader里,并且影响若干个pass的采样深度的代码。 主要是如果定义了FullPreZ,

static TAutoConsoleVariable<int32> CVarMobileEarlyZPass(
	TEXT("r.Mobile.EarlyZPass"),
	0,
	TEXT("Whether to use a depth only pass to initialize Z culling for the mobile base pass. Changing this setting requires restarting the editor.\n")
	TEXT("  0: off\n")
	TEXT("  1: all opaque \n")
	TEXT("  2: masked primitives only \n"),
	ECVF_ReadOnly
);

那么如果我们想让手机可以在Prepass和Not Prepass之间切换,应该怎么做呢?

仔细观察shader里采取深度的地方,我们注意到,只要我们强行定义FORCE_DEPTH_TEXTURE_READS这个宏,我们就能让所有Shader的采样深度从FullPrepassZ里读取。

/** Returns DeviceZ which is the z value stored in the depth buffer. */
float LookupDeviceZ( float2 ScreenUV )
{
#if	SCENE_TEXTURES_DISABLED
	return FarDepthValue;
#elif FORCE_DEPTH_TEXTURE_READS || PLATFORM_NEEDS_DEPTH_TEXTURE_READS
	// native Depth buffer lookup
	return Texture2DSampleLevel(MobileSceneTextures.SceneDepthTexture, MobileSceneTextures.SceneDepthTextureSampler, ScreenUV, 0).r;
#elif MOBILE_DEPTHFECTH && COMPILER_GLSL_ES3_1
	return DepthbufferFetchES2();
#elif MOBILE_DEPTHFECTH && VULKAN_PROFILE
	return VulkanSubpassDepthFetch();
#elif MOBILE_DEPTHFECTH && (METAL_PROFILE && !MAC)
	return DepthbufferFetchES2();
#elif (USE_SCENE_DEPTH_AUX && !MOBILE_DEFERRED_SHADING)
	// SceneDepth texture is discarded after BasePass (with forward shading) 
	// instead fetch DeviceZ from SceneDepthAuxTexture
	return Texture2DSampleLevel(MobileSceneTextures.SceneDepthAuxTexture, MobileSceneTextures.SceneDepthAuxTextureSampler, ScreenUV, 0).r;
#else
	// native Depth buffer lookup
	return Texture2DSampleLevel(MobileSceneTextures.SceneDepthTexture, MobileSceneTextures.SceneDepthTextureSampler, ScreenUV, 0).r;
#endif
}

所以改动思路还是很简单:

IS_MOBILE_DEPTHREAD_SUBPASS实际上是作为一个宏MOBILE_DEPTHFECTH的条件。为什么要强行设置成1 ? 因为如果不设置成1,cook后的结果不会包含带有subpass的变体,没法动态切换过来。

最后我们的逻辑变成了

edit-061decb12d604c73a6678d90c94bf4ee-2025-03-11-14-56-35

View上额外补充一个uniform来标记一个变量 edit-061decb12d604c73a6678d90c94bf4ee-2025-03-11-14-57-39