// 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 DistanceFieldIndirection2Table; Texture3D 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 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 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 RWObjectIndirectArguments; Buffer ObjectIndirectArguments; RWStructuredBuffer RWCulledObjectIndices; StructuredBuffer 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 SceneObjectBounds; StructuredBuffer 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 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 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 SceneObjectData; StructuredBuffer 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 }