UE贴花光照烘培难点分析
背景
项目里, 经常使用很多贴花去补充一些细节
所以想尝试一下移动端带光照的贴花, 截至UE 4.27版本移动端贴花仅支持平行光光照, 而不支持烘焙
分析
其中涉及的几个比较重要的分支宏, 控制同一套Shader的不同处理流程
宏\同场景下测试平台值 | PC | Mobile | 备注 |
---|---|---|---|
DECAL_RENDERTARGET_COUNT | 3 | 1 | 控制GBuffer缓存的个数 |
DECAL_OUTPUT_NORMAL | false | ||
DECAL_RENDERSTAGE | 2 | 3 | 0:before base pass, 1:after base pass, 2:before lighting, 3:mobile |
DECAL_BLEND_MODE | DBM_Translucent | DBM_Translucent |
PC平台
管线
PC贴花预灯光
PC贴花灯光
C++部分
计算最后的贴花混合模式等, 当满足诸如移动延迟渲染等条件, 会触发一定强制转换
/** Returns if the GBuffer is used. Only valid for the current platform. */
inline bool IsUsingGBuffers(const FStaticShaderPlatform Platform)
{
if (IsMobilePlatform(Platform))
{
return IsMobileDeferredShadingEnabled(Platform);
}
...
}
static EDecalBlendMode ComputeFinalDecalBlendMode(EShaderPlatform Platform, EDecalBlendMode DecalBlendMode, bool bUseNormal)
{
//
const bool bShouldConvertToDBuffer = !IsUsingGBuffers(Platform) && !IsSimpleForwardShadingEnabled(Platform) && IsUsingDBuffers(Platform);
...
}
贴花渲染自发光相比常规延迟贴花多一些参数设置
// DecalRenderingShared.cpp
void FDecalRendering::SetShader(FRHICommandList& RHICmdList, ...)
{
// When in shader complexity, decals get rendered as emissive even though there might not be emissive decals.
// FDeferredDecalEmissivePS might not be available depending on the decal blend mode.
TShaderRef<FDeferredDecalPS> PixelShader = (DecalRenderStage == DRS_Emissive && DebugViewMode == DVSM_None)
? TShaderRef<FDeferredDecalPS>(MaterialShaderMap->GetShader<FDeferredDecalEmissivePS>())
: MaterialShaderMap->GetShader<FDeferredDecalPS>();
TShaderMapRef<FDeferredDecalVS> VertexShader(View.ShaderMap);
PixelShader->SetParameters(...);
VertexShader->SetParameters(...);
}
Shader部分
// DeferredDecal.usf
void FPixelShaderInOut_MainPS(inout FPixelShaderIn In, inout FPixelShaderOut Out)
{
...
FGBufferData GBufferData;
GBufferData.BaseColor = BaseColor;
GBufferData.Metallic = Metallic;
GBufferData.Specular = Specular;
...
DecalCommonOutput(In, Out, Color, Opacity, GBufferData);
}
void DecalCommonOutput(inout FPixelShaderIn In, inout FPixelShaderOut Out, ... FGBufferData Data)
{
// RETURN_COLOR not needed unless writing to SceneColor
Out.MRT[0] = float4(Color, Opacity);
...
#if DECAL_RENDERTARGET_COUNT > 1
#if DECAL_RENDERSTAGE == 0
// some MRT rendering
#elif DECAL_RENDERSTAGE == 1 || DECAL_RENDERSTAGE == 2 || DECAL_RENDERSTAGE == 3
{
// 1 == after base pass (GBuffer) || 2 = before lighting (GBuffer)
#if DECAL_OUTPUT_NORMAL
EncodeGBuffer(Data, Out.MRT[1], Out.MRT[2], Out.MRT[3], OutTarget4, OutTarget5, OutTarget6);
#else
EncodeGBuffer(Data, OutTarget1, Out.MRT[1], Out.MRT[2], OutTarget4, OutTarget5, OutTarget6);
#endif
}
#endif
...
}
// DecalCommon.ush
/** Populates OutGBufferA, B and C */
void EncodeGBuffer(
FGBufferData GBuffer,
out float4 OutGBufferA,
out float4 OutGBufferB,
out float4 OutGBufferC,
...)
{
...
OutGBufferB.r = GBuffer.Metallic;
OutGBufferB.g = GBuffer.Specular;
OutGBufferB.b = GBuffer.Roughness;
OutGBufferB.a = EncodeShadingModelIdAndSelectiveOutputMask(GBuffer.ShadingModelID, GBuffer.SelectiveOutputMask);
OutGBufferC.rgb = EncodeBaseColor( GBuffer.BaseColor );
#if ALLOW_STATIC_LIGHTING
// No space for AO. Multiply IndirectIrradiance by AO instead of storing.
OutGBufferC.a = EncodeIndirectIrradiance(GBuffer.IndirectIrradiance * GBuffer.GBufferAO) + QuantizationBias * (1.0 / 255.0);
#else
OutGBufferC.a = GBuffer.GBufferAO;
#endif
...
}
// DeferredLightPixelShaders.usf
void DeferredLightPixelMain(
...
out float4 OutColor : SV_Target0
)
{
...
FScreenSpaceData ScreenSpaceData = GetScreenSpaceData(InputParams.ScreenUV);
...
const float4 Radiance = GetDynamicLighting(..., ScreenSpaceData.GBuffer, ...);
const float Attenuation = ComputeLightProfileMultiplier(...);
OutColor += (Radiance * Attenuation);
...
}
// DeferredShadingCommon.ush
// @param UV - UV space in the GBuffer textures (BufferSize resolution)
FScreenSpaceData GetScreenSpaceData(float2 UV, bool bGetNormalizedNormal = true)
{
FScreenSpaceData Out;
Out.GBuffer = GetGBufferData(UV, bGetNormalizedNormal);
...
return Out;
}
// @param UV - UV space in the GBuffer textures (BufferSize resolution)
FGBufferData GetGBufferData(float2 UV, bool bGetNormalizedNormal = true)
{
...
float4 GBufferC = Texture2DSampleLevel(SceneTexturesStruct.GBufferCTexture, SceneTexturesStruct_GBufferCTextureSampler, UV, 0);
...
return DecodeGBufferData(GBufferA, GBufferB, GBufferC, ...);
}
// DeferredLightingCommon.usf
/** Calculates lighting for a given position, normal, etc with a fully featured lighting model designed for quality. */
FDeferredLightingSplit GetDynamicLightingSplit(..., FGBufferData GBuffer, ...)
{
...
#if NON_DIRECTIONAL_DIRECT_LIGHTING
float Lighting;
Lighting = IntegrateLight(...);
float3 LightingDiffuse = Diffuse_Lambert( GBuffer.DiffuseColor ) * Lighting;
LightAccumulator_AddSplit(LightAccumulator, LightingDiffuse, 0.0f, 0, ...);
#else
FDirectLighting Lighting;
Lighting = IntegrateBxDF( GBuffer, ...);
Lighting.Specular *= LightData.SpecularScale;
LightAccumulator_AddSplit( LightAccumulator, Lighting.Diffuse, Lighting.Specular, Lighting.Diffuse, LightColor * LightMask * Shadow.SurfaceShadow, ...);
LightAccumulator_AddSplit( LightAccumulator, Lighting.Transmission, 0.0f, Lighting.Transmission, LightColor * LightMask * Shadow.TransmissionShadow, ...);
#endif
...
}
移动平台
管线
移动端地板
移动端贴花
C++相关
准备好Decal Pass所需Shader
// MobileDecalRendering.cpp: Decals for mobile renderer
void RenderDeferredDecalsMobile(FRHICommandList& RHICmdList, ...)
{
...
// Set shader params
FDecalRendering::SetShader(...);
...
}
计算移动平台所需的参数, 诸如贴花混合模式、RenderTarget类型等
// DecalRenderingCommon.h
static ERenderTargetMode ComputeRenderTargetMode(EShaderPlatform Platform, EDecalBlendMode DecalBlendMode, bool bHasNormal)
{
if (IsMobilePlatform(Platform))
{
return IsMobileDeferredShadingEnabled(Platform) ? RTM_SceneColorAndGBufferWithNormal : RTM_SceneColor;
}
...
}
// @return DECAL_RENDERTARGET_COUNT for the shader
static uint32 ComputeRenderTargetCount(EShaderPlatform Platform, ERenderTargetMode RenderTargetMode)
{
// has to be SceneColor on mobile
check(!IsMobilePlatform(Platform) || RenderTargetMode == RTM_SceneColor || IsMobileDeferredShadingEnabled(Platform));
...
return 0;
}
可行性方案探究
移动端贴花的渲染仅以一定透明度的方式, 叠到SceneColor上去
一
- 步骤
- 延迟贴花渲染Pass上, 转换贴花uv到接受贴花的mesh uv空间, 采样烘培纹理混合
- 问题
- 烘培纹理难获取
- uv跨mesh转换难, 尤其针对一贴花跨多mesh
二
- 步骤
- 在BasePass前插入Decal Pass, 单独渲染贴花到纹理T0上, (类似PC上贴花渲染到GBufferC上)
- 以混合T0及SceneColor纹理, 替代延迟贴花渲染Pass
- 后续Decal Pass去掉
- 问题
- 贴花的着色计算复杂度较高, 后续的着色步骤基本按需移植
三
- 能否使用LightProbe方式?
无论哪种方式, 若解决上述问题, 均可保证贴花在移动过程中, 能做到与烘培的投影面混合