GTAO和HBAO比主要是加了cosine weight和multi-bounce拟合两个方面的改进
问题1:GTAO假设normal在sample slice上
解决:
其中 n p n_p np是 n n n在sample slice的投影
float3 PlaneNormal = normalize(cross(float3(ScreenDir.xy, 0), ViewDir));
float3 ProjNormal = ViewSpaceNormal - PlaneNormal * dot(ViewSpaceNormal, PlaneNormal);
首先通过ViewDir(图中 θ \theta θ)和screen dir叉乘得到Plane normal(图中Sn),再用Gram-Schmidt得到 n p n_p np
4.26版本的引擎的GTAO虽然有相应的代码,但好像无法使用,编辑器里没有,用r.AmbientMethod也没用。
结果:
PS:衰减,随着距离增加而衰减,如下,随着距离增加,之前Angle的权重不断增加,距离达到30.0后不再影响。
const float WorldRadius = 30.0f;
float AttenFactor = 2.0 / (WorldRadius * WorldRadius);
......
for (uint i = 0; i < NumSteps; i++)
{
......
FallOff = saturate(LenDsSquare * AttenFactor);
Ang = lerp(Ang, BestAng.x, FallOff);
......
}
PS:Muti-bounce
float MutiBounce(float AO,float3 albedo)
{
float3 a = 2.0404 * albedo - 0.3324;
float3 b = -4.7951 * albedo + 0.6417;
float3 c = 2.7552 * albedo + 0.6903;
return max(AO, ((AO * a + b) * AO + c) * AO);
}
PS:
float Thickness = 0.9f;
BestAng.x = (Ang > BestAng.x) ? Ang : lerp(Ang, BestAng.x, Thickness);
base color with AO +without AO
GTAO.usf
#include "/Engine/Private/Common.ush"
#include "/Engine/Private/SceneTexturesCommon.ush"
void MainVS(
in float4 InPosition : ATTRIBUTE0,
in float2 InUV : ATTRIBUTE1,
out float2 OutUV: TEXCOORD0,
out float4 OutPosition : SV_POSITION
)
{
OutUV = InUV;
OutPosition = float4(InPosition.x * 2.0 - 1.0, 1.0 - 2.0 * InPosition.y, 0, 1);
}
#define PI_HALF (PI*0.5)
static const float c_NumAngles = 8.0f;
static const uint GTAO_NUMTAPS = 10;
float2 Inv_View_Extent;
float4 GTAOParams[2];
//交错梯度噪声
//http://www.iryoku.com/next-generation-post-processing-in-call-of-duty-advanced-warfare
//https://bartwronski.com/2016/10/30/dithering-part-three-real-world-2d-quantization-dithering/
float InterleavedGradientNoise(float2 iPos)
{
return frac(52.9829189f * frac((iPos.x * 0.06711056) + (iPos.y * 0.00583715)));
}
float2 GetRandomAngleOffset(uint2 iPos)
{
iPos.y = 4096 - iPos.y;
float Angle = InterleavedGradientNoise(float2(iPos));
float Offset = (1.0 / 4.0) * ((iPos.y - iPos.x) & 3);
return float2(Angle, Offset);
}
float3 GetRandomVector(uint2 iPos)
{
iPos.y = 16384 - iPos.y;
float3 RandomVec = float3(0, 0, 0);
float3 RandomTexVec = float3(0, 0, 0);
float ScaleOffset;
float TemporalCos = GTAOParams[0].x;
float TemporalSin = GTAOParams[0].y;
TemporalCos = 0.705;
TemporalSin = 0.705;
float GradientNoise = InterleavedGradientNoise(float2(iPos));
RandomTexVec.x = cos((GradientNoise * PI));
RandomTexVec.y = sin((GradientNoise * PI));
ScaleOffset = (1.0 / 4.0) * ((iPos.y - iPos.x) & 3);
// ScaleOffset = (1.0/5.0) * (( iPos.y - iPos.x) % 5);
RandomVec.x = dot(RandomTexVec.xy, float2(TemporalCos, -TemporalSin));
RandomVec.y = dot(RandomTexVec.xy, float2(TemporalSin, TemporalCos));
RandomVec.z = frac(ScaleOffset + GTAOParams[0].z);
return RandomVec;
}
float2 SearchForLargestAngleDual(uint NumSteps, float2 BaseUV, float2 ScreenDir, float3 ViewPos, float3 ViewDir)
{
float LenDsSquare, LenDsInv,Ang,FallOff;
float3 Ds;
float2 SceneDepths = 0;
float2 BestAng = float2(-1, -1);
const float WorldRadius = 30.0f;
float AttenFactor = 2.0 / (WorldRadius * WorldRadius);
float Thickness = 0.9f;
for (uint i = 0; i < NumSteps; i++)
{
float fi = (float) i;
float2 UVOffset = ScreenDir * (fi + 1.0) * Inv_View_Extent;
UVOffset.y *= -1;
float4 UV2 = BaseUV.xyxy + float4(UVOffset.xy, -UVOffset.xy);
SceneDepths.x = ConvertFromDeviceZ(Texture2DSample(SceneTexturesStruct.SceneDepthTexture, SceneTexturesStruct_SceneDepthTextureSampler, UV2.xy).r);
SceneDepths.y = ConvertFromDeviceZ(Texture2DSample(SceneTexturesStruct.SceneDepthTexture, SceneTexturesStruct_SceneDepthTextureSampler, UV2.zw).r);
//正向 slider54
Ds = ScreenToViewPos(UV2.xy, SceneDepths.x) - ViewPos;
LenDsSquare = dot(Ds, Ds);
LenDsInv = rsqrt(LenDsSquare + 0.0001);
Ang = dot(Ds, ViewDir) * LenDsInv;
FallOff = saturate(LenDsSquare * AttenFactor);
Ang = lerp(Ang, BestAng.x, FallOff);
BestAng.x = (Ang > BestAng.x) ? Ang : lerp(Ang, BestAng.x, Thickness);;
//反向
Ds = ScreenToViewPos(UV2.zw, SceneDepths.x) - ViewPos;
LenDsSquare = dot(Ds, Ds);
LenDsInv = rsqrt(LenDsSquare + 0.0001);
Ang = dot(Ds, ViewDir) * LenDsInv;
FallOff = saturate(LenDsSquare * AttenFactor);
Ang = lerp(Ang, BestAng.x, FallOff);
BestAng.y = (Ang > BestAng.y) ? Ang : lerp(Ang, BestAng.y, Thickness);;
}
BestAng.x = acosFast(clamp(BestAng.x, -1.0, 1.0));
BestAng.y = acosFast(clamp(BestAng.y, -1.0, 1.0));
return BestAng;
}
float ComputeInnerIntegral(float2 UV, float2 Angles, float2 ScreenDir, float3 ViewDir, float3 ViewSpaceNormal, float SceneDepth)
{
// Given the angles found in the search plane we need to project the View Space Normal onto the plane defined by the search axis and the View Direction and perform the inner integrate
float3 PlaneNormal = normalize(cross(float3(ScreenDir.xy, 0), ViewDir));
float3 Perp = cross(ViewDir, PlaneNormal);
float3 ProjNormal = ViewSpaceNormal - PlaneNormal * dot(ViewSpaceNormal, PlaneNormal);
float LenProjNormal = length(ProjNormal) + 0.000001f;
float RecipMag = 1.0f / (LenProjNormal);
float CosAng = dot(ProjNormal, Perp) * RecipMag;
float Gamma = acosFast(CosAng) - PI_HALF;
float CosGamma = dot(ProjNormal, ViewDir) * RecipMag;
float SinGamma = CosAng * -2.0f;
// clamp to normal hemisphere
Angles.x = Gamma + max(-Angles.x - Gamma, -(PI_HALF));
Angles.y = Gamma + min(Angles.y - Gamma, (PI_HALF));
float AO = ((LenProjNormal) * 0.25 *
((Angles.x * SinGamma + CosGamma - cos((2.0 * Angles.x) - Gamma)) +
(Angles.y * SinGamma + CosGamma - cos((2.0 * Angles.y) - Gamma))));
return AO;
}
float MutiBounce(float AO,float3 albedo)
{
float3 a = 2.0404 * albedo - 0.3324;
float3 b = -4.7951 * albedo + 0.6417;
float3 c = 2.7552 * albedo + 0.6903;
return max(AO, ((AO * a + b) * AO + c) * AO);
}
void MainPS(
in float2 UV: TEXCOORD0,
out float4 OutColor : SV_Target0
)
{
int2 iPos = int2(UV / Inv_View_Extent);
float2 TexUV = UV;
float DeviceZ = Texture2DSample(SceneTexturesStruct.SceneDepthTexture, SceneTexturesStruct_SceneDepthTextureSampler, UV).r;
float SceneDepth = ConvertFromDeviceZ(DeviceZ);
float3 ViewSpacePos = ScreenToViewPos(UV, SceneDepth);
float3 WorldNormal = Texture2DSample(SceneTexturesStruct.GBufferATexture, SceneTexturesStruct_GBufferATextureSampler, UV).xyz;
float3 ViewSpaceNormal = normalize(mul(WorldNormal, (float3x3) View.TranslatedWorldToView));
float3 ViewDir = normalize(-ViewSpacePos.xyz);
uint NumAngles = (uint) c_NumAngles;
float SinDeltaAngle = sin(PI / c_NumAngles);
float CosDeltaAngle = cos(PI / c_NumAngles);
float3 RandomAndOffset = GetRandomVector(iPos);
float2 RandomVec = RandomAndOffset.xy;
float Offset = RandomAndOffset.z;
float Sum = 0.0;
float2 ScreenDir = float2(RandomVec.x, RandomVec.y);
for (uint Angle = 0; Angle < NumAngles;Angle++)
{
float2 Angles = SearchForLargestAngleDual(GTAO_NUMTAPS, TexUV, ScreenDir, ViewSpacePos, ViewDir);
Sum += ComputeInnerIntegral(TexUV, Angles, ScreenDir, ViewDir, ViewSpaceNormal, SceneDepth);
// Rotate for the next angle
float2 TempScreenDir = ScreenDir.xy;
ScreenDir.x = (TempScreenDir.x * CosDeltaAngle) + (TempScreenDir.y * -SinDeltaAngle);
ScreenDir.y = (TempScreenDir.x * SinDeltaAngle) + (TempScreenDir.y * CosDeltaAngle);
}
float AO = Sum;
AO = AO / ((float) NumAngles);
AO *= 2.0 / PI;
float3 baseColor = Texture2DSample(SceneTexturesStruct.GBufferCTexture, SceneTexturesStruct.PointClampSampler, UV).rgb;
AO = MutiBounce(AO, baseColor);
AO = saturate(AO);
OutColor = float4(AO, AO, AO, 1.0);
}
GTAO.cpp
void FDeferredShadingSceneRenderer::RenderGTAO(FRDGBuilder& GraphBuilder, FRDGTextureRef SceneColor, TRDGUniformBufferRef<FSceneTextureUniformParameters> SceneTexturesWithDepth)
{
for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++)
{
FViewInfo& View = Views[ViewIndex];
const FVector2D InverseViewExtent(
1.0f / static_cast<float>(View.ViewRect.Max.X - View.ViewRect.Min.X),
1.0f / static_cast<float>(View.ViewRect.Max.Y - View.ViewRect.Min.Y));
RDG_EVENT_SCOPE(GraphBuilder, "SimpleUseRenderGraph(ViewId=%d)", ViewIndex);
const FScreenPassTextureViewport GTAOTextureViewport(SceneColor);
FRDGTextureDesc OutputDesc = SceneColor->Desc;
OutputDesc.Reset();
OutputDesc.Extent = GTAOTextureViewport.Extent;
OutputDesc.Flags |= TexCreate_None;
OutputDesc.ClearValue = FClearValueBinding(FLinearColor::Transparent);
const FScreenPassRenderTarget Output(GraphBuilder.CreateTexture(OutputDesc, TEXT("BloomBlurTex")), GTAOTextureViewport.Rect, ERenderTargetLoadAction::ENoAction);
TShaderMapRef<FGTAOShaderPS> PixelShader(View.ShaderMap);
TShaderMapRef<FGTAOShaderVS> VertexShader(View.ShaderMap);
FGTAOParameters* PassParameters = GraphBuilder.AllocParameters<FGTAOParameters>();
PassParameters->SceneTextures = SceneTexturesWithDepth;
PassParameters->RenderTargets[0] = Output.GetRenderTargetBinding();
uint32 TemporalFrame = 0;
uint32 Frame = 0;
const FSceneViewState* ViewState = static_cast<const FSceneViewState*>(View.State);
if (ViewState)
{
TemporalFrame = ViewState->GetCurrentUnclampedTemporalAASampleIndex();
Frame = ViewState->GetFrameIndex();
}
const float Rots[6] = { 60.0f, 300.0f, 180.0f, 240.0f, 120.0f, 0.0f };
const float Offsets[4] = { 0.1f, 0.6f, 0.35f, 0.85f };
float TemporalAngle = Rots[TemporalFrame % 6] * (PI / 360.0f);
float SinAngle, CosAngle;
FMath::SinCos(&SinAngle, &CosAngle, TemporalAngle);
FMyGTAORenderingParameters Params;
Params.GTAOParams[0] = FVector4(CosAngle, SinAngle, Offsets[(TemporalFrame / 6) % 4] * 0.25, Offsets[TemporalFrame % 4]);
Params.GTAOParams[1] = FVector4(0, 0, 1, 1);
Params.Inv_View_Extent = InverseViewExtent;
GraphBuilder.AddPass(
RDG_EVENT_NAME("SimpleTest"),
PassParameters,
ERDGPassFlags::Raster,
[&View, Params,PixelShader, VertexShader](FRHICommandList& RHICmdList)
{
RenderFullScreenQuadGTAO(RHICmdList, View.ShaderMap, PixelShader, VertexShader, Params, View.ViewRect);
});
//Pass2 模糊
FMyBloomBlurInputs BlurInputs;
BlurInputs.Filter = Output;
BlurInputs.BlurRadi = 3;
BlurInputs.Tint = FLinearColor(1.0, 1.0, 1.0, 1.0);
FScreenPassTexture Res = AddMyBloomBlurMultiPass(GraphBuilder, View, BlurInputs);
//Pass3 显示最终结果
FRHISamplerState* SamplerState = TStaticSamplerState<SF_Bilinear, AM_Border, AM_Border, AM_Clamp>::GetRHI();
FOutGTAOPS::FParameters* PassResParameters = GraphBuilder.AllocParameters<FOutGTAOPS::FParameters>();
PassResParameters->SceneTextures = SceneTexturesWithDepth;
PassResParameters->AOInputSampler = SamplerState;
PassResParameters->AOInputTexture = Res.Texture;
PassResParameters->RenderTargets[0] = FRenderTargetBinding(SceneColor, ERenderTargetLoadAction::ENoAction);
TShaderMapRef<FOutGTAOPS> PixelOutShader(View.ShaderMap);
TShaderMapRef<FOutGTAOVS> VertexOutShader(View.ShaderMap);
AddDrawScreenPass(
GraphBuilder,
RDG_EVENT_NAME("Final GTAO", TEXT("Final GTAO My")),
View,
FScreenPassTextureViewport(Res),
FScreenPassTextureViewport(SceneColor),
FScreenPassPipelineState(VertexOutShader, PixelOutShader, TStaticBlendState<>::GetRHI(), TStaticDepthStencilState<false, CF_Always>::GetRHI()),
PassResParameters,
[PixelOutShader, PassResParameters](FRHICommandListImmediate& RHICmdList)
{
SetShaderParameters(RHICmdList, PixelOutShader, PixelOutShader.GetPixelShader(), *PassResParameters);
});
}
}