Files
UnrealEngine/Engine/Shaders/Private/RayTracing/RayTracingAmbientOcclusionRGS.usf
2025-05-18 13:04:45 +08:00

240 lines
7.4 KiB
HLSL

// Copyright Epic Games, Inc. All Rights Reserved.
#include "../Common.ush"
#include "../Montecarlo.ush"
#include "../DeferredShadingCommon.ush"
#include "../SceneTextureParameters.ush"
#include "../PathTracing/Utilities/PathTracingRandomSequence.ush"
#include "../SphericalGaussian.ush"
#include "../Substrate/Substrate.ush"
#include "RayTracingCommon.ush"
#include "RayTracingDeferredShadingCommon.ush"
#define CONFIG_SHOOT_WITH_GEOMETRIC_NORMAL 1
uint SamplesPerPixel;
float MaxRayDistance;
float Intensity;
float MaxNormalBias;
RaytracingAccelerationStructure TLAS;
RWTexture2D<float> RWAmbientOcclusionMaskUAV;
RWTexture2D<float> RWAmbientOcclusionHitDistanceUAV;
/** Returns the radius of a pixel in world space. */
float ComputeWorldBluringRadiusCausedByPixelSize(float WorldDepth)
{
// Should be multiplied 0.5* for the diameter to radius, and by 2.0 because GetTanHalfFieldOfView() cover only half of the pixels.
return GetDepthPixelRadiusForProjectionType(WorldDepth);
}
void GenerateCosineNormalRay(
uint2 PixelCoord,
float3 TranslatedWorldPosition,
float3 WorldNormal,
float3 GeometricNormal,
inout RandomSequence RandSequence,
out float3 RayOrigin,
out float3 RayDirection,
out float RayTMin,
out float RayTMax,
out float RayPDF
)
{
// Draw random variable
float2 BufferSize = View.BufferSizeAndInvSize.xy;
float2 RandSample = RandomSequence_GenerateSample2D(RandSequence);
// Perform cosine-hemispherical sampling and convert to world-space
float4 Direction_Tangent;
#if 1
{
Direction_Tangent = CosineSampleHemisphereConcentric(RandSample);
Direction_Tangent.w = Direction_Tangent.z;
}
#else
{
Direction_Tangent = CosineSampleHemisphere(RandSample);
}
#endif
#if CONFIG_SHOOT_WITH_GEOMETRIC_NORMAL
float3 Direction_World = TangentToWorld(Direction_Tangent.xyz, GeometricNormal);
#else
float3 Direction_World = TangentToWorld(Direction_Tangent.xyz, WorldNormal);
#endif
RayOrigin = TranslatedWorldPosition;
RayDirection = Direction_World;
RayTMin = 0.0;
RayTMax = MaxRayDistance;
RayPDF = Direction_Tangent.w;
}
float3 SampleNeightborPosition(uint2 PixelCoord, int2 Offset)
{
uint2 ClampedPixelCoord = clamp(uint2(PixelCoord + Offset), uint2(View.ViewRectMin.xy), uint2(View.ViewRectMin.xy + View.ViewSizeAndInvSize.xy - 1));
float DeviceZ = SceneDepthTexture.Load(int3(ClampedPixelCoord, 0)).r;
return ReconstructTranslatedWorldPositionFromDeviceZ(PixelCoord + Offset, DeviceZ);
}
RAY_TRACING_ENTRY_RAYGEN(AmbientOcclusionRGS)
{
const uint2 PixelCoord = DispatchRaysIndex().xy + View.ViewRectMin.xy;
#if SUBTRATE_GBUFFER_FORMAT==1
const FSubstrateTopLayerData TopLayerData = SubstrateUnpackTopLayerData(Substrate.TopLayerTexture.Load(uint3(PixelCoord, 0)));
const bool bIsValid = IsSubstrateMaterial(TopLayerData);
const bool bReconstructGeometryNormal = true; // SUBSTRATE_TODO: legacy avoids this on foliage, how do we do that?
const float3 WorldNormal = TopLayerData.WorldNormal;
#else
const FGBufferData GBufferData = GetGBufferDataFromSceneTexturesLoad(PixelCoord);
const bool bIsValid = GBufferData.ShadingModelID != SHADINGMODELID_UNLIT;
const bool bReconstructGeometryNormal = GBufferData.ShadingModelID != SHADINGMODELID_TWOSIDED_FOLIAGE;
const float3 WorldNormal = GBufferData.WorldNormal;
#endif
const float DeviceZ = SceneDepthTexture.Load(int3(PixelCoord, 0)).r;
const float SceneDepth = ConvertFromDeviceZ(DeviceZ);
float3 TranslatedWorldPosition;
float3 CameraDirection;
ReconstructTranslatedWorldPositionAndCameraDirectionFromDeviceZ(PixelCoord, DeviceZ, TranslatedWorldPosition, CameraDirection);
float3 GeometricNormal;
float ShadingDotGeometric;
if (bReconstructGeometryNormal && CONFIG_SHOOT_WITH_GEOMETRIC_NORMAL)
{
float WorldPixelRadius = ComputeWorldBluringRadiusCausedByPixelSize(SceneDepth);
float3 E = SampleNeightborPosition(PixelCoord, int2( 1, 0)) - TranslatedWorldPosition;
float3 W = SampleNeightborPosition(PixelCoord, int2(-1, 0)) - TranslatedWorldPosition;
float3 N = SampleNeightborPosition(PixelCoord, int2( 0, -1)) - TranslatedWorldPosition;
float3 S = SampleNeightborPosition(PixelCoord, int2( 0, 1)) - TranslatedWorldPosition;
float DH = dot(E, View.ViewForward) + dot(W, View.ViewForward);
float DV = dot(N, View.ViewForward) + dot(S, View.ViewForward);
float3 H = (length2(E) < length2(W)) ? E : W;
float3 V = (length2(S) < length2(N)) ? S : N;
GeometricNormal = normalize(cross(H, V));
FLATTEN
if (dot(GeometricNormal, CameraDirection) > 0)
{
GeometricNormal = -GeometricNormal;
}
FSphericalGaussian CosineSG = ClampedCosine_ToSphericalGaussian(float3(0, 0, 1));
FSphericalGaussian ShadingSG = ClampedCosine_ToSphericalGaussian(WorldNormal);
FSphericalGaussian GeometricSG = ClampedCosine_ToSphericalGaussian(GeometricNormal);
ShadingDotGeometric = saturate(Dot(ShadingSG, GeometricSG) * rcp(Dot(CosineSG, CosineSG)));
// Detect the grass to fail grassfully and avoid 1pixel outlining.
if (any(float2(DH, DV) < -5 * WorldPixelRadius))
{
GeometricNormal = WorldNormal;
ShadingDotGeometric = 1.0;
}
}
else
{
GeometricNormal = WorldNormal;
ShadingDotGeometric = 1.0;
}
float RayCount = 0.0;
float Visibility = 0.0;
float ClosestRayHitDistance = 10000.0;
// Declaring intensity as a local variable is a workaround for a shader compiler bug with NV drivers 430.39 and 430.64.
const float IntensityLocal = Intensity;
uint SamplesPerPixelLocal = SamplesPerPixel;
// Mask out depth values beyond far plane
bool IsFiniteDepth = DeviceZ > 0.0;
bool bTraceRay = (IsFiniteDepth && bIsValid);
if (!bTraceRay)
{
Visibility = 1.0;
RayCount = SamplesPerPixel;
SamplesPerPixelLocal = 0.0;
}
uint TimeSeed = View.StateFrameIndex;
for (uint SampleIndex = 0; SampleIndex < SamplesPerPixelLocal; ++SampleIndex)
{
FRayDesc Ray;
float RayPDF;
RandomSequence RandSequence;
RandomSequence_Initialize(RandSequence, PixelCoord, SampleIndex, TimeSeed, SamplesPerPixelLocal);
GenerateCosineNormalRay(PixelCoord, TranslatedWorldPosition, WorldNormal, GeometricNormal, RandSequence, Ray.Origin, Ray.Direction, Ray.TMin, Ray.TMax, RayPDF);
ApplyCameraRelativeDepthBias(Ray, PixelCoord, DeviceZ, WorldNormal, MaxNormalBias);
//if (dot(WorldNormal, Ray.Direction) <= 0.0) // TODO(Denoiser): does it needs to be handled by the denoiser?
// continue;
uint RayFlags = 0;
const uint InstanceInclusionMask = RAY_TRACING_MASK_SHADOW | RAY_TRACING_MASK_THIN_SHADOW;
#if !ENABLE_MATERIALS
RayFlags |= RAY_FLAG_FORCE_OPAQUE;
#endif
#if !ENABLE_TWO_SIDED_GEOMETRY
RayFlags |= RAY_FLAG_CULL_BACK_FACING_TRIANGLES;
#endif
#if CONFIG_SHOOT_WITH_GEOMETRIC_NORMAL
float RayWeight = max(dot(WorldNormal, Ray.Direction), 0.05) / max(RayPDF, 0.05);
#else
float RayWeight = 1.0;
#endif
FMinimalPayload MinimalPayload = TraceVisibilityRay(
TLAS,
RayFlags,
InstanceInclusionMask,
Ray);
RayCount += RayWeight;
Visibility += RayWeight * (MinimalPayload.IsMiss() ? 1.0 : (1.0 - IntensityLocal));
if (MinimalPayload.IsHit())
{
ClosestRayHitDistance = min(ClosestRayHitDistance, MinimalPayload.HitT);
}
}
// Output.
{
float2 RawOutput = 1;
if (SamplesPerPixelLocal == 0)
{
RawOutput.x = 1;
RawOutput.y = -2;
}
else if (RayCount == 0)
{
RawOutput.x = 1;
RawOutput.y = -1;
}
else
{
RawOutput.x = ShadingDotGeometric * (Visibility / RayCount);
RawOutput.y = ClosestRayHitDistance;
}
RWAmbientOcclusionMaskUAV[PixelCoord] = RawOutput.x;
RWAmbientOcclusionHitDistanceUAV[PixelCoord] = RawOutput.y;
}
}