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

515 lines
19 KiB
HLSL

// Copyright Epic Games, Inc. All Rights Reserved.
/*=============================================================================
DistanceFieldLightingShared.usf
=============================================================================*/
#pragma once
#ifndef THREADGROUP_SIZEX
#define THREADGROUP_SIZEX 1
#endif
#ifndef THREADGROUP_SIZEY
#define THREADGROUP_SIZEY 1
#endif
#define THREADGROUP_TOTALSIZE (THREADGROUP_SIZEX * THREADGROUP_SIZEY)
#ifndef DOWNSAMPLE_FACTOR
#define DOWNSAMPLE_FACTOR 1
#endif
#ifndef UPDATEOBJECTS_THREADGROUP_SIZE
#define UPDATEOBJECTS_THREADGROUP_SIZE 1
#endif
#ifndef DISTANCEFIELD_PRIMITIVE_TYPE_DEFINED
#define DISTANCEFIELD_PRIMITIVE_TYPE_DEFINED
#define DFPT_SignedDistanceField 0
#define DFPT_HeightField 1
#endif
#ifndef DISTANCEFIELD_PRIMITIVE_TYPE
#define DISTANCEFIELD_PRIMITIVE_TYPE DFPT_SignedDistanceField
#endif
#ifndef OFFSET_DATA_STRUCT
#define OFFSET_DATA_STRUCT (0)
#endif
#ifndef USE_DISTANCE_FIELD_SAMPLER
#define USE_DISTANCE_FIELD_SAMPLER (0)
#endif
// Size of SDF brick in voxels, including padding
float3 DistanceFieldBrickSize;
// Size of SDF brick unique data in voxels
float3 DistanceFieldUniqueDataBrickSize;
// Size of brick atlas, in bricks
uint3 DistanceFieldBrickAtlasSizeInBricks;
// Mask and Log2 sizes used to decompose linear brick index into 3d with shifts and masks
uint3 DistanceFieldBrickAtlasMask;
uint3 DistanceFieldBrickAtlasSizeLog2;
// Size of a voxel in the brick atlas, which is also 1 / AtlasSize.
float3 DistanceFieldBrickAtlasTexelSize;
float3 DistanceFieldBrickAtlasHalfTexelSize;
float3 DistanceFieldBrickOffsetToAtlasUVScale;
float3 DistanceFieldUniqueDataBrickSizeInAtlasTexels;
// Stores ranges of global brick indices for each asset mip. Brick indices be INVALID_BRICK_INDEX.
ByteAddressBuffer DistanceFieldIndirectionTable;
Buffer<float4> DistanceFieldIndirection2Table;
Texture3D<float4> DistanceFieldIndirectionAtlas;
// Brick atlas texture, used with hardware trilinear filtering
Texture3D DistanceFieldBrickTexture;
// Distance Field Asset data specific to a given mip
struct FDFAssetData
{
// Number of available mip maps
uint NumMips;
// 3d size of the mip's indirection table, which is flattened in DistanceFieldIndirectionTable
uint3 IndirectionDimensions;
// Offset into DistanceFieldIndirectionTable that was allocated for this mip
uint IndirectionTableOffset;
// Transforms the volume texture encoded distance to volume space
float2 DistanceFieldToVolumeScaleBias;
// Transforms a volume space position to virtual indirection space
float3 VolumeToIndirectionAdd;
float3 VolumeToIndirectionScale;
};
// Per-scene buffer containing packed Distance Field Asset data
StructuredBuffer<float4> SceneDistanceFieldAssetData;
// Must match C++
#define DF_ASSET_DATA_MIP_STRIDE 3
#define DF_MAX_NUM_MIPS 3
#define DF_ASSET_DATA_STRIDE (DF_ASSET_DATA_MIP_STRIDE * DF_MAX_NUM_MIPS)
#define INVALID_BRICK_INDEX 0xFFFFFFFF
#define INDIRECTION_DIMENSION_MASK 0x3FF // Must match DistanceField::MaxIndirectionDimension
FDFAssetData LoadDFAssetData(StructuredBuffer<float4> AssetDataBuffer, uint Offset)
{
uint4 Vector0 = asuint(AssetDataBuffer[Offset + 0]);
float4 Vector1 = AssetDataBuffer[Offset + 1];
float4 Vector2 = AssetDataBuffer[Offset + 2];
FDFAssetData Data;
Data.IndirectionDimensions.x = Vector0.x & INDIRECTION_DIMENSION_MASK;
Data.IndirectionDimensions.y = (Vector0.x >> 10) & INDIRECTION_DIMENSION_MASK;
Data.IndirectionDimensions.z = (Vector0.x >> 20) & INDIRECTION_DIMENSION_MASK;
Data.NumMips = Vector0.x >> 30;
Data.IndirectionTableOffset = Vector0.y;
Data.DistanceFieldToVolumeScaleBias = float2(Vector1.w, Vector2.w);
Data.VolumeToIndirectionScale = Vector1.xyz;
Data.VolumeToIndirectionAdd = Vector2.xyz;
return Data;
}
// ReversedMipIndex must be in [0, NumMips - 1], where 0 is the lowest resolution mip that is always present
FDFAssetData LoadDFAssetData(uint AssetIndex, uint ReversedMipIndex)
{
return LoadDFAssetData(SceneDistanceFieldAssetData, AssetIndex * DF_ASSET_DATA_STRIDE + ReversedMipIndex * DF_ASSET_DATA_MIP_STRIDE);
}
// Loads the mip asset data for the highest resolution mip, which is also the narrowest band, so the distance field sampled with this asset data will have clamped distances.
// Use DistanceToMeshSurfaceStandalone instead to get both an accurate and unclamped distance field.
FDFAssetData LoadDFAssetDataHighestResolution(uint AssetIndex)
{
uint NumMips = LoadDFAssetData(AssetIndex, 0).NumMips;
return LoadDFAssetData(AssetIndex, NumMips - 1);
}
RWBuffer<uint> RWObjectIndirectArguments;
Buffer<uint> ObjectIndirectArguments;
RWStructuredBuffer<uint> RWCulledObjectIndices;
StructuredBuffer<uint> CulledObjectIndices;
uint GetCulledNumObjects()
{
// IndexCount, NumInstances, StartIndex, BaseVertexIndex, FirstInstance
return ObjectIndirectArguments[1];
}
// In float4's. Must match equivalent C++ variables.
#define DF_OBJECT_BOUNDS_STRIDE 3
#define DF_OBJECT_DATA_STRIDE 10
#define HEIGHTFIELD_OBJECT_BOUNDS_STRIDE 3
#define HEIGHTFIELD_OBJECT_DATA_STRIDE 7
struct FDFObjectBounds
{
FDFVector3 Center; // World space bounds center
float SphereRadius; // World space bounding sphere extent
float3 BoxExtent; // World space AABB extent
uint OftenMoving;
bool bVisible;
uint bCastShadow;
bool bIsNaniteMesh;
uint bEmissiveLightSource;
bool bAffectIndirectLightingWhileHidden;
};
uint NumSceneObjects;
StructuredBuffer<float4> SceneObjectBounds;
StructuredBuffer<float4> SceneHeightfieldObjectBounds;
uint NumSceneHeightfieldObjects;
SamplerState DistanceFieldSampler;
FDFObjectBounds LoadDFObjectBounds(uint ObjectIndex)
{
FDFObjectBounds Bounds;
float4 Vector0 = SceneObjectBounds[ObjectIndex * DF_OBJECT_BOUNDS_STRIDE + 0];
float3 PositionHigh = Vector0.xyz;
float4 Vector1 = SceneObjectBounds[ObjectIndex * DF_OBJECT_BOUNDS_STRIDE + 1];
float3 PositionLow = Vector1.xyz;
Bounds.Center = MakeDFVector3(PositionHigh, PositionLow);
Bounds.SphereRadius = Vector1.w;
float4 Vector2 = SceneObjectBounds[ObjectIndex * DF_OBJECT_BOUNDS_STRIDE + 2];
Bounds.BoxExtent = Vector2.xyz;
uint Flags = asuint(Vector2.w);
Bounds.OftenMoving = Flags & 1U;
Bounds.bCastShadow = (Flags & 2U) != 0U;
Bounds.bIsNaniteMesh = (Flags & 4U) != 0U;
Bounds.bEmissiveLightSource = (Flags & 8U) != 0U;
Bounds.bVisible = (Flags & 16U) != 0U;
Bounds.bAffectIndirectLightingWhileHidden = (Flags & 32U) != 0U;
return Bounds;
}
struct FHeightfieldObjectBounds
{
FDFVector3 BoxOrigin; // World space AABB center
float3 BoxExtent; // World space AABB extent
bool bInAtlas;
};
// Must match FLumenHeightfieldData as heightfield culling is reused
FHeightfieldObjectBounds LoadHeightfieldObjectBounds(uint ObjectIndex)
{
FHeightfieldObjectBounds Bounds;
float4 Vector0 = SceneHeightfieldObjectBounds[ObjectIndex * HEIGHTFIELD_OBJECT_BOUNDS_STRIDE + 0];
float4 Vector1 = SceneHeightfieldObjectBounds[ObjectIndex * HEIGHTFIELD_OBJECT_BOUNDS_STRIDE + 1];
float3 PositionHigh = Vector0.xyz;
float3 RelativeWorldPosition = Vector1.xyz;
Bounds.BoxOrigin = DFTwoSum(PositionHigh, RelativeWorldPosition);
float4 Vector2 = SceneHeightfieldObjectBounds[ObjectIndex * HEIGHTFIELD_OBJECT_BOUNDS_STRIDE + 2];
Bounds.BoxExtent = Vector2.xyz;
uint Flags = asuint(Vector2.w);
Bounds.bInAtlas = Flags & 1U;
return Bounds;
}
struct FDFObjectData
{
// Extent of the mesh bounds in volume space
float3 VolumePositionExtent;
// Volume space bias and surface expand amount
float VolumeSurfaceBias;
// Whether the mesh used two sided materials and hit positions from tracing will be more inaccurate
bool bMostlyTwoSided;
// Deprecated, use VolumeToWorldScale instead
float VolumeScale;
// Artist specified world space distance to avoid self shadowing on this mesh, usually to prevent incorrect self-intersection from WPO animation
float SelfShadowBias;
// For view distance culling to match main view rasterizer
float2 MinMaxDrawDistance2;
uint GPUSceneInstanceIndex;
// Transforms between world space and the volume space that tracing happens in
FDFInverseMatrix WorldToVolume;
FDFMatrix VolumeToWorld;
// Scales along each axis to transform from volume space to world space. Transform a scaled direction to better handle non-uniform scaling.
float3 VolumeToWorldScale;
// Index into SceneDistanceFieldAssetData
uint AssetIndex;
};
FDFObjectData LoadDFObjectDataFromBuffer(StructuredBuffer<float4> SourceBuffer, uint ObjectIndex)
{
float3 PositionHigh = SourceBuffer[ObjectIndex * DF_OBJECT_DATA_STRIDE + 0].xyz;
FDFObjectData Data;
float4 V0 = SourceBuffer[ObjectIndex * DF_OBJECT_DATA_STRIDE + 1];
float4 V1 = SourceBuffer[ObjectIndex * DF_OBJECT_DATA_STRIDE + 2];
float4 V2 = SourceBuffer[ObjectIndex * DF_OBJECT_DATA_STRIDE + 3];
float4x4 RelativeWorldToVolume = transpose(float4x4(V0, V1, V2, float4(0.0f, 0.0f, 0.0f, 1.0f)));
Data.WorldToVolume = MakeDFInverseMatrix4x3(PositionHigh, RelativeWorldToVolume);
float4 Vector3 = SourceBuffer[ObjectIndex * DF_OBJECT_DATA_STRIDE + 4];
Data.VolumePositionExtent = Vector3.xyz;
Data.VolumeSurfaceBias = abs(Vector3.w);
Data.bMostlyTwoSided = Vector3.w < 0.0f;
float4 Vector4 = SourceBuffer[ObjectIndex * DF_OBJECT_DATA_STRIDE + 5];
Data.MinMaxDrawDistance2 = Vector4.xy;
Data.SelfShadowBias = Vector4.z;
Data.GPUSceneInstanceIndex = asuint(Vector4.w);
V0 = SourceBuffer[ObjectIndex * DF_OBJECT_DATA_STRIDE + 6];
V1 = SourceBuffer[ObjectIndex * DF_OBJECT_DATA_STRIDE + 7];
V2 = SourceBuffer[ObjectIndex * DF_OBJECT_DATA_STRIDE + 8];
float4x4 VolumeToRelativeWorld = transpose(float4x4(V0, V1, V2, float4(0.0f, 0.0f, 0.0f, 1.0f)));
Data.VolumeToWorld = MakeDFMatrix(PositionHigh, VolumeToRelativeWorld);
float4 Vector8 = SourceBuffer[ObjectIndex * DF_OBJECT_DATA_STRIDE + 9];
Data.VolumeToWorldScale = Vector8.xyz;
Data.VolumeScale = min(Data.VolumeToWorldScale.x, min(Data.VolumeToWorldScale.y, Data.VolumeToWorldScale.z));
Data.AssetIndex = asuint(Vector8.w);
return Data;
}
struct FHeightfieldObjectData
{
FDFInverseMatrix WorldToLocal;
float4 SizeScale;
float4 AtlasUVScaleBias;
float4 VisibilityAtlasUVScaleBias;
};
FHeightfieldObjectData LoadHeightfieldObjectDataFromBuffer(StructuredBuffer<float4> SourceBuffer, uint ObjectIndex)
{
FHeightfieldObjectData Data;
float3 PositionHigh = SourceBuffer[ObjectIndex * HEIGHTFIELD_OBJECT_DATA_STRIDE + 0].xyz;
float4 V0 = SourceBuffer[ObjectIndex * HEIGHTFIELD_OBJECT_DATA_STRIDE + 1];
float4 V1 = SourceBuffer[ObjectIndex * HEIGHTFIELD_OBJECT_DATA_STRIDE + 2];
float4 V2 = SourceBuffer[ObjectIndex * HEIGHTFIELD_OBJECT_DATA_STRIDE + 3];
float4x4 RelativeWorldToVolume = transpose(float4x4(V0, V1, V2, float4(0.0f, 0.0f, 0.0f, 1.0f)));
Data.WorldToLocal = MakeDFInverseMatrix4x3(PositionHigh, RelativeWorldToVolume);
Data.SizeScale = SourceBuffer[ObjectIndex * HEIGHTFIELD_OBJECT_DATA_STRIDE + 4];
Data.AtlasUVScaleBias = SourceBuffer[ObjectIndex * HEIGHTFIELD_OBJECT_DATA_STRIDE + 5];
Data.VisibilityAtlasUVScaleBias = SourceBuffer[ObjectIndex * HEIGHTFIELD_OBJECT_DATA_STRIDE + 6];
return Data;
}
StructuredBuffer<float4> SceneObjectData;
StructuredBuffer<float4> SceneHeightfieldObjectData;
FDFObjectData LoadDFObjectData(uint ObjectIndex)
{
return LoadDFObjectDataFromBuffer(SceneObjectData, ObjectIndex);
}
FHeightfieldObjectData LoadHeightfieldObjectData(uint ObjectIndex)
{
return LoadHeightfieldObjectDataFromBuffer(SceneHeightfieldObjectData, ObjectIndex);
}
uint NumConvexHullPlanes;
float4 ViewFrustumConvexHull[6];
bool ViewFrustumIntersectSphere(float3 SphereOrigin, float SphereRadius)
{
for (uint PlaneIndex = 0; PlaneIndex < NumConvexHullPlanes; PlaneIndex++)
{
float4 PlaneData = ViewFrustumConvexHull[PlaneIndex];
float PlaneDistance = dot(PlaneData.xyz, SphereOrigin) - PlaneData.w;
if (PlaneDistance > SphereRadius)
{
return false;
}
}
return true;
}
bool ViewFrustumIntersectAABB(float3 BoxCenter, float3 BoxExtent)
{
for (uint PlaneIndex = 0; PlaneIndex < NumConvexHullPlanes; PlaneIndex++)
{
float4 PlaneData = ViewFrustumConvexHull[PlaneIndex];
float PlaneDistance = dot(PlaneData.xyz, BoxCenter) - PlaneData.w;
float ProjectedExtent = dot(BoxExtent, abs(PlaneData.xyz));
if (PlaneDistance > ProjectedExtent)
{
return false;
}
}
return true;
}
float SampleDistanceFieldBrickTexture(float3 UV)
{
#if SUPPORTS_INDEPENDENT_SAMPLERS && !USE_DISTANCE_FIELD_SAMPLER
return Texture3DSampleLevel(DistanceFieldBrickTexture, GlobalBilinearWrappedSampler, UV, 0).x;
#else
return Texture3DSampleLevel(DistanceFieldBrickTexture, DistanceFieldSampler, UV, 0).x;
#endif
}
// Returns a Volume space signed distance to the mesh's surface
// This distance is clamped to the band of the current mip
float SampleSparseMeshSignedDistanceField(float3 SampleVolumePosition, FDFAssetData DFAssetData)
{
// Transform the volume space position into the [0, IndirectionDimensions] virtual indirection space of the mesh
float3 IndirectionPos = SampleVolumePosition * DFAssetData.VolumeToIndirectionScale + DFAssetData.VolumeToIndirectionAdd;
int3 IndirectionCoord = IndirectionPos;
#if OFFSET_DATA_STRUCT == 0
uint IndirectionIndex = (IndirectionCoord.z * DFAssetData.IndirectionDimensions.y + IndirectionCoord.y) * DFAssetData.IndirectionDimensions.x + IndirectionCoord.x;
// Fetch the brick index that covers this region of the virtual UV space
uint BrickIndex = DistanceFieldIndirectionTable.Load((DFAssetData.IndirectionTableOffset + IndirectionIndex) * 4);
bool ValidBrick = BrickIndex != INVALID_BRICK_INDEX;
#elif OFFSET_DATA_STRUCT == 1
uint IndirectionIndex = (IndirectionCoord.z * DFAssetData.IndirectionDimensions.y + IndirectionCoord.y) * DFAssetData.IndirectionDimensions.x + IndirectionCoord.x;
float4 BrickOffset = DistanceFieldIndirection2Table.Load(DFAssetData.IndirectionTableOffset + IndirectionIndex);
bool ValidBrick = BrickOffset.w > 0;
#else
float4 BrickOffset = DistanceFieldIndirectionAtlas.Load(int4(IndirectionCoord, 0));
bool ValidBrick = BrickOffset.w > 0;
#endif
float MaxEncodedDistance = DFAssetData.DistanceFieldToVolumeScaleBias.x + DFAssetData.DistanceFieldToVolumeScaleBias.y;
float DistanceField = MaxEncodedDistance;
// If the brick was not stored then it had the maximum encodable distance
if (ValidBrick)
{
// Half voxel border around each brick
float3 BrickLocalUV = IndirectionPos - IndirectionCoord;
#if OFFSET_DATA_STRUCT == 0
// Decompose into 3d offset
float3 BrickOffset = uint3(
BrickIndex & DistanceFieldBrickAtlasMask.x,
(BrickIndex >> DistanceFieldBrickAtlasSizeLog2.x) & DistanceFieldBrickAtlasMask.y,
BrickIndex >> (DistanceFieldBrickAtlasSizeLog2.x + DistanceFieldBrickAtlasSizeLog2.y));
#endif
float3 AtlasUV = BrickOffset.xyz * DistanceFieldBrickOffsetToAtlasUVScale + BrickLocalUV * DistanceFieldUniqueDataBrickSizeInAtlasTexels + DistanceFieldBrickAtlasHalfTexelSize;
float EncodedDistanceField = SampleDistanceFieldBrickTexture(AtlasUV);
DistanceField = EncodedDistanceField * DFAssetData.DistanceFieldToVolumeScaleBias.x + DFAssetData.DistanceFieldToVolumeScaleBias.y;
}
return DistanceField;
}
// Returns the Volume space distance to the mesh's surface, sampling only the lowest resolution mip for speed. Will be inaccurate near the mesh surface.
// Use this variant when querying distance for culling, rather than sphere tracing
// SampleVolumePosition must be within [-VolumePositionExtent, VolumePositionExtent]
float DistanceToMeshSurfaceStandaloneApproximate(float3 SampleVolumePosition, FDFObjectData DFObjectData)
{
// Hardcode lowest resolution mip
FDFAssetData DFAssetMipData = LoadDFAssetData(DFObjectData.AssetIndex, 0);
float DistanceField = SampleSparseMeshSignedDistanceField(SampleVolumePosition, DFAssetMipData);
return DistanceField;
}
// Returns the Volume space distance to the mesh's surface
// Use this variant when querying distance for culling, rather than sphere tracing
// SampleVolumePosition must be within [-VolumePositionExtent, VolumePositionExtent]
float DistanceToMeshSurfaceStandalone(float3 SampleVolumePosition, FDFObjectData DFObjectData)
{
uint NumMips = LoadDFAssetData(DFObjectData.AssetIndex, 0).NumMips;
float DistanceField = 0;
// Start from the lowest resolution mip, continue sampling higher resolution mips if we are near the surface
for (uint ReversedMipIndex = 0; ReversedMipIndex < NumMips; ReversedMipIndex++)
{
FDFAssetData DFAssetMipData = LoadDFAssetData(DFObjectData.AssetIndex, ReversedMipIndex);
DistanceField = SampleSparseMeshSignedDistanceField(SampleVolumePosition, DFAssetMipData);
float MaxEncodedDistance = DFAssetMipData.DistanceFieldToVolumeScaleBias.x + DFAssetMipData.DistanceFieldToVolumeScaleBias.y;
if (abs(DistanceField) > .25 * MaxEncodedDistance)
{
break;
}
}
return DistanceField;
}
// Only valid near the surface
// SampleVolumePosition must be within [-VolumePositionExtent, VolumePositionExtent]
float3 CalculateMeshSDFGradient(float3 SampleVolumePosition, FDFAssetData DFAssetData)
{
float3 VirtualUVVoxelSize = 1.0f / float3(DistanceFieldUniqueDataBrickSize);
float3 VolumeSpaceVoxelSize = VirtualUVVoxelSize / DFAssetData.VolumeToIndirectionScale;
// Mesh distance fields have a 1 voxel border around the mesh bounds and can't bilinear sample further than 0.5 voxel from that border
float3 VoxelOffset = 0.5f * VolumeSpaceVoxelSize;
float R = SampleSparseMeshSignedDistanceField(float3(SampleVolumePosition.x + VoxelOffset.x, SampleVolumePosition.y, SampleVolumePosition.z), DFAssetData);
float L = SampleSparseMeshSignedDistanceField(float3(SampleVolumePosition.x - VoxelOffset.x, SampleVolumePosition.y, SampleVolumePosition.z), DFAssetData);
float F = SampleSparseMeshSignedDistanceField(float3(SampleVolumePosition.x, SampleVolumePosition.y + VoxelOffset.y, SampleVolumePosition.z), DFAssetData);
float B = SampleSparseMeshSignedDistanceField(float3(SampleVolumePosition.x, SampleVolumePosition.y - VoxelOffset.y, SampleVolumePosition.z), DFAssetData);
float U = SampleSparseMeshSignedDistanceField(float3(SampleVolumePosition.x, SampleVolumePosition.y, SampleVolumePosition.z + VoxelOffset.z), DFAssetData);
float D = SampleSparseMeshSignedDistanceField(float3(SampleVolumePosition.x, SampleVolumePosition.y, SampleVolumePosition.z - VoxelOffset.z), DFAssetData);
float3 Gradient = float3(R - L, F - B, U - D);
return Gradient;
}
Texture2D HeightFieldTexture;
Texture2D HFVisibilityTexture;
float SampleHeightFieldAtlas(float2 UV)
{
#if SUPPORTS_INDEPENDENT_SAMPLERS && !USE_DISTANCE_FIELD_SAMPLER
float4 SampleValue = Texture2DSampleLevel(HeightFieldTexture, GlobalBilinearWrappedSampler, UV, 0);
#else
float4 SampleValue = Texture2DSampleLevel(HeightFieldTexture, DistanceFieldSampler, UV, 0);
#endif
return DecodePackedHeight(SampleValue.xy);
}
float SampleHFVisibilityTexture(float2 UV)
{
#if SUPPORTS_INDEPENDENT_SAMPLERS && !USE_DISTANCE_FIELD_SAMPLER
return Texture2DSampleLevel(HFVisibilityTexture, GlobalBilinearWrappedSampler, UV, 0).r;
#else
return Texture2DSampleLevel(HFVisibilityTexture, DistanceFieldSampler, UV, 0).r;
#endif
}