// Copyright Epic Games, Inc. All Rights Reserved. /*============================================================================= HairRendering.h: Hair rendering implementation. =============================================================================*/ #pragma once #include "CoreMinimal.h" #include "RendererInterface.h" #include "RenderGraphResources.h" #include "RHIGPUReadback.h" #include "Shader.h" #include "ConvexVolume.h" #include "HairStrandsDefinitions.h" #include "HairStrandsInterface.h" class FLightSceneInfo; class FPrimitiveSceneProxy; class FViewInfo; class FScene; class FInstanceCullingManager; class FHairGroupPublicData; struct FMeshBatch; struct FMeshBatchAndRelevance; //////////////////////////////////////////////////////////////////////////////////// // HairStrands uniform buffer BEGIN_GLOBAL_SHADER_PARAMETER_STRUCT(FHairStrandsViewUniformParameters, ) SHADER_PARAMETER(FIntPoint, HairTileCountXY) // Tile count in X/Y SHADER_PARAMETER(uint32, MaxSamplePerPixelCount) // Max number of sample per pixel SHADER_PARAMETER(float, HairDualScatteringRoughnessOverride) // Override the roughness used for dual scattering (for hack/test purpose only) SHADER_PARAMETER(FIntPoint, HairSampleViewportResolution) // Maximum viewport resolution of the sample space SHADER_PARAMETER(uint32, bHairTileValid) // True if tile data are valid SHADER_PARAMETER(FVector4f, HairOnlyDepthHZBParameters) // HZB parameters SHADER_PARAMETER_RDG_TEXTURE(Texture2D, HairCoverageTexture) // Hair pixel's coverage SHADER_PARAMETER_RDG_TEXTURE(Texture2D, HairOnlyDepthTexture) // Depth texture containing only hair depth (not strongly typed for legacy shader code reason) SHADER_PARAMETER_RDG_TEXTURE(Texture2D, HairOnlyDepthClosestHZBTexture) // HZB closest depth texture containing only hair depth (not strongly typed for legacy shader code reason) SHADER_PARAMETER_RDG_TEXTURE(Texture2D, HairOnlyDepthFurthestHZBTexture) // HZB furthest depth texture containing only hair depth (not strongly typed for legacy shader code reason) SHADER_PARAMETER_SAMPLER(SamplerState, HairOnlyDepthHZBSampler) // HZB sampler SHADER_PARAMETER_RDG_TEXTURE(Texture2D, HairSampleOffset) // Offset & count, for accessing pixel's samples, based on screen pixel position SHADER_PARAMETER_RDG_BUFFER_SRV(StructuredBuffer, HairSampleCount) // Total count of hair sample, in sample space SHADER_PARAMETER_RDG_BUFFER_SRV(StructuredBuffer, HairSampleData)// Sample data (coverage, tangent, base color, ...), in sample space // HAIRSTRANDS_TODO: change this to be a uint4 so that we don't have to include the type for generated contant buffer SHADER_PARAMETER_RDG_BUFFER_SRV(Buffer, HairSampleCoords) // Screen pixel coordinate of each sample, in sample space SHADER_PARAMETER_RDG_BUFFER_SRV(Buffer, HairTileData) // Tile coords (RG16F) SHADER_PARAMETER_RDG_BUFFER_SRV(Buffer, HairTileCount) // Tile total count (actual number of tiles) END_GLOBAL_SHADER_PARAMETER_STRUCT() void GetHairStrandsInstanceCommon(FRDGBuilder& GraphBuilder, const FViewInfo& ViewInfo, const FHairGroupPublicData* HairGroupPublicData, FHairStrandsInstanceCommonParameters& OutCommon); void GetHairStrandsInstanceResources(FRDGBuilder& GraphBuilder, const FViewInfo& ViewInfo, const FHairGroupPublicData* HairGroupPublicData, bool bForceRegister, FHairStrandsInstanceResourceParameters& OutResources); void GetHairStrandsInstanceCulling(FRDGBuilder& GraphBuilder, const FViewInfo& ViewInfo, const FHairGroupPublicData* HairGroupPublicData, bool bCullingEnable, FHairStrandsInstanceCullingParameters& OutCulling); FHairStrandsInstanceParameters GetHairStrandsInstanceParameters(FRDGBuilder& GraphBuilder, const FViewInfo& ViewInfo, const FHairGroupPublicData* HairGroupPublicData, bool bCullingEnable, bool bForceRegister); //////////////////////////////////////////////////////////////////////////////////// // Tile data struct FHairStrandsTiles { enum class ETileType : uint8 { HairAll, HairFull, HairPartial, Other, Count }; static const uint32 TileTypeCount = uint32(ETileType::Count); FIntPoint BufferResolution = FIntPoint(0, 0); static const uint32 GroupSize = 64; static const uint32 TileSize = 8; static const uint32 TilePerThread_GroupSize = 64; uint32 TileCount = 0; FIntPoint TileCountXY = FIntPoint(0, 0); bool bRectPrimitive = false; // Buffers per tile types FRDGBufferSRVRef TileDataSRV[TileTypeCount] = { nullptr, nullptr }; FRDGBufferRef TileDataBuffer[TileTypeCount] = { nullptr, nullptr }; // Buffer shared for all tile types FRDGBufferSRVRef TileCountSRV = nullptr; FRDGBufferRef TileCountBuffer = nullptr; FRDGBufferRef TileIndirectDrawBuffer = nullptr; FRDGBufferRef TileIndirectDispatchBuffer = nullptr; FRDGBufferRef TileIndirectRayDispatchBuffer = nullptr; FRDGBufferRef TilePerThreadIndirectDispatchBuffer = nullptr; static FORCEINLINE uint32 GetIndirectDrawArgOffset(ETileType Type) { return uint32(Type) * sizeof(FRHIDrawIndirectParameters); } static FORCEINLINE uint32 GetIndirectDispatchArgOffset(ETileType Type) { return uint32(Type) * sizeof(FRHIDispatchIndirectParameters); } static FORCEINLINE uint32 GetIndirectRayDispatchArgOffset(ETileType Type) { return uint32(Type) * sizeof(FRHIDispatchIndirectParameters); } bool IsValid() const { return TileCount > 0 && TileDataBuffer[uint32(ETileType::HairAll)] != nullptr; } FRDGBufferRef GetTileBuffer(ETileType Type) const { const uint32 Index = uint32(Type); check(TileDataBuffer[Index] != nullptr); return TileDataBuffer[Index];} FRDGBufferSRVRef GetTileBufferSRV(ETileType Type) const { const uint32 Index = uint32(Type); check(TileDataSRV[Index] != nullptr); return TileDataSRV[Index];} }; FORCEINLINE uint32 ToIndex(FHairStrandsTiles::ETileType Type) { return uint32(Type); } const TCHAR* ToString(FHairStrandsTiles::ETileType Type); //////////////////////////////////////////////////////////////////////////////////// // Visibility Data struct FHairStrandsVisibilityData { FRDGTextureRef VelocityTexture = nullptr; FRDGTextureRef ResolveMaskTexture = nullptr; FRDGTextureRef CoverageTexture = nullptr; FRDGTextureRef ViewHairCountTexture = nullptr; FRDGTextureRef ViewHairCountUintTexture = nullptr; FRDGTextureRef HairOnlyDepthTexture = nullptr; FRDGTextureRef HairOnlyDepthClosestHZBTexture = nullptr; FRDGTextureRef HairOnlyDepthFurthestHZBTexture = nullptr; FRDGTextureRef LightChannelMaskTexture = nullptr; uint32 MaxSampleCount = 8; // Sample count per pixel uint32 MaxNodeCount = 0; // Total sample count FRDGBufferRef NodeCount = nullptr; FRDGTextureRef NodeIndex = nullptr; FRDGBufferRef NodeData = nullptr; FRDGBufferRef NodeVisData = nullptr; FRDGBufferRef NodeCoord = nullptr; FRDGBufferRef NodeIndirectArg = nullptr; uint32 NodeGroupSize = 0; FVector4f HairOnlyDepthHZBParameters = FVector4f::Zero(); uint32 RasterizedInstanceCount = 0; uint32 MaxControlPointCount = 0; FRDGBufferSRVRef ControlPointsSRV = nullptr; FRDGBufferSRVRef ControlPointCount = nullptr; FRDGBufferSRVRef ControlPointVelocitySRV = nullptr; FHairStrandsTiles TileData; const static EPixelFormat NodeCoordFormat = PF_R16G16_UINT; const static EPixelFormat CoverageFormat = PF_R16F; // Hair lighting is accumulated within this buffer // Allocated conservatively // User indirect dispatch for accumulating contribution FIntPoint SampleLightingViewportResolution; FRDGTextureRef SampleLightingTexture = nullptr; }; //////////////////////////////////////////////////////////////////////////////////// // Voxel data struct FPackedVirtualVoxelNodeDesc { // This is just a placeholder having the correct size. The actual definition is in HairStradsNVoxelPageCommon.ush const static EPixelFormat Format = PF_R32G32B32A32_UINT; const static uint32 ComponentCount = 2; // Shader View is struct { uint4; uint4; } FVector3f MinAABB; uint32 PackedPageIndexResolution; FVector3f MaxAABB; uint32 PageIndexOffset; }; // PixelRadiusAtDepth1 shouldn't be stored into this structure should be view independent, // but is put here for convenience at the moment since multiple views are not supported // at the moment BEGIN_SHADER_PARAMETER_STRUCT(FHairStrandsVoxelCommonParameters, ) SHADER_PARAMETER(FIntVector, PageCountResolution) SHADER_PARAMETER(float, CPUMinVoxelWorldSize) SHADER_PARAMETER(FIntVector, PageTextureResolution) SHADER_PARAMETER(uint32, PageCount) SHADER_PARAMETER(uint32, PageResolution) SHADER_PARAMETER(uint32, PageResolutionLog2) SHADER_PARAMETER(uint32, PageIndexCount) SHADER_PARAMETER(uint32, IndirectDispatchGroupSize) SHADER_PARAMETER(uint32, NodeDescCount) SHADER_PARAMETER(uint32, JitterMode) SHADER_PARAMETER(float, DensityScale) SHADER_PARAMETER(float, DensityScale_AO) SHADER_PARAMETER(float, DensityScale_Shadow) SHADER_PARAMETER(float, DensityScale_Transmittance) SHADER_PARAMETER(float, DensityScale_Environment) SHADER_PARAMETER(float, DensityScale_Raytracing) SHADER_PARAMETER(float, DepthBiasScale_Shadow) SHADER_PARAMETER(float, DepthBiasScale_Transmittance) SHADER_PARAMETER(float, DepthBiasScale_Environment) SHADER_PARAMETER(float, SteppingScale_Shadow) SHADER_PARAMETER(float, SteppingScale_Transmittance) SHADER_PARAMETER(float, SteppingScale_Environment) SHADER_PARAMETER(float, SteppingScale_Raytracing) SHADER_PARAMETER(float, HairCoveragePixelRadiusAtDepth1) SHADER_PARAMETER(float, Raytracing_ShadowOcclusionThreshold) SHADER_PARAMETER(float, Raytracing_SkyOcclusionThreshold) SHADER_PARAMETER(FVector3f, TranslatedWorldOffset) // For debug purpose SHADER_PARAMETER(FVector3f, TranslatedWorldOffsetStereoCorrection) // PreViewTranslation correction between View0 & View1 when rendering stereo SHADER_PARAMETER(uint32, AllocationFeedbackEnable) SHADER_PARAMETER_RDG_BUFFER_SRV(Buffer, AllocatedPageCountBuffer) SHADER_PARAMETER_RDG_BUFFER_SRV(Buffer, PageIndexBuffer) SHADER_PARAMETER_RDG_BUFFER_SRV(Buffer, PageIndexCoordBuffer) SHADER_PARAMETER_RDG_BUFFER_SRV(StructuredBuffer, NodeDescBuffer) // Packed into 2 x uint4 SHADER_PARAMETER_RDG_BUFFER_SRV(Buffer, CurrGPUMinVoxelSize) SHADER_PARAMETER_RDG_BUFFER_SRV(Buffer, NextGPUMinVoxelSize) END_SHADER_PARAMETER_STRUCT() BEGIN_GLOBAL_SHADER_PARAMETER_STRUCT(FVirtualVoxelParameters, RENDERER_API) SHADER_PARAMETER_STRUCT_INCLUDE(FHairStrandsVoxelCommonParameters, Common) SHADER_PARAMETER_RDG_TEXTURE(Texture3D, PageTexture) END_GLOBAL_SHADER_PARAMETER_STRUCT() struct FHairStrandsVoxelResources { FVirtualVoxelParameters Parameters; TRDGUniformBufferRef UniformBuffer; FRDGTextureRef PageTexture = nullptr; FRDGBufferRef PageIndexBuffer = nullptr; FRDGBufferRef NodeDescBuffer = nullptr; FRDGBufferRef PageIndexCoordBuffer = nullptr; FRDGBufferRef IndirectArgsBuffer = nullptr; FRDGBufferRef PageIndexGlobalCounter = nullptr; const bool IsValid() const { return UniformBuffer != nullptr && PageTexture != nullptr && NodeDescBuffer != nullptr; } }; //////////////////////////////////////////////////////////////////////////////////// // Deep shadow data struct FMinHairRadiusAtDepth1 { float Primary = 1; float Velocity = 1; float Stable = 1; }; /// Hold deep shadow information for a given light. struct FHairStrandsDeepShadowData { FMinHairRadiusAtDepth1 CPU_MinStrandRadiusAtDepth1; uint32 MacroGroupId = ~0; uint32 AtlasSlotIndex = 0; FIntPoint AtlasResolution = FIntPoint::ZeroValue; uint32 LightId = ~0; bool bIsLightDirectional = false; FVector3f LightDirection; FVector3f TranslatedLightPosition; FLinearColor LightLuminance; float LayerDistribution; FBoxSphereBounds Bounds; }; typedef TArray FHairStrandsDeepShadowDatas; struct FHairStrandsDeepShadowResources { // Limit the number of atlas slot to 32, in order to create the view info per slot in single compute // This limitation can be alleviate, and is just here for convenience (see FDeepShadowCreateViewInfoCS) static const uint32 MaxAtlasSlotCount = 32u; uint32 TotalAtlasSlotCount = 0; FIntPoint AtlasSlotResolution; FRDGTextureRef DepthAtlasTexture = nullptr; FRDGTextureRef LayersAtlasTexture = nullptr; FRDGBufferRef DeepShadowViewInfoBuffer = nullptr; }; //////////////////////////////////////////////////////////////////////////////////// // Cluster data // A groom component contains one or several HairGroup. These hair group are send to the // render as mesh batches. These meshes batches are filtered/culled per view, and regroup // into HairMacroGroup for computing voxelization/DOM data, ... // // The hierarchy of the data structure is as follow: // * HairMacroGroup // * HairGroup // * HairCluster struct FHairStrandsMacroGroupResources { uint32 MacroGroupCount = 0; FRDGBufferRef MacroGroupAABBsBuffer = nullptr; // Tight bounding boxes FRDGBufferRef MacroGroupVoxelAlignedAABBsBuffer = nullptr; // Contents of MacroGroupAABBsBuffer, but snapped to the voxel page boundaries FRDGBufferRef MacroGroupVoxelSizeBuffer = nullptr; }; class FHairGroupPublicData; /// Hair macro group infos struct FHairStrandsMacroGroupData { static const uint32 MaxMacroGroupCount = 16u; // Needs to be kept consistent with MAX_HAIR_MACROGROUP_COUNT (HairStrandsVisibilityCommon.ush) // List of primitive/mesh batch within an instance group struct PrimitiveInfo { const FMeshBatch* Mesh = nullptr; const FPrimitiveSceneProxy* PrimitiveSceneProxy = nullptr; uint32 MaterialId = 0; uint32 ResourceId = 0; uint32 GroupIndex = 0; uint32 Flags = 0; // Hair instance flags FHairGroupPublicData* PublicDataPtr = nullptr; bool IsCullingEnable() const; }; typedef TArray TPrimitiveInfos; FHairStrandsDeepShadowDatas DeepShadowDatas; TPrimitiveInfos PrimitivesInfos; FBoxSphereBounds Bounds; FIntRect ScreenRect; uint32 MacroGroupId = 0; uint32 Flags = 0; // Aggregated flags for all instances from the group bool bSupportVoxelization = false; // true if at least one of the Primitive requires voxelization }; //////////////////////////////////////////////////////////////////////////////////// // Debug data struct FHairStrandsDebugData { BEGIN_SHADER_PARAMETER_STRUCT(FWriteParameters, ) SHADER_PARAMETER(uint32, Debug_MaxShadingPointCount) SHADER_PARAMETER(uint32, Debug_MaxSampleCount) SHADER_PARAMETER_RDG_BUFFER_UAV(RWStructuredBuffer, Debug_ShadingPointBuffer) SHADER_PARAMETER_RDG_BUFFER_UAV(RWBuffer, Debug_ShadingPointCounter) SHADER_PARAMETER_RDG_BUFFER_UAV(RWStructuredBuffer, Debug_SampleBuffer) SHADER_PARAMETER_RDG_BUFFER_UAV(RWBuffer, Debug_SampleCounter) END_SHADER_PARAMETER_STRUCT() BEGIN_SHADER_PARAMETER_STRUCT(FReadParameters, ) SHADER_PARAMETER(uint32, Debug_MaxShadingPointCount) SHADER_PARAMETER(uint32, Debug_MaxSampleCount) SHADER_PARAMETER_RDG_BUFFER_SRV(StructuredBuffer, Debug_ShadingPointBuffer) SHADER_PARAMETER_RDG_BUFFER_SRV(Buffer, Debug_ShadingPointCounter) SHADER_PARAMETER_RDG_BUFFER_SRV(StructuredBuffer, Debug_SampleBuffer) SHADER_PARAMETER_RDG_BUFFER_SRV(Buffer, Debug_SampleCounter) END_SHADER_PARAMETER_STRUCT() // Plot Data struct ShadingInfo { FVector3f BaseColor; float Roughness; FVector3f T; uint32 SampleCount; FVector3f V; float SampleOffset; }; struct Sample { FVector3f Direction; float Pdf; FVector3f Weights; float Pad; }; static const uint32 MaxShadingPointCount = 32; static const uint32 MaxSampleCount = 1024 * 32; struct FPlotData { FRDGBufferRef ShadingPointBuffer = nullptr; FRDGBufferRef ShadingPointCounter = nullptr; FRDGBufferRef SampleBuffer = nullptr; FRDGBufferRef SampleCounter = nullptr; } PlotData; bool IsPlotDataValid() const { return PlotData.ShadingPointBuffer && PlotData.ShadingPointCounter && PlotData.SampleBuffer && PlotData.SampleCounter; } static FPlotData CreatePlotData(FRDGBuilder& GraphBuilder); static void SetParameters(FRDGBuilder& GraphBuilder, const FPlotData& In, FWriteParameters& Out); static void SetParameters(FRDGBuilder& GraphBuilder, const FPlotData& In, FReadParameters& Out); // PPLL debug data struct FPPLLData { FRDGTextureRef NodeCounterTexture = nullptr; FRDGTextureRef NodeIndexTexture = nullptr; FRDGBufferRef NodeDataBuffer = nullptr; } PPLLData; bool IsPPLLDataValid() const { return PPLLData.NodeCounterTexture && PPLLData.NodeIndexTexture && PPLLData.NodeDataBuffer; } // Instance cull data struct FCullData { struct FBound { FVector3f Min; FVector3f Max; }; struct FLight { FMatrix WorldToLight; FMatrix LightToWorld; FVector3f Center; FVector3f Extent; FConvexVolume ViewFrustumInLightSpace; TArray InstanceBoundInLightSpace; TArray InstanceBoundInWorldSpace; TArray InstanceIntersection; }; bool bIsValid = false; TArray DirectionalLights; FConvexVolume ViewFrustum; } CullData; struct FCommon { FRDGTextureRef SceneDepthTextureBeforeCompsition = nullptr; } Common; }; typedef TArray FHairStrandsMacroGroupDatas; //////////////////////////////////////////////////////////////////////////////////// // View Data struct FHairStrandsViewData { TRDGUniformBufferRef UniformBuffer{}; bool bIsValid = false; // Internal data FHairStrandsVisibilityData VisibilityData; FHairStrandsMacroGroupDatas MacroGroupDatas; FHairStrandsDeepShadowResources DeepShadowResources; FHairStrandsVoxelResources VirtualVoxelResources; FHairStrandsMacroGroupResources MacroGroupResources; FHairStrandsDebugData DebugData; uint32 Flags = 0; // Transient: store all light visible in primary view(s) struct FDirectionalLightCullData { const FLightSceneInfo* LightInfo = nullptr; FConvexVolume ViewFrustumInLightSpace; }; TArray VisibleShadowCastingLights; TArray VisibleShadowCastingDirectionalLights; }; // View State data (i.e., persistent across frame) struct FHairStrandsViewStateData { bool IsInit() const { return PositionsChangedDatas.Num() > 0; } void Init(); void Release(); // Buffer used for reading back the number of allocated voxels on the GPU TRefCountPtr VoxelFeedbackBuffer = nullptr; // Buffer used for reading back hair strands position changed on the GPU struct FPositionChangedData { FRHIGPUBufferReadback* ReadbackBuffer = nullptr; bool bHasPendingReadback = false; }; void EnqueuePositionsChanged(FRDGBuilder& GraphBuilder, FRDGBufferRef InBuffer); bool ReadPositionsChanged(); TArray PositionsChangedDatas; }; namespace HairStrands { TRDGUniformBufferRef CreateDefaultHairStrandsViewUniformBuffer(FRDGBuilder& GraphBuilder, FViewInfo& View); TRDGUniformBufferRef BindHairStrandsViewUniformParameters(const FViewInfo& View); TRDGUniformBufferRef BindHairStrandsVoxelUniformParameters(const FViewInfo& View); bool HasViewHairStrandsData(const FViewInfo& View); bool HasViewHairStrandsData(const TArray& Views); bool HasViewHairStrandsVoxelData(const FViewInfo& View); bool HasPositionsChanged(FRDGBuilder& GraphBuilder, const FScene& Scene, const FViewInfo& View); void DrawHitProxies(FRDGBuilder& GraphBuilder, const FScene& Scene, const FViewInfo& View, FInstanceCullingManager& InstanceCullingManager, FRDGTextureRef HitProxyTexture, FRDGTextureRef HitProxyDepthTexture); void DrawEditorSelection(FRDGBuilder& GraphBuilder, const FViewInfo& View, const FIntRect& ViewportRect, FRDGTextureRef SelectionDepthTexture); // Mesh batch helpers FHairGroupPublicData* GetHairData(const FMeshBatch* In); bool IsHairCompatible(const FMeshBatch* Mesh); bool IsHairStrandsVF(const FMeshBatch* Mesh); bool IsHairCardsVF(const FMeshBatch* Mesh); bool IsHairVisible(const FMeshBatchAndRelevance& MeshBatch, bool bCheckLengthScale); // Hair helpers bool HasHairInstanceInScene(const FScene& Scene); bool HasHairCardsVisible(const TArray& Views); bool HasHairStrandsVisible(const TArray& Views); void AddVisibleShadowCastingLight(const FScene& Scene, TArray& Views, const FLightSceneInfo* LightSceneInfo); void PostRender(FScene& Scene); }