Files
UnrealEngine/Engine/Shaders/Private/HairStrands/HairStrandsVoxelPageTraversal.ush
2025-05-18 13:04:45 +08:00

497 lines
18 KiB
HLSL

// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
// VOXEL_TRAVERSAL_TYPE needs to be defined for selecting which type of traversal needs to be used
// Definition of traversal types are defined in HairStrandsVoxelPageCommon.ush
#ifndef VOXEL_TRAVERSAL_TYPE
#define VOXEL_TRAVERSAL_TYPE VOXEL_TRAVERSAL_NONE
#endif
#ifndef VOXEL_TRAVERSAL_DEBUG
#define VOXEL_TRAVERSAL_DEBUG 0
#else
#include "../ShaderPrint.ush"
#endif
#ifndef VOXEL_TRAVERSAL_FORCE_MIP_ENABLE
#define VOXEL_TRAVERSAL_FORCE_MIP_ENABLE 0
#endif
#include "HairStrandsVoxelPageCommon.ush"
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#if VOXEL_TRAVERSAL_DEBUG
void AddLine(bool bEnabled, float3 P0, float3 P1)
{
if (!bEnabled)
return;
const float4 DebugColor = float4(1, 1, 0, 1);
AddLineTWS(P0, P1, DebugColor, DebugColor);
}
void AddMacroGroupAABB(bool bEnabled, float3 P0, float3 P1)
{
if (!bEnabled)
return;
AddAABBTWS(P0, P1, float4(1, 0, 1, 1));
}
void AddPageAABB(bool bEnabled, float3 P0, float3 P1, bool bIsPageValid)
{
if (!bEnabled)
return;
const float4 DebugColor = bIsPageValid ? float4(0, 1, 1, 1) : float4(1, 0, 0, 1);
AddAABBTWS(P0, P1, DebugColor);
}
void AddStepAABB(bool bEnabled, float3 P0, float3 P1, float3 Color)
{
if (!bEnabled)
return;
const float4 DebugColor = float4(Color, 1);
AddAABBTWS(P0, P1, DebugColor);
}
void AddAABBVolume(bool bEnabled, float3 P0, float3 P1)
{
if (!bEnabled)
return;
const float4 DebugColor = float4(1,1,0, 1);
AddAABBTWS(P0, P1, DebugColor);
}
#endif
///////////////////////////////////////////////////////////////////////////////////////////////////
// Jitter functions
float3 GetHairVoxelJitter(uint2 PixelCoord, uint Seed, uint JitterMode)
{
Seed = JitterMode > 1 ? 0 : Seed;
return JitterMode > 0 ? float3(
InterleavedGradientNoise(float2(PixelCoord.xy), float(Seed)),
InterleavedGradientNoise(float2(PixelCoord.xy), float(Seed * 117.f)),
InterleavedGradientNoise(float2(PixelCoord.xy), float(Seed * 7901.f))) : float3(0, 0, 0);
}
float4 GetHairVoxelJitter2(uint2 PixelCoord, uint Seed, uint JitterMode)
{
Seed = JitterMode > 1 ? 0 : Seed;
const uint2 Seed0 = Rand3DPCG16(int3(PixelCoord, Seed)).xy;
const uint2 Seed1 = Rand3DPCG16(int3(PixelCoord + 17, Seed)).xy;
return JitterMode > 0 ? float4(Hammersley16(Seed, 8, Seed0), Hammersley16(Seed, 8, Seed1)) : float4(0, 0, 0, 0);
}
// Use define as this function use global resources not always available where this file is included
#define DEFINE_HAIR_BLUE_NOISE_JITTER_FUNCTION float4 GetHairBlueNoiseJitter(uint2 PixelCoord, uint SampleIndex, uint MaxSampleCount, uint TimeIndex)\
{\
int2 Offset1 = int2(R2Sequence(SampleIndex) * BlueNoise.Dimensions.xy);\
int2 Offset2 = int2(R2Sequence(SampleIndex + MaxSampleCount) * BlueNoise.Dimensions.xy);\
float4 RandomSample;\
RandomSample.xy = BlueNoiseVec2(PixelCoord + Offset1, TimeIndex);\
RandomSample.zw = BlueNoiseVec2(PixelCoord + Offset2, TimeIndex);\
return RandomSample;\
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
bool ShouldStopTraversal(const FHairTraversalResult InResult, float InHairCountThreshold, bool InUseOpaqueVisibility=true)
{
// Change this function once we have better result/control over the opaque filtering
return (InUseOpaqueVisibility && InResult.Visibility == 0) || (InHairCountThreshold != 0 && InResult.HairCount > InHairCountThreshold);
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
struct FHairTraversalSettings
{
float DensityScale;
float CountThreshold;
float DistanceThreshold;
float SteppingScale; // Rate at which the raymarching step increase
float3 Random;
float TanConeAngle;
float PixelRadius;
float JitterScale;
float ForcedMip;
bool bUseOpaqueVisibility;
bool bDebugEnabled;
bool bIsPrimaryRay;
bool bCastShadow; // True of the traversal is for casting (opaque) shadow, i.e., not for hair transmission
};
float GetHairTraversalMaxT()
{
return 999999;
}
FHairTraversalSettings InitHairTraversalSettings()
{
FHairTraversalSettings Out;
Out.DensityScale = 1;
Out.CountThreshold = 0;
Out.DistanceThreshold = GetHairTraversalMaxT();
Out.SteppingScale = 1;
Out.Random = 0.5f;
Out.TanConeAngle = 0;
Out.PixelRadius = 0;
Out.JitterScale = 1;
Out.bUseOpaqueVisibility = true;
Out.bDebugEnabled = false;
Out.bIsPrimaryRay = false;
Out.ForcedMip = -1;
Out.bCastShadow = false;
return Out;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Sparse voxel traversal routines
FHairTraversalResult ComputeHairCountVirtualVoxel(
float3 TranslatedWorldPosition0,
float3 TranslatedWorldPosition1,
FVirtualVoxelCommonDesc InCommonDesc,
FVirtualVoxelNodeDesc InNodeDesc,
Buffer<uint> InPageIndexBuffer,
Texture3D<uint> InPageTexture,
FHairTraversalSettings InSettings)
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#if VOXEL_TRAVERSAL_TYPE == VOXEL_TRAVERSAL_NONE
{
Error: VOXEL TRAVERSAL TYPE is undefined
}
#endif
#if VOXEL_TRAVERSAL_TYPE == VOXEL_TRAVERSAL_LINEAR || VOXEL_TRAVERSAL_TYPE == VOXEL_TRAVERSAL_LINEAR_MIPMAP
{
const float MipLevelDistanceScale = 1.0f / InNodeDesc.VoxelWorldSize;
FHairTraversalResult Out = InitHairTraversalResult();
if (!InNodeDesc.bIsValid)
{
return Out;
}
uint3 CurrentPageIndexCoord = 9999;
bool bIsPageValid = false;
uint3 PageCoord = 0;
const float2 HitT = LineBoxIntersect(TranslatedWorldPosition0, TranslatedWorldPosition1, InNodeDesc.TranslatedWorldMinAABB, InNodeDesc.TranslatedWorldMaxAABB);
if (HitT.x < HitT.y)
{
// Count the number of fibers which are within a cylinder defined by the voxel size,
// and the distance between the origin and the extent of the volume
// This assumes that the voxel volume is cubic (i.e. equal dimensions on all sides)
const float3 O = lerp(TranslatedWorldPosition0, TranslatedWorldPosition1, HitT.xxx);
const float3 E = lerp(TranslatedWorldPosition0, TranslatedWorldPosition1, HitT.yyy);
const float OELength = min(length(E - O), InSettings.DistanceThreshold);
const float3 PagesBoundMax = InNodeDesc.TranslatedWorldMinAABB + InNodeDesc.PageIndexResolution * InNodeDesc.VoxelWorldSize * InCommonDesc.PageResolution;
#if VOXEL_TRAVERSAL_DEBUG
FShaderPrintContext Ctx = InitShaderPrintContext(InSettings.bDebugEnabled, uint2(750, 50));
#endif
#if VOXEL_TRAVERSAL_DEBUG
if (InSettings.bDebugEnabled)
{
AddLine(true, O, E);
AddAABBVolume(true, InNodeDesc.TranslatedWorldMinAABB, InNodeDesc.TranslatedWorldMaxAABB);
AddAABBTWS(InNodeDesc.TranslatedWorldMinAABB, PagesBoundMax, ColorPurple);
}
#endif
// Step according to voxel size
const float3 D = normalize(E - O) * InNodeDesc.VoxelWorldSize;
const float MaxStep = float(min(ceil(OELength / InNodeDesc.VoxelWorldSize), 1024.f));
const float DeltaWorld = OELength / float(MaxStep);
float StepScale = 1.0f;
float3 PreviousP = O;
float ClosestHitT = -1;
const float3 RandomStepJitter = InSettings.Random.xyz * 2 - 1; // [-1..1]
for (float StepIt = 0.0f; StepIt < MaxStep; StepIt += StepScale)
{
const float SteppingWorldSize = max(DeltaWorld * StepScale, InSettings.TanConeAngle * StepIt * InNodeDesc.VoxelWorldSize);
const float3 HitP = O + StepIt * D + InSettings.JitterScale * RandomStepJitter * SteppingWorldSize * 0.5f;
const bool bIsWithinPageBound = all(HitP < PagesBoundMax);
#if VOXEL_TRAVERSAL_DEBUG
AddStepAABB(InSettings.bDebugEnabled, PreviousP, HitP, float3(0, 1, 0));
#endif
const uint3 VolumeCoord = clamp((HitP - InNodeDesc.TranslatedWorldMinAABB) / InNodeDesc.VoxelWorldSize, 0, InNodeDesc.VirtualResolution - 1);
const uint3 PageIndexCoord = (VolumeCoord >> InCommonDesc.PageResolutionLog2);
// Update page index only when needed
const bool bHasPageIndexChanged = any(PageIndexCoord != CurrentPageIndexCoord);
if (bHasPageIndexChanged)
{
CurrentPageIndexCoord = PageIndexCoord;
const uint LinearPageIndexCoord = CoordToIndex(PageIndexCoord, InNodeDesc.PageIndexResolution, InNodeDesc.PageIndexOffset);
const uint PageIndex = InPageIndexBuffer.Load(LinearPageIndexCoord);
bIsPageValid = PageIndex != INVALID_VOXEL_PAGE_INDEX;
{
PageCoord = IndexToCoord(PageIndex, InCommonDesc.PageCountResolution);
}
}
#if VOXEL_TRAVERSAL_DEBUG
if (InSettings.bDebugEnabled && bIsPageValid)
{
const float3 PageAABBMin = (PageIndexCoord ) * InCommonDesc.PageResolution * InNodeDesc.VoxelWorldSize + InNodeDesc.TranslatedWorldMinAABB;
const float3 PageAABBMax = (PageIndexCoord+1) * InCommonDesc.PageResolution * InNodeDesc.VoxelWorldSize + InNodeDesc.TranslatedWorldMinAABB;
AddAABBTWS(Ctx, PageAABBMin, PageAABBMax, ColorRed);
}
#endif
if (bIsPageValid && bIsWithinPageBound)
{
const uint3 VoxelPageBase = (PageCoord << InCommonDesc.PageResolutionLog2);
const uint3 VoxelPageOffset = VolumeCoord - (PageIndexCoord << InCommonDesc.PageResolutionLog2);
const uint3 VoxelPageCoord = VoxelPageBase + VoxelPageOffset;
{
float HairCountScale = 1;
float MipLevel = 0.0f;
#if VOXEL_TRAVERSAL_TYPE == VOXEL_TRAVERSAL_LINEAR_MIPMAP
HairCountScale = SteppingWorldSize * MipLevelDistanceScale;
MipLevel = log2(SteppingWorldSize * MipLevelDistanceScale);
#endif
#if VOXEL_TRAVERSAL_FORCE_MIP_ENABLE
MipLevel = InSettings.ForcedMip >= 0 ? InSettings.ForcedMip : MipLevel;
#endif
const FHairTraversalResult StepResult = GetHairVirtualVoxelDensity(VoxelPageCoord, InPageTexture, uint(MipLevel), InSettings.DensityScale * HairCountScale, InSettings.bCastShadow);
Acc(Out, StepResult);
if (InSettings.bIsPrimaryRay && StepResult.HairCount > 0)
{
ClosestHitT = 1;
}
#if VOXEL_TRAVERSAL_DEBUG
if (InSettings.bDebugEnabled)
{
const float3 WorldOffset = PageIndexCoord * InCommonDesc.PageResolution * InNodeDesc.VoxelWorldSize;
const float MipVoxelWorldSize = InNodeDesc.VoxelWorldSize * InCommonDesc.PageResolution / (InCommonDesc.PageResolution >> uint(MipLevel));
const uint3 MinCoord = VoxelPageOffset >> uint(MipLevel);
const uint3 MaxCoord = MinCoord+1;
const float3 FetchMinAABB = InNodeDesc.TranslatedWorldMinAABB + WorldOffset + MinCoord * MipVoxelWorldSize;
const float3 FetchMaxAABB = InNodeDesc.TranslatedWorldMinAABB + WorldOffset + MaxCoord * MipVoxelWorldSize;
AddStepAABB(true, FetchMinAABB, FetchMaxAABB, StepResult.Visibility > 0.5f ? float3(0, 0.5f, 1) : float3(1, 0.5f, 0));
}
#endif
if (ShouldStopTraversal(Out, InSettings.CountThreshold, InSettings.bUseOpaqueVisibility))
{
//Out.HitT = length(HitP - O);
Out.HitT = length(HitP - TranslatedWorldPosition0);
return Out;
}
}
}
#if VOXEL_TRAVERSAL_TYPE == VOXEL_TRAVERSAL_LINEAR_MIPMAP
if (InSettings.bIsPrimaryRay && ClosestHitT < 0)
{
// Start the mipmap traversal only when we have reach the first hit (primaryview)
}
else
{
StepScale = min(float(InCommonDesc.PageResolution), StepScale * InSettings.SteppingScale);
}
#endif
PreviousP = HitP;
}
}
return Out;
}
#endif
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#if VOXEL_TRAVERSAL_TYPE == VOXEL_TRAVERSAL_SPARSE_LINEAR || VOXEL_TRAVERSAL_TYPE == VOXEL_TRAVERSAL_SPARSE_MIPMAP
{
FHairTraversalResult Out = InitHairTraversalResult();
int3 CurrentPageIndexCoord = -1;
bool bIsPageValid = false;
uint3 PageCoord = 0;
#if VOXEL_TRAVERSAL_DEBUG
AddMacroGroupAABB(InSettings.bDebugEnabled, InNodeDesc.TranslatedWorldMinAABB, InNodeDesc.TranslatedWorldMaxAABB);
AddLine(InSettings.bDebugEnabled, TranslatedWorldPosition0, TranslatedWorldPosition1);
#endif
const float2 HitT = LineBoxIntersect(TranslatedWorldPosition0, TranslatedWorldPosition1, InNodeDesc.TranslatedWorldMinAABB, InNodeDesc.TranslatedWorldMaxAABB);
if (HitT.x < HitT.y)
{
// Count the number of fibers which are within a cylinder defined by the voxel size,
// and the distance between the origin and the extent of the volume
// This assumes that the voxel volume is cubic (i.e. equal dimensions on all sides)
const float3 O = TranslatedWorldPosition0; // lerp(TranslatedWorldPosition, IntersectEndPoint, HitT.xxx);
const float3 E = TranslatedWorldPosition1; // lerp(TranslatedWorldPosition, IntersectEndPoint, HitT.yyy);
const float OELength = min(length(E - O), InSettings.DistanceThreshold);
// Init to 1 or -1 depending of the orientation of stepping
const float3 UNormD = TranslatedWorldPosition1 - TranslatedWorldPosition0;
const int3 Step = sign(UNormD);
// Step according to voxel size
const float3 D = normalize(UNormD) * InNodeDesc.VoxelWorldSize;
float t = HitT.x;
// this is slop coeff for each axis: how far we need to move in units of t for each axis
const float PageWorldSize = InCommonDesc.PageResolution * InNodeDesc.VoxelWorldSize;
const float3 tDelta = Step * PageWorldSize / UNormD;
// Init to the starting voxel
int3 PageIndexCoord = -1;
float3 tMax = 0;
{
const float3 HitP = O + HitT.x * UNormD;
const float Epsilon = 0.000001f;
const float3 Coords = clamp(
saturate((HitP - InNodeDesc.TranslatedWorldMinAABB) / (InNodeDesc.TranslatedWorldMaxAABB - InNodeDesc.TranslatedWorldMinAABB)) * InNodeDesc.PageIndexResolution,
0,
InNodeDesc.PageIndexResolution - Epsilon);
const float3 FractCoords = max(Step, 0) - Step * frac(Coords);
tMax = FractCoords * tDelta;
PageIndexCoord = clamp(uint3(Coords), uint3(0, 0, 0), InNodeDesc.PageIndexResolution-1);
}
// Page stepping is the walking quantity (i.e. number of voxel) for fine ray-marching within a valid page
float PageStepping = 1;
const uint LoopCount = 256u;
for (uint LoopIt = 0; LoopIt < LoopCount; ++LoopIt)
{
const bool bIsInside =
PageIndexCoord.x >= 0 &&
PageIndexCoord.y >= 0 &&
PageIndexCoord.z >= 0 &&
PageIndexCoord.x < int(InNodeDesc.PageIndexResolution.x) &&
PageIndexCoord.y < int(InNodeDesc.PageIndexResolution.y) &&
PageIndexCoord.z < int(InNodeDesc.PageIndexResolution.z);
if (!bIsInside)
{
return Out;
}
const uint LinearPageIndexCoord = CoordToIndex(PageIndexCoord, InNodeDesc.PageIndexResolution, InNodeDesc.PageIndexOffset);
const uint PageIndex = InPageIndexBuffer.Load(LinearPageIndexCoord);
const bool bIsPageValid = PageIndex != INVALID_VOXEL_PAGE_INDEX;
#if VOXEL_TRAVERSAL_DEBUG
{
const float3 PageMinAABB = PageIndexCoord * InCommonDesc.PageResolution * InNodeDesc.VoxelWorldSize + InNodeDesc.TranslatedWorldMinAABB;
const float3 PageMaxAABB = (PageIndexCoord+float3(1,1,1)) * InCommonDesc.PageResolution * InNodeDesc.VoxelWorldSize + InNodeDesc.TranslatedWorldMinAABB;
AddPageAABB(InSettings.bDebugEnabled, PageMinAABB, PageMaxAABB, bIsPageValid);
}
#endif
if (bIsPageValid)
{
const uint3 PageCoord = IndexToCoord(PageIndex, InCommonDesc.PageCountResolution);
float3 InnerO = O + t * UNormD;
const uint MaxInnerStep = InCommonDesc.PageResolution * 1.75f; // ~ Diagonal step count
#if VOXEL_TRAVERSAL_TYPE == VOXEL_TRAVERSAL_SPARSE_LINEAR
const uint IntPageStepping = 1;
const uint MipLevel = 0;
#endif
#if VOXEL_TRAVERSAL_TYPE == VOXEL_TRAVERSAL_SPARSE_MIPMAP
const uint IntPageStepping = uint(PageStepping);
const uint MipLevel = log2(IntPageStepping);
#endif
const uint MipPageResolution = InCommonDesc.PageResolution;
const int3 VoxelPageBase = PageCoord * MipPageResolution;
for (uint InnerStepIt = 0; InnerStepIt < MaxInnerStep; InnerStepIt += IntPageStepping)
{
const float3 InnerHitP = InnerO + D * InnerStepIt;
const int3 VolumeCoord = PositionToCoordUnclampled(InnerHitP, InNodeDesc.TranslatedWorldMinAABB, InNodeDesc.TranslatedWorldMaxAABB, InNodeDesc.VirtualResolution);
const int3 VoxelPageOffset = VolumeCoord - PageIndexCoord * InCommonDesc.PageResolution;
#if VOXEL_TRAVERSAL_DEBUG
if (InSettings.bDebugEnabled)
{
if (InnerStepIt==0)
AddStepAABB(InSettings.bDebugEnabled, InnerHitP - D * 2, InnerHitP + D*2, float3(0,1,0));
else
AddStepAABB(InSettings.bDebugEnabled, InnerHitP, InnerHitP + D, float3(1, 0, 1));
}
#endif
const bool bIsInsideInner =
VoxelPageOffset.x >= 0 &&
VoxelPageOffset.y >= 0 &&
VoxelPageOffset.z >= 0 &&
VoxelPageOffset.x < int(MipPageResolution) &&
VoxelPageOffset.y < int(MipPageResolution) &&
VoxelPageOffset.z < int(MipPageResolution);
if (!bIsInsideInner)
{
break;
}
const int3 VoxelPageCoord = VoxelPageBase + VoxelPageOffset;
#if VOXEL_TRAVERSAL_DEBUG
{
const float VoxelExtent = InNodeDesc.VoxelWorldSize * (1 << MipLevel) * 0.5f;
const float3 P = (PageIndexCoord*InCommonDesc.PageResolution + VoxelPageOffset) * InNodeDesc.VoxelWorldSize + InNodeDesc.TranslatedWorldMinAABB;
AddStepAABB(InSettings.bDebugEnabled, P - VoxelExtent, P + VoxelExtent, float3(0, 0.5f, 1));
}
#endif
const FHairTraversalResult StepResult = GetHairVirtualVoxelDensity(VoxelPageCoord, InPageTexture, MipLevel, InSettings.DensityScale, InSetttings.bCastShadow);
Acc(Out, StepResult);
if (ShouldStopTraversal(Out, InSettings.CountThreshold))
{
#if VOXEL_TRAVERSAL_DEBUG
AddStepAABB(InSettings.bDebugEnabled, InnerHitP - D * 2, InnerHitP + D * 2, float3(1, 0.25f, 0));
#endif
Out.HitT = length(InnerHitP - O);
return Out;
}
}
}
#if VOXEL_TRAVERSAL_TYPE == VOXEL_TRAVERSAL_SPARSE_MIPMAP
// Increase the stepping size as we walk away from the start point.
// Hypothesis:
// * don't increase the stepping too soon as we might miss important closeby details
// * don't increase too much the stepping other wise we will miss important thing or look too coarse
PageStepping = clamp(PageStepping += 0.5f, 1.f, 8.f);
#endif
// t is used for defining the intersection point at the entry of a valid page
t = min(tMax.x, min(tMax.y, tMax.z));
// Find the next page indx to visit and update the tmax, accordingly
const float3 Mask = tMax.x < tMax.y ?
(tMax.x < tMax.z ? float3(1, 0, 0) : float3(0, 0, 1)) :
(tMax.y < tMax.z ? float3(0, 1, 0) : float3(0, 0, 1));
PageIndexCoord += Step * Mask;
tMax += tDelta * Mask;
const float3 NewDelta = tDelta * Mask;
}
}
return Out;
}
#endif