以前、UE4で簡単ではありますが Weighted Blended OIT を実装してみました。
monsho.hatenablog.com
この技術の詳細については上の記事と、そこにリンクしているペーパーをお読みください。
あまり使われていない技術だとは思うのですが、『Saints Row』シリーズでおなじみのVolitionの作品『Agents of Mayhem』で使用されていることがGDC 2018で発表されています。
www.gdcvault.com
こちらはウェイト関数や様々な問題に対して調整を行っていますので、もしこの技術を実装したいのであれば上のリンクを参考にしてみてください。
さて、今回は要望がありましたので、UE 4.12で実装していたものをUE 4.20.3に対応させてみました。
ほんとに単純に対応しただけなので、『Agents of Mayhem』くらいちゃんとやりたい!という人はこの修正を参考にしてきちんと作成してみましょう。
前回と同様にソースコードを修正しますので、お仕事で使用する場合は十分に注意してください。
では、各ソースコードの修正項目を見ていきます。
Engine/Source/Runtime/Renderer/Private/PostProcess/SceneRenderTargets.h
near by line 231
void FreeSeparateTranslucency()
{
SeparateTranslucencyRT.SafeRelease();
check(!SeparateTranslucencyRT);
SeparateTranslucencyAlphaRT.SafeRelease();
check(!SeparateTranslucencyAlphaRT);
}
near by line 278
TRefCountPtr<IPooledRenderTarget>& GetSeparateTranslucency(FRHICommandList& RHICmdList, FIntPoint Size);
TRefCountPtr<IPooledRenderTarget>& GetSeparateTranslucencyAlpha(FRHICommandList& RHICmdList, FIntPoint Size);
near by line 567
TRefCountPtr<IPooledRenderTarget> SeparateTranslucencyRT;
TRefCountPtr<IPooledRenderTarget> DownsampledTranslucencyDepthRT;
TRefCountPtr<IPooledRenderTarget> SeparateTranslucencyAlphaRT;
Engine/Source/Runtime/Renderer/Private/PostProcess/SceneRenderTargets.cpp
near by line 1268
uint32 Flags = TexCreate_RenderTargetable;
FPooledRenderTargetDesc Desc(FPooledRenderTargetDesc::Create2DDesc(Size, PF_FloatRGBA, FClearValueBinding(), TexCreate_None, Flags, false));
Desc.Flags |= GFastVRamConfig.SeparateTranslucency;
near by line 1279
TRefCountPtr<IPooledRenderTarget>& FSceneRenderTargets::GetSeparateTranslucencyAlpha(FRHICommandList& RHICmdList, FIntPoint Size)
{
if (!SeparateTranslucencyAlphaRT || SeparateTranslucencyAlphaRT->GetDesc().Extent != Size)
{
uint32 Flags = TexCreate_RenderTargetable;
FPooledRenderTargetDesc Desc(FPooledRenderTargetDesc::Create2DDesc(Size, PF_R16F, FClearValueBinding::White, TexCreate_None, Flags, false));
Desc.Flags |= GFastVRamConfig.SeparateTranslucency;
Desc.AutoWritable = false;
Desc.NumSamples = GetNumSceneColorMSAASamples(CurrentFeatureLevel);
GRenderTargetPool.FindFreeElement(RHICmdList, Desc, SeparateTranslucencyAlphaRT, TEXT("SeparateTranslucencyAlpha"));
}
return SeparateTranslucencyAlphaRT;
}
near by line 1355
TRefCountPtr<IPooledRenderTarget>* SeparateTranslucency;
TRefCountPtr<IPooledRenderTarget>* SeparateTranslucencyAlpha;
if (bSnapshot)
{
check(SeparateTranslucencyRT.GetReference());
check(SeparateTranslucencyAlphaRT.GetReference());
SeparateTranslucency = &SeparateTranslucencyRT;
SeparateTranslucencyAlpha = &SeparateTranslucencyAlphaRT;
}
else
{
SeparateTranslucency = &GetSeparateTranslucency(RHICmdList, SeparateTranslucencyBufferSize);
SeparateTranslucencyAlpha = &GetSeparateTranslucencyAlpha(RHICmdList, SeparateTranslucencyBufferSize);
}
const FTexture2DRHIRef &SeparateTranslucencyDepth = SeparateTranslucencyScale < 1.0f ? (const FTexture2DRHIRef&)GetDownsampledTranslucencyDepth(RHICmdList, SeparateTranslucencyBufferSize)->GetRenderTargetItem().TargetableTexture : GetSceneDepthSurface();
#if 0
#else
{
ERenderTargetLoadAction ColorLoadAction = bFirstTimeThisFrame ? ERenderTargetLoadAction::EClear : ERenderTargetLoadAction::ELoad;
ERenderTargetStoreAction ColorStoreAction = ERenderTargetStoreAction::EStore;
ERenderTargetLoadAction DepthLoadAction = ERenderTargetLoadAction::ELoad;
ERenderTargetStoreAction DepthStoreAction = ERenderTargetStoreAction::ENoAction;
FRHIRenderTargetView ColorViews[2] = {
FRHIRenderTargetView((*SeparateTranslucency)->GetRenderTargetItem().TargetableTexture, 0, -1, ColorLoadAction, ColorStoreAction),
FRHIRenderTargetView((*SeparateTranslucencyAlpha)->GetRenderTargetItem().TargetableTexture, 0, -1, ColorLoadAction, ColorStoreAction)
};
FRHISetRenderTargetsInfo Info(2, ColorViews, FRHIDepthRenderTargetView(SeparateTranslucencyDepth, DepthLoadAction, DepthStoreAction, FExclusiveDepthStencil::DepthRead_StencilWrite));
RHICmdList.SetRenderTargetsAndClear(Info);
}
#endif
near by line 1433
FTextureRHIParamRef RenderTargets[2]{};
SetRenderTargets(RHICmdList, 2, RenderTargets, nullptr, 0, nullptr);
TRefCountPtr<IPooledRenderTarget>* SeparateTranslucency;
TRefCountPtr<IPooledRenderTarget>* SeparateTranslucencyAlpha;
TRefCountPtr<IPooledRenderTarget>* SeparateTranslucencyDepth;
if (bSnapshot)
{
check(SeparateTranslucencyRT.GetReference());
SeparateTranslucency = &SeparateTranslucencyRT;
SeparateTranslucencyAlpha = &SeparateTranslucencyAlphaRT;
SeparateTranslucencyDepth = SeparateTranslucencyScale < 1.f ? &DownsampledTranslucencyDepthRT : &SceneDepthZ;
}
else
{
SeparateTranslucency = &GetSeparateTranslucency(RHICmdList, SeparateTranslucencyBufferSize);
SeparateTranslucencyAlpha = &GetSeparateTranslucencyAlpha(RHICmdList, SeparateTranslucencyBufferSize);
SeparateTranslucencyDepth = SeparateTranslucencyScale < 1.f ? &GetDownsampledTranslucencyDepth(RHICmdList, SeparateTranslucencyBufferSize) : &SceneDepthZ;
}
const FResolveRect SeparateResolveRect(
View.ViewRect.Min.X * SeparateTranslucencyScale,
View.ViewRect.Min.Y * SeparateTranslucencyScale,
View.ViewRect.Max.X * SeparateTranslucencyScale,
View.ViewRect.Max.Y * SeparateTranslucencyScale
);
RHICmdList.CopyToResolveTarget((*SeparateTranslucency)->GetRenderTargetItem().TargetableTexture, (*SeparateTranslucency)->GetRenderTargetItem().ShaderResourceTexture, SeparateResolveRect);
RHICmdList.CopyToResolveTarget((*SeparateTranslucencyAlpha)->GetRenderTargetItem().TargetableTexture, (*SeparateTranslucencyAlpha)->GetRenderTargetItem().ShaderResourceTexture, SeparateResolveRect);
RHICmdList.CopyToResolveTarget((*SeparateTranslucencyDepth)->GetRenderTargetItem().TargetableTexture, (*SeparateTranslucencyDepth)->GetRenderTargetItem().ShaderResourceTexture, SeparateResolveRect);
Engine/Source/Runtime/Renderer/Private/BasePassRendering.h
near by line 806
case BLEND_Translucent:
DrawRenderState.SetBlendState(TStaticBlendState<
CW_RGBA, BO_Add, BF_One, BF_One, BO_Add, BF_One, BF_One,
CW_RGBA, BO_Add, BF_Zero, BF_InverseSourceColor>::GetRHI());
break;
Engine/Source/Runtime/Renderer/Private/PostProcess/PostProcessBokehDOFRecombine.h
near by line 17
class FRCPassPostProcessBokehDOFRecombine : public TRenderingCompositePassBase<4, 1>
Engine/Source/Runtime/Renderer/Private/PostProcess/PostProcessing.cpp
near by line 514
static void AddPostProcessDepthOfFieldBokeh(FPostprocessContext& Context, FRenderingCompositeOutputRef& SeparateTranslucency, FRenderingCompositeOutputRef& SeparateTranslucencyAlpha, FRenderingCompositeOutputRef& VelocityInput)
near by line 555
FRenderingCompositePass* NodeRecombined = Context.Graph.RegisterPass(new(FMemStack::Get()) FRCPassPostProcessBokehDOFRecombine(bIsComputePass));
NodeRecombined->SetInput(ePId_Input0, Context.FinalOutput);
NodeRecombined->SetInput(ePId_Input1, NodeBlurred);
NodeRecombined->SetInput(ePId_Input2, SeparateTranslucency);
NodeRecombined->SetInput(ePId_Input3, SeparateTranslucencyAlpha);
near by line 1406
FRenderingCompositeOutputRef SeparateTranslucency;
FRenderingCompositeOutputRef SeparateTranslucencyAlpha;
FRenderingCompositeOutputRef BloomOutputCombined;
near by line 1427
if (FSceneRenderTargets::Get(RHICmdList).SeparateTranslucencyRT)
{
FRenderingCompositePass* NodeSeparateTranslucency = Context.Graph.RegisterPass(new(FMemStack::Get()) FRCPassPostProcessInput(FSceneRenderTargets::Get(RHICmdList).SeparateTranslucencyRT));
SeparateTranslucency = FRenderingCompositeOutputRef(NodeSeparateTranslucency);
FRenderingCompositePass* NodeSeparateTranslucencyAlpha = Context.Graph.RegisterPass(new(FMemStack::Get()) FRCPassPostProcessInput(FSceneRenderTargets::Get(RHICmdList).SeparateTranslucencyAlphaRT));
SeparateTranslucencyAlpha = FRenderingCompositeOutputRef(NodeSeparateTranslucencyAlpha);
near by line 1531
if(bBokehDOF)
{
if (FPostProcessing::HasAlphaChannelSupport())
{
UE_LOG(LogRenderer, Log, TEXT("Boked depth of field does not have alpha channel support. Only Circle DOF has."));
}
if(VelocityInput.IsValid())
{
AddPostProcessDepthOfFieldBokeh(Context, SeparateTranslucency, SeparateTranslucencyAlpha, VelocityInput);
}
else
{
FRenderingCompositePass* NoVelocity = Context.Graph.RegisterPass(new(FMemStack::Get()) FRCPassPostProcessInput(GSystemTextures.BlackDummy));
FRenderingCompositeOutputRef NoVelocityRef(NoVelocity);
AddPostProcessDepthOfFieldBokeh(Context, SeparateTranslucency, SeparateTranslucencyAlpha, NoVelocityRef);
}
bSepTransWasApplied = true;
}
if(SeparateTranslucency.IsValid() && !bSepTransWasApplied)
{
checkf(!FPostProcessing::HasAlphaChannelSupport(), TEXT("Separate translucency was supposed to be disabled automatically."));
const bool bIsComputePass = ShouldDoComputePostProcessing(Context.View);
FRenderingCompositePass* NodeRecombined = Context.Graph.RegisterPass(new(FMemStack::Get()) FRCPassPostProcessBokehDOFRecombine(bIsComputePass));
NodeRecombined->SetInput(ePId_Input0, Context.FinalOutput);
NodeRecombined->SetInput(ePId_Input2, SeparateTranslucency);
NodeRecombined->SetInput(ePId_Input3, SeparateTranslucencyAlpha);
Context.FinalOutput = FRenderingCompositeOutputRef(NodeRecombined);
}
Engine/Shaders/Private/BasePassPixelShader.usf
near by line 1073
#elif MATERIALBLENDING_TRANSLUCENT
Out.MRT[0] = half4(Color * Fogging.a + Fogging.rgb, Opacity);
Out.MRT[0] = RETURN_COLOR(Out.MRT[0]);
{
Out.MRT[1] = Out.MRT[0].aaaa;
float screenZ = SvPositionToScreenPosition(MaterialParameters.SvPosition).z;
float z = MaterialParameters.SvPosition.z / MaterialParameters.SvPosition.w;
float a = Out.MRT[0].a * Out.MRT[0].a;
float w = a * a * max(1e-2, min(3.0 * 1e3, 0.03 / (1e-5 + pow(MaterialParameters.SvPosition.w / 1000.0, 4.0))));
Out.MRT[0] *= w;
}
#elif MATERIALBLENDING_ADDITIVE
Out.MRT[0] = half4(Color * Fogging.a * Opacity, 0.0f);
Out.MRT[0] = RETURN_COLOR(Out.MRT[0]);
Out.MRT[1] = half4(0, 0, 0, 0);
#elif MATERIALBLENDING_MODULATE
half3 FoggedColor = lerp(float3(1, 1, 1), Color, Fogging.aaa * Fogging.aaa);
Out.MRT[0] = half4(FoggedColor, Opacity);
Out.MRT[1] = half4(0, 0, 0, 0);
ここにウェイト関数があります。
調整はここで行うと良いでしょう。
near by line 1185
#define PIXELSHADEROUTPUT_BASEPASS 1
#define PIXELSHADEROUTPUT_MRT
#define PIXELSHADEROUTPUT_MRT1 (USES_GBUFFER && (!SELECTIVE_BASEPASS_OUTPUTS || !MATERIAL_SHADINGMODEL_UNLIT)) || (MATERIALBLENDING_TRANSLUCENT || MATERIALBLENDING_ADDITIVE || MATERIALBLENDING_MODULATE)
Engine/Shaders/Private/PostProcessBokehDOF.usf
near by line 455
#if RECOMBINE_METHOD == 2 || RECOMBINE_METHOD == 3
#if 0
#else
float4 WeightedColor;
float SeparateAlpha;
UpsampleSeparateTranslucency(WeightedColor, SeparateAlpha, SvPosition.xy, FullResUV, PostprocessInput2, PostprocessInput3, PostprocessInput2Size.zw);
if (SeparateAlpha < 1.0f)
{
float3 averageColor = WeightedColor.rgb / max(WeightedColor.a, 1e-4);
OutColor.rgb = averageColor * (1.0 - SeparateAlpha) + OutColor.rgb * SeparateAlpha;
}
#endif
#endif
Engine/Shaders/Private/SeparateTranslucency.ush
near by line 455
void BilinearUpsamplingForWeightedBlendedOIT(out float4 WeightedColor, out float Alpha, float2 UV, Texture2D LowResTex, Texture2D LowResAlphaTex)
{
WeightedColor = Texture2DSampleLevel(LowResTex, BilinearClampedSampler, UV, 0);
Alpha = Texture2DSampleLevel(LowResAlphaTex, BilinearClampedSampler, UV, 0).r;
}
void NearestDepthNeighborUpsamplingForWeightedBlendedOIT(out float4 WeightedColor, out float Alpha, float2 Position, float2 UV, Texture2D LowResTex, Texture2D LowResAlphaTex, float2 LowResTexelSize)
{
#if (SM5_PROFILE && !(METAL_SM5_PROFILE || METAL_SM5_NOTESS_PROFILE || METAL_MRT_PROFILE || PS4_PROFILE))
float MaxOperationDepth = 2000000.0f;
float4 LowResDepthBuffer = LowResDepthTexture.GatherRed(BilinearClampedSampler, UV);
float4 LowResDepth = min(float4(ConvertFromDeviceZ(LowResDepthBuffer.x), ConvertFromDeviceZ(LowResDepthBuffer.y), ConvertFromDeviceZ(LowResDepthBuffer.z), ConvertFromDeviceZ(LowResDepthBuffer.w)), MaxOperationDepth.xxxx);
float FullResDepth = min(ConvertFromDeviceZ(SceneTexturesStruct.SceneDepthTexture[uint2(Position.xy)].x), MaxOperationDepth);
float RelativeDepthThreshold = .1f;
float MinDist = 1.e8f;
float2 UV00 = UV - 0.5f * LowResTexelSize;
float2 NearestUV = UV00;
UpdateNearestSample(LowResDepth.w, UV00, FullResDepth, MinDist, NearestUV);
float2 UV10 = float2(UV00.x + LowResTexelSize.x, UV00.y);
UpdateNearestSample(LowResDepth.z, UV10, FullResDepth, MinDist, NearestUV);
float2 UV01 = float2(UV00.x, UV00.y + LowResTexelSize.y);
UpdateNearestSample(LowResDepth.x, UV01, FullResDepth, MinDist, NearestUV);
float2 UV11 = float2(UV00.x + LowResTexelSize.x, UV00.y + LowResTexelSize.y);
UpdateNearestSample(LowResDepth.y, UV11, FullResDepth, MinDist, NearestUV);
float4 Output = 0.0f;
float InvFullResDepth = 1.0f / FullResDepth;
BRANCH
if (abs(LowResDepth.w - FullResDepth) * InvFullResDepth < RelativeDepthThreshold
&& abs(LowResDepth.z - FullResDepth) * InvFullResDepth < RelativeDepthThreshold
&& abs(LowResDepth.x - FullResDepth) * InvFullResDepth < RelativeDepthThreshold
&& abs(LowResDepth.y - FullResDepth) * InvFullResDepth < RelativeDepthThreshold)
{
WeightedColor = Texture2DSampleLevel(LowResTex, BilinearClampedSampler, UV, 0);
Alpha = Texture2DSampleLevel(LowResAlphaTex, BilinearClampedSampler, UV, 0).r;
}
else
{
WeightedColor = Texture2DSampleLevel(LowResTex, PointClampedSampler, UV, 0);
Alpha = Texture2DSampleLevel(LowResAlphaTex, PointClampedSampler, UV, 0).r;
}
#else
WeightedColor = Texture2DSampleLevel(LowResTex, BilinearClampedSampler, UV, 0);
Alpha = Texture2DSampleLevel(LowResAlphaTex, BilinearClampedSampler, UV, 0).r;
#endif
}
#if 0
#else
void UpsampleSeparateTranslucency(out float4 WeightedColor, out float Alpha, float2 Position, float2 UV, Texture2D LowResTex, Texture2D LowResAlphaTex, float2 LowResTexelSize)
{
#if NEAREST_DEPTH_NEIGHBOR_UPSAMPLE
NearestDepthNeighborUpsamplingForWeightedBlendedOIT(WeightedColor, Alpha, Position, UV, LowResTex, LowResAlphaTex, LowResTexelSize);
#else
BilinearUpsamplingForWeightedBlendedOIT(WeightedColor, Alpha, UV, LowResTex, LowResAlphaTex);
#endif
}
#endif
以上です。
私の手元ではビルドが通っていますが、通らなかった人がいた場合は…まあ、頑張って修正してね!
ウェイト関数を変更したいだけなら1箇所変更すれば対応できるはずです。
また、より細かな調整をしたい場合も今回修正した部分に手を入れるだけでほとんどの場合で対応できるのではないかと思います。
ただ、通常のアルファブレンドとOITを同時に使用したい、という場合はかなり面倒な修正が必要になると思いますのでご注意ください。
また、Separate Translucency がOFFのマテリアルのブレンドもおかしくなってしまうので、この点にも注意ですね。