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

637 lines
17 KiB
HLSL

// Copyright Epic Games, Inc. All Rights Reserved.
#include "HeterogeneousVolumesSparseVoxelUtils.ush"
#include "../Common.ush"
#include "../RayTracing/RayTracingCommon.ush"
#define SUPPORT_CONTACT_SHADOWS 0
#define DYNAMICALLY_SHADOWED 1
#define TREAT_MAXDEPTH_UNSHADOWED 1
#define SHADOW_QUALITY 2
#define NO_TRANSLUCENCY_AVAILABLE
#if VIRTUAL_SHADOW_MAP
#include "../VirtualShadowMaps/VirtualShadowMapProjectionCommon.ush"
#endif
#include "HeterogeneousVolumesSparseVoxelUniformBufferUtils.ush"
#include "HeterogeneousVolumesTracingUtils.ush"
#include "HeterogeneousVolumesLightingUtils.ush"
#ifndef DIM_APPLY_SHADOW_TRANSMITTANCE
#define DIM_APPLY_SHADOW_TRANSMITTANCE 0
#endif // DIM_APPLY_SHADOW_TRANSMITTANCE
#define VOXEL_COUNT_MAX 6
#define SHADOW_BIT 0x80000000u
struct FSparseVoxelPayload
{
float HitT[2];
uint VoxelID;
uint Data;
float3 Transmittance;
void SetMiss() { HitT[0] = -1; }
bool IsMiss() { return HitT[0] < 0; }
bool IsHit() { return !IsMiss(); }
void SetShadowRay()
{
Data = (Data & ~SHADOW_BIT) | SHADOW_BIT;
}
bool IsShadowRay()
{
return (Data & SHADOW_BIT) != 0;
}
void SetTransmittance(float3 Transm)
{
Transmittance = Transm;
}
float3 GetTransmittance()
{
return Transmittance;
}
};
CHECK_RT_PAYLOAD_SIZE(FSparseVoxelPayload)
FSparseVoxelPayload CreateSparseVoxelPayload()
{
FSparseVoxelPayload Payload = (FSparseVoxelPayload)0;
Payload.SetMiss();
Payload.SetTransmittance(1.0);
return Payload;
}
uint3 GetVolumeResolutionAtMip(uint MipLevel)
{
uint3 VolumeResolution = GetVolumeResolution();
return uint3(
VolumeResolution.x >> MipLevel,
VolumeResolution.y >> MipLevel,
VolumeResolution.z >> MipLevel
);
}
FVoxelData GetVoxelData(uint PrimitiveIndex, uint InstanceID)
{
FVoxelDataPacked VoxelDataPacked = GetVoxelDataPacked(PrimitiveIndex);
return DecodeVoxelData(VoxelDataPacked, GetVolumeResolution());
}
float3 GetObjectBoundsMin()
{
float3 ObjectBoundsMin = GetLocalBoundsOrigin() - GetLocalBoundsExtent();
return ObjectBoundsMin;
}
float3 GetObjectBoundsMax()
{
float3 ObjectBoundsMax = GetLocalBoundsOrigin() + GetLocalBoundsExtent();
return ObjectBoundsMax;
}
struct FSparseVoxelIntersectionAttributes
{
float2 HitT;
};
FSparseVoxelIntersectionAttributes CreateVolumeIntersectionAttributes(float2 HitT)
{
FSparseVoxelIntersectionAttributes Attr = (FSparseVoxelIntersectionAttributes)0;
Attr.HitT = HitT;
return Attr;
}
RAY_TRACING_ENTRY_CLOSEST_HIT(SparseVoxelsClosestHitShader,
FSparseVoxelPayload, Payload,
FSparseVoxelIntersectionAttributes, Attributes)
{
FVoxelData VoxelData = GetVoxelData(PrimitiveIndex(), InstanceID());
Payload.HitT[0] = Attributes.HitT.x;
Payload.HitT[1] = Attributes.HitT.y;
Payload.VoxelID = PrimitiveIndex();
}
RAY_TRACING_ENTRY_ANY_HIT(SparseVoxelsAnyHitShader,
FSparseVoxelPayload, Payload,
FSparseVoxelIntersectionAttributes, Attributes)
{
FVoxelData VoxelData = GetVoxelData(PrimitiveIndex(), InstanceID());
// Compute transmittance instead..
Payload.HitT[0] = Attributes.HitT.x;
Payload.HitT[1] = Attributes.HitT.y;
Payload.VoxelID = PrimitiveIndex();
if (Payload.IsShadowRay())
{
float3 Transmittance = Payload.GetTransmittance();
float StepSize = GetShadowStepSize();
float HitSpan = Payload.HitT[1] - Payload.HitT[0];
int StepCount = max(HitSpan / StepSize, 1);
StepSize = HitSpan / StepCount;
float MipLevel = 0.0;
for (int StepIndex = 0; StepIndex < StepCount; ++StepIndex)
{
float MarchT = StepIndex * StepSize;
float3 ObjectPosition = ObjectRayOrigin() + ObjectRayDirection() * (Payload.HitT[0] + MarchT);
#if 1
float3 UVW = (ObjectPosition - GetObjectBoundsMin()) / (GetObjectBoundsMax() - GetObjectBoundsMin());
float3 Extinction = Texture3DSampleLevel(SparseVoxelUniformBuffer.ExtinctionTexture, SparseVoxelUniformBuffer.TextureSampler, UVW, MipLevel).rgb;
#else
float3 WorldPosition = mul(float4(ObjectPosition, 1.0), GetLocalToWorld()).xyz;
FVolumeSampleContext SampleContext = CreateVolumeSampleContext(ObjectPosition, WorldPosition, MipLevel);
float3 Extinction = SampleExtinction(SampleContext);
#endif
Transmittance *= exp(-Extinction * StepSize);
}
Payload.SetTransmittance(Transmittance);
// Early ray termination
float Epsilon = 1.0e-3;
if (all(Transmittance < Epsilon))
{
AcceptHitAndEndSearch();
}
else
{
IgnoreHit();
}
}
}
RAY_TRACING_ENTRY_INTERSECTION(SparseVoxelsIntersectionShader)
{
FVoxelData VoxelData = GetVoxelData(PrimitiveIndex(), InstanceID());
FBox Box = GetVoxelBoundingBox(VoxelData, GetVolumeResolution(), GetObjectBoundsMin(), GetObjectBoundsMax());
// Intersect ray against box
float2 HitT = IntersectAABB(ObjectRayOrigin(), ObjectRayDirection(), RayTMin(), RayTCurrent(), Box.Min, Box.Max);
if (HitT.y > HitT.x)
{
uint HitKind = 0;
FSparseVoxelIntersectionAttributes IntersectionAttributes = CreateVolumeIntersectionAttributes(HitT);
ReportHit(HitT.x, HitKind, IntersectionAttributes);
}
}
RAY_TRACING_ENTRY_MISS(SparseVoxelsMissShader,
FSparseVoxelPayload, Payload)
{
Payload.SetMiss();
}
// These variables are only used by the ray gen shaders below
// Investigate why MipLevel isn't dead stripped when building the shaders above (see UE-218402).
#if RAYGENSHADER
// Scene
RaytracingAccelerationStructure TLAS;
// Ray data
int MaxStepCount;
int bJitter;
// Volume sampling data
int MipLevel;
// Output
RWTexture2D<float4> RWLightingTexture;
FRayDesc CreateVolumeRay(
float3 Origin,
float3 Direction,
float TMin,
float TMax
)
{
FRayDesc Ray;
Ray.Origin = Origin;
Ray.Direction = Direction;
Ray.TMin = TMin;
Ray.TMax = TMax;
return Ray;
}
void TraceVolumeRay(
FRayDesc Ray,
inout FSparseVoxelPayload Payload
)
{
uint RayFlags = 0;
uint InstanceInclusionMask = 0xFF;
uint RayContributionToHitGroupIndex = 0;
uint MultiplierForGeometryContributionToHitGroupIndex = 0;
uint MissShaderIndex = 0;
TraceRay(
TLAS,
RayFlags,
InstanceInclusionMask,
RayContributionToHitGroupIndex,
MultiplierForGeometryContributionToHitGroupIndex,
MissShaderIndex,
Ray.GetNativeDesc(),
Payload
);
}
void TraceVolumeShadowRay(
FRayDesc Ray,
inout FSparseVoxelPayload Payload
)
{
Payload.SetShadowRay();
TraceVolumeRay(Ray, Payload);
}
// h = [0, 1], s = [0 ,1], v = [0, 1]
// h: hue,
// s: saturate
// v: value
float3 hsv2rgb(float h, float s, float v)
{
h = 360.0 * h;
float C = v * s;
float X = C * (1 - abs((h / 60) % 2 - 1));
float m = v - C;
float3 RGB = 0;
if (h >= 0 && h < 60)
{
RGB.r = C;
RGB.g = X;
}
else if (h >= 60 && h < 120)
{
RGB.r = X;
RGB.g = C;
}
else if (h >= 120 && h < 180)
{
RGB.g = C;
RGB.b = X;
}
else if (h >= 180 && h < 240)
{
RGB.g = X;
RGB.b = C;
}
else if (h >= 240 && h < 300)
{
RGB.r = X;
RGB.b = C;
}
else if (h >= 300 && h < 360)
{
RGB.r = C;
RGB.b = X;
}
RGB += m;
return RGB;
}
float3 GetVoxelDebugColor(uint VoxelID)
{
float Hue = (VoxelID % GetVolumeResolutionAtMip(MipLevel).x) / float(GetVolumeResolutionAtMip(MipLevel).x);
return hsv2rgb(Hue, 1.0, 1.0);
}
float3 GetVoxelDebugColor(uint3 VoxelIndex)
{
//float Hue = RandFast(VoxelIndex.yz);
float Hue = float(Rand3DPCG32(VoxelIndex).x) / 0xFFFFFFFF;
return hsv2rgb(Hue, 1.0, 1.0);
}
RAY_TRACING_ENTRY_RAYGEN(SparseVoxelsRayGenerationShader1)
{
uint2 PixelCoord = DispatchRaysIndex().xy;
if (any(PixelCoord.xy >= View.ViewSizeAndInvSize.xy))
{
return;
}
PixelCoord += View.ViewRectMin.xy;
// Extract depth
float DeviceZ = SceneDepthTexture.Load(int3(PixelCoord, 0)).r;
#if HAS_INVERTED_Z_BUFFER
DeviceZ = max(0.000000000001, DeviceZ);
#endif // HAS_INVERTED_Z_BUFFER
// Clip trace distance
float SceneDepth = min(ConvertFromDeviceZ(DeviceZ), GetMaxTraceDistance());
DeviceZ = ConvertToDeviceZ(SceneDepth);
float Jitter = bJitter ? InterleavedGradientNoise(PixelCoord, View.StateFrameIndexMod8) : 0.0;
// Intersect ray with bounding volume
// TODO: LWC integration..
float3 WorldRayOrigin = DFHackToFloat(DFFastSubtract(GetTranslatedWorldCameraPosFromView(PixelCoord + 0.5), PrimaryView.PreViewTranslation));
float3 WorldRayEnd = DFHackToFloat(SvPositionToWorld(float4(PixelCoord + 0.5, DeviceZ, 1)));
float3 WorldRayDirection = WorldRayEnd - WorldRayOrigin;
float WorldRayLength = length(WorldRayDirection); // SceneDepth
float3 LocalRayOrigin = mul(float4(WorldRayOrigin, 1.0), GetWorldToLocal()).xyz;
float3 LocalRayEnd = mul(float4(WorldRayEnd, 1.0), GetWorldToLocal()).xyz;
float3 LocalRayDirection = LocalRayEnd - LocalRayOrigin;
float LocalRayLength = length(LocalRayDirection);
float3 LocalBoundsMin = GetLocalBoundsOrigin() - GetLocalBoundsExtent();
float3 LocalBoundsMax = GetLocalBoundsOrigin() + GetLocalBoundsExtent();
float2 HitT = LineBoxIntersect(LocalRayOrigin, LocalRayEnd, LocalBoundsMin, LocalBoundsMax);
float HitSpan = HitT.y - HitT.x;
if (HitSpan > 0.0)
{
float3 WorldRayStart = WorldRayOrigin + WorldRayDirection * HitT.x;
float3 LocalRayStart = LocalRayOrigin + LocalRayDirection * HitT.x;
// Convert normalized t-value intersection parameters to world-space units
WorldRayDirection *= rcp(WorldRayLength);
LocalRayDirection *= rcp(LocalRayLength);
LocalRayLength *= HitSpan;
// Jitter starting position to mitigate moire
LocalRayStart += LocalRayDirection * Jitter;
// Intersect against TLAS - HitT.x is real close to first intersection shader return value...
FRayDesc Ray = CreateVolumeRay(WorldRayOrigin + WorldRayDirection * Jitter, WorldRayDirection, WorldRayLength * HitT.x, WorldRayLength * HitT.y);
FSparseVoxelPayload Payload = CreateSparseVoxelPayload();
TraceVolumeShadowRay(Ray, Payload);
// Output..
float3 Transmittance = Payload.GetTransmittance();
float3 Opacity = 1.0 - Transmittance;
RWLightingTexture[PixelCoord] = float4(Transmittance, Luminance(Opacity));
}
}
// Lighting
int bApplyEmissionAndTransmittance;
int bApplyDirectLighting;
int bApplyShadowTransmittance;
int LightType;
float VolumetricScatteringIntensity;
// Shadowing
uint VirtualShadowMapId;
float3 ComputeTransmittanceHardwareRayTracing(
float3 Origin,
float3 Direction,
float TMin,
float TMax
)
{
// TODO: Apply jitter?
FRayDesc ShadowRay = CreateVolumeRay(Origin, Direction, TMin, TMax);
FSparseVoxelPayload Payload = CreateSparseVoxelPayload();
TraceVolumeShadowRay(ShadowRay, Payload);
return Payload.GetTransmittance();
}
float EvalAmbientOcclusion(float3 WorldPosition)
{
return 1.0;
}
// Cinematic feature options
bool UseInscatteringVolume()
{
return true;
}
#define INDIRECT_LIGHTING_MODE_OFF 0
#define INDIRECT_LIGHTING_MODE_LIGHTING_CACHE 1
#define INDIRECT_LIGHTING_MODE_SINGLE_SCATTERING 2
int GetIndirectLightingMode()
{
return INDIRECT_LIGHTING_MODE_OFF;
}
bool IsOfflineRender()
{
return false;
}
#define FOG_INSCATTERING_MODE_OFF 0
#define FOG_INSCATTERING_MODE_REFERENCE 1
#define FOG_INSCATTERING_MODE_LINEAR_APPROX 2
int GetFogInscatteringMode()
{
return FOG_INSCATTERING_MODE_LINEAR_APPROX;
}
#define HARDWARE_RAY_TRACING 1
#include "HeterogeneousVolumesRayMarchingUtils.ush"
uint3 DispatchRaysToVoxelIndex(uint2 DispatchRaysIndex)
{
uint3 VolumeResolution = GetLightingCacheResolution();
uint3 VoxelIndex = uint3(DispatchRaysIndex.x,
DispatchRaysIndex.y % LightingCacheResolution.y,
DispatchRaysIndex.y / LightingCacheResolution.y);
return VoxelIndex;
}
RWTexture3D<float3> RWLightingCacheTexture;
RAY_TRACING_ENTRY_RAYGEN(RenderLightingCacheWithPreshadingRGS)
{
uint3 VoxelIndex = DispatchRaysToVoxelIndex(DispatchRaysIndex().xy);
if (any(VoxelIndex >= LightingCacheResolution))
{
return;
}
float3 UVW = (VoxelIndex + 0.5) / float3(LightingCacheResolution);
float3 LocalBoundsMin = GetLocalBoundsOrigin() - GetLocalBoundsExtent();
float3 LocalBoundsMax = GetLocalBoundsOrigin() + GetLocalBoundsExtent();
float3 LocalRayOrigin = UVW * GetLocalBoundsExtent() * 2.0 + LocalBoundsMin;
float3 WorldRayOrigin = mul(float4(LocalRayOrigin, 1.0), GetLocalToWorld()).xyz;
// Existence culling
float MipLevel = 0.0f;
FVolumeSampleContext SampleContext = CreateVolumeSampleContext(LocalRayOrigin, WorldRayOrigin, MipLevel);
float3 Extinction = SampleExtinction(SampleContext);
float Epsilon = 1.0e-7;
if (all(Extinction < Epsilon))
{
#if DIM_LIGHTING_CACHE_MODE == LIGHTING_CACHE_TRANSMITTANCE
RWLightingCacheTexture[VoxelIndex] = 1.0f;
#elif DIM_LIGHTING_CACHE_MODE == LIGHTING_CACHE_INSCATTERING
RWLightingCacheTexture[VoxelIndex] = 0.0f;
#endif // DIM_LIGHTING_CACHE_MODE
return;
}
const FDeferredLightData LightData = InitDeferredLightFromUniforms(LightType, VolumetricScatteringIntensity);
#if DIM_LIGHTING_CACHE_MODE == LIGHTING_CACHE_TRANSMITTANCE
float3 L = LightData.Direction;
float3 ToLight = L * GetMaxShadowTraceDistance();
if (LightType != LIGHT_TYPE_DIRECTIONAL)
{
// Note: this function also permutes ToLight and L
float3 TranslatedWorldPosition = DFFastToTranslatedWorld(WorldRayOrigin, PrimaryView.PreViewTranslation);
GetLocalLightAttenuation(TranslatedWorldPosition, LightData, ToLight, L);
if (LightData.bRectLight)
{
FRect Rect = GetRect(ToLight, LightData);
}
else
{
FCapsuleLight Capsule = GetCapsule(ToLight, LightData);
}
}
float3 Transmittance = ComputeTransmittance(WorldRayOrigin, ToLight, MaxStepCount);
RWLightingCacheTexture[VoxelIndex] = Transmittance;
#elif DIM_LIGHTING_CACHE_MODE == LIGHTING_CACHE_INSCATTERING
float3 Inscattering = ComputeInscattering(WorldRayOrigin, LightData, LightType, MaxStepCount, CalcShadowBias(), bApplyShadowTransmittance);
RWLightingCacheTexture[VoxelIndex] += Inscattering * View.PreExposure;
#endif // DIM_LIGHTING_CACHE_MODE
}
RAY_TRACING_ENTRY_RAYGEN(RenderSingleScatteringWithPreshadingRGS)
{
float3 Radiance = 0.0;
float3 Transmittance = 1.0;
// TODO: Render tiles?
uint2 PixelCoord = DispatchRaysIndex().xy;
if (any(PixelCoord.xy >= View.ViewSizeAndInvSize.xy))
{
return;
}
PixelCoord += View.ViewRectMin.xy;
// Extract depth
float DeviceZ = SceneDepthTexture.Load(int3(PixelCoord, 0)).r;
#if HAS_INVERTED_Z_BUFFER
DeviceZ = max(0.000000000001, DeviceZ);
#endif // HAS_INVERTED_Z_BUFFER
// Clip trace distance
float SceneDepth = min(ConvertFromDeviceZ(DeviceZ), GetMaxTraceDistance());
DeviceZ = ConvertToDeviceZ(SceneDepth);
float Jitter = bJitter ? InterleavedGradientNoise(PixelCoord, View.StateFrameIndexMod8) : 0.0;
// Intersect ray with bounding volume
// TODO: LWC integration..
float3 WorldRayOrigin = DFHackToFloat(DFFastSubtract(GetTranslatedWorldCameraPosFromView(PixelCoord + 0.5), PrimaryView.PreViewTranslation));
float3 WorldRayEnd = DFHackToFloat(SvPositionToWorld(float4(PixelCoord + 0.5, DeviceZ, 1)));
float3 WorldRayDirection = WorldRayEnd - WorldRayOrigin;
float WorldRayLength = length(WorldRayDirection);
WorldRayDirection /= WorldRayLength;
float3 LocalRayOrigin = mul(float4(WorldRayOrigin, 1.0), GetWorldToLocal()).xyz;
float3 LocalRayEnd = mul(float4(WorldRayEnd, 1.0), GetWorldToLocal()).xyz;
float3 LocalRayDirection = LocalRayEnd - LocalRayOrigin;
float LocalRayLength = length(LocalRayDirection);
LocalRayDirection /= LocalRayLength;
float3 LocalBoundsMin = GetLocalBoundsOrigin() - GetLocalBoundsExtent();
float3 LocalBoundsMax = GetLocalBoundsOrigin() + GetLocalBoundsExtent();
// Test bounding volume
float2 BoundingHitT = IntersectAABB(LocalRayOrigin, LocalRayDirection, 0.0, LocalRayLength, LocalBoundsMin, LocalBoundsMax);
bool bIntersectsVolume = (BoundingHitT.y - BoundingHitT.x > 0.0);
if (!bIntersectsVolume)
{
return;
}
// Intersect against TLAS - HitT.x is real close to first intersection shader return value...
FRayDesc Ray = CreateVolumeRay(WorldRayOrigin, WorldRayDirection, BoundingHitT.x, BoundingHitT.y);
FSparseVoxelPayload Payload = CreateSparseVoxelPayload();
TraceVolumeRay(Ray, Payload);
float HitSpan = Payload.HitT[1] - Payload.HitT[0];
while (Payload.IsHit() && Luminance(Transmittance) > 0.001)
{
FRayMarchingContext RayMarchingContext = CreateRayMarchingContext(
// Local-space
LocalRayOrigin,
LocalRayDirection,
Payload.HitT[0],
Payload.HitT[1],
// World-space
WorldRayOrigin,
WorldRayDirection,
// Ray-step attributes
Jitter,
GetStepSize(),
MaxStepCount,
bApplyEmissionAndTransmittance,
bApplyDirectLighting,
DIM_APPLY_SHADOW_TRANSMITTANCE
);
const FDeferredLightData LightData = InitDeferredLightFromUniforms(LightType, VolumetricScatteringIntensity);
uint StepCount = CalcStepCount(RayMarchingContext);
float ScatterHitT = Payload.HitT[1];
RayMarchSingleScattering(
RayMarchingContext,
LightData,
LightType,
StepCount,
ScatterHitT,
Radiance,
Transmittance
);
//#define DEBUG_VISUALIZATION 1
#if DEBUG_VISUALIZATION
FVoxelData VoxelData = GetVoxelData(Payload.VoxelID, 0);
if (View.GeneralPurposeTweak == 0)
{
// TODO: Remap Extinction to Transmittance
float3 Extinction = 0.02 * (1.0 - GetVoxelDebugColor(VoxelData.VoxelIndex));
Transmittance *= exp(-Extinction * HitSpan);
if (!bApplyDirectLighting)
{
float3 Emission = 0.002 * GetVoxelDebugColor(VoxelData.VoxelIndex);
Radiance += Emission * HitSpan * Transmittance;
}
}
#endif // DEBUG_VISUALIZATION
//Ray = CreateVolumeRay(WorldRayOrigin, WorldRayDirection, Payload.HitT[1] + 0.001, WorldRayLength * HitT.y);
Ray = CreateVolumeRay(WorldRayOrigin, WorldRayDirection, Payload.HitT[1] + 0.001, BoundingHitT.y);
Payload = CreateSparseVoxelPayload();
TraceVolumeRay(Ray, Payload);
HitSpan = Payload.HitT[1] - Payload.HitT[0];
}
// Output..
RWLightingTexture[PixelCoord] += float4(Radiance * View.PreExposure, Luminance(Transmittance));
}
#endif // RAYGENSHADER