Files
UnrealEngine/Engine/Source/Runtime/Renderer/Private/HairStrands/HairStrandsRendering.cpp
2025-05-18 13:04:45 +08:00

382 lines
13 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "HairStrandsRendering.h"
#include "HairStrandsData.h"
#include "SceneRendering.h"
#include "ScenePrivate.h"
#include "SystemTextures.h"
#include "RenderGraphUtils.h"
#include "HairStrands/HairStrandsMacroGroup.h"
#include "RenderCurveSceneExtension.h"
/////////////////////////////////////////////////////////////////////////////////////////
void AddRenderCurveRasterPipeline(
FRDGBuilder& GraphBuilder,
FScene* Scene,
const TArray<FViewInfo>& Views,
FRDGTextureRef SceneColorTexture,
FRDGTextureRef SceneDepthTexture);
/////////////////////////////////////////////////////////////////////////////////////////
static TRDGUniformBufferRef<FHairStrandsViewUniformParameters> InternalCreateHairStrandsViewUniformBuffer(
FRDGBuilder& GraphBuilder,
FHairStrandsVisibilityData* In)
{
FHairStrandsViewUniformParameters* Parameters = GraphBuilder.AllocParameters<FHairStrandsViewUniformParameters>();
Parameters->HairDualScatteringRoughnessOverride = GetHairDualScatteringRoughnessOverride();
if (In && In->CoverageTexture)
{
Parameters->HairCoverageTexture = In->CoverageTexture;
Parameters->HairOnlyDepthTexture = In->HairOnlyDepthTexture;
Parameters->HairOnlyDepthHZBParameters = In->HairOnlyDepthHZBParameters;
Parameters->HairOnlyDepthClosestHZBTexture = In->HairOnlyDepthClosestHZBTexture;
Parameters->HairOnlyDepthFurthestHZBTexture = In->HairOnlyDepthFurthestHZBTexture;
Parameters->HairOnlyDepthHZBSampler = TStaticSamplerState<SF_Point>::GetRHI();
Parameters->HairSampleOffset = In->NodeIndex;
Parameters->HairSampleData = GraphBuilder.CreateSRV(In->NodeData);
Parameters->HairSampleCoords = GraphBuilder.CreateSRV(In->NodeCoord, FHairStrandsVisibilityData::NodeCoordFormat);
Parameters->HairSampleCount = GraphBuilder.CreateSRV(In->NodeCount);
Parameters->HairSampleViewportResolution = In->SampleLightingViewportResolution;
Parameters->MaxSamplePerPixelCount = In->MaxSampleCount;
check(In->TileData.IsValid())
Parameters->HairTileData = In->TileData.GetTileBufferSRV(FHairStrandsTiles::ETileType::HairAll);
Parameters->HairTileCount = In->TileData.TileCountSRV;
Parameters->HairTileCountXY = In->TileData.TileCountXY;
Parameters->bHairTileValid = true;
if (!Parameters->HairOnlyDepthFurthestHZBTexture)
{
FRDGTextureRef BlackTexture = GSystemTextures.GetBlackDummy(GraphBuilder);
Parameters->HairOnlyDepthHZBParameters = FVector4f::Zero();
Parameters->HairOnlyDepthFurthestHZBTexture = BlackTexture;
Parameters->HairOnlyDepthClosestHZBTexture = BlackTexture;
}
}
else
{
FRDGBufferRef DummyBuffer = GSystemTextures.GetDefaultBuffer(GraphBuilder, 4);
FRDGBufferRef DummyNodeBuffer = GSystemTextures.GetDefaultStructuredBuffer(GraphBuilder, 20);
FRDGTextureRef BlackTexture = GSystemTextures.GetBlackDummy(GraphBuilder);
FRDGTextureRef ZeroR32_UINT = GSystemTextures.GetDefaultTexture2D(GraphBuilder, PF_R32_UINT, 0u);
FRDGTextureRef ZeroRGBA16_UINT = GSystemTextures.GetDefaultTexture2D(GraphBuilder, PF_R16G16_UINT, 0u);
FRDGTextureRef FarDepth = GSystemTextures.GetDepthDummy(GraphBuilder);
FRDGBufferSRVRef DummyBufferR32SRV = GraphBuilder.CreateSRV(DummyBuffer, PF_R32_UINT);
FRDGBufferSRVRef DummyBufferRG16SRV = GraphBuilder.CreateSRV(DummyBuffer, PF_R16G16_UINT);
Parameters->HairOnlyDepthTexture = FarDepth;
Parameters->HairOnlyDepthHZBParameters = FVector4f::Zero();
Parameters->HairOnlyDepthFurthestHZBTexture = BlackTexture;
Parameters->HairOnlyDepthClosestHZBTexture = Parameters->HairOnlyDepthFurthestHZBTexture;
Parameters->HairOnlyDepthHZBSampler = TStaticSamplerState<SF_Point>::GetRHI();
Parameters->HairCoverageTexture = BlackTexture;
Parameters->HairSampleCount = DummyBufferR32SRV;
Parameters->HairSampleOffset = ZeroR32_UINT;
Parameters->HairSampleCoords = DummyBufferRG16SRV;
Parameters->HairSampleData = GraphBuilder.CreateSRV(DummyNodeBuffer);
Parameters->HairSampleViewportResolution = FIntPoint(0, 0);
Parameters->MaxSamplePerPixelCount = 0u;
Parameters->HairTileData = DummyBufferR32SRV;
Parameters->HairTileCount = DummyBufferRG16SRV;
Parameters->HairTileCountXY = FIntPoint(0, 0);
}
return GraphBuilder.CreateUniformBuffer(Parameters);
}
IMPLEMENT_GLOBAL_SHADER_PARAMETER_STRUCT(FHairStrandsViewUniformParameters, "HairStrands");
bool GetHairStrandsSkyLightingDebugEnable();
void AddMeshDrawTransitionPass(
FRDGBuilder& GraphBuilder,
const FViewInfo& ViewInfo,
const FHairStrandsMacroGroupDatas& MacroGroupDatas);
FHairTransientResources* AllocateHairTransientResources(FRDGBuilder& GraphBuilder, FScene* Scene, const TArray<FViewInfo>& Views)
{
if (Scene->HairStrandsSceneData.TransientResources == nullptr)
{
Scene->HairStrandsSceneData.TransientResources = new FHairTransientResources();
}
const uint32 InstanceCount = Scene->HairStrandsSceneData.RegisteredProxies.Num();
FHairTransientResources* Out = Scene->HairStrandsSceneData.TransientResources;
*Out = FHairTransientResources();
Out->TranslatedWorldOffsetView0 = Views.Num() > 0 ? Views[0].ViewMatrices.GetPreViewTranslation() : FVector3d::ZeroVector;
if (InstanceCount > 0)
{
Out->bIsGroupAABBValid.Init(false, InstanceCount);
// Change this into a structure buffer
Out->GroupAABBBuffer = GraphBuilder.CreateBuffer(FRDGBufferDesc::CreateBufferDesc(4, 6 * InstanceCount), TEXT("Hair.Transient.GroupAABB"));
Out->GroupAABBSRV = GraphBuilder.CreateSRV(Out->GroupAABBBuffer, PF_R32_SINT);
// *4u* elements for storing DispatchCount.xyz and .w which contains the original number of items
Out->IndirectDispatchArgsBuffer = GraphBuilder.CreateBuffer(FRDGBufferDesc::CreateIndirectDesc(4u * InstanceCount), TEXT("Hair.Transient.IndirectDispatchArgs"));
Out->IndirectDispatchArgsSRV = GraphBuilder.CreateSRV(Out->IndirectDispatchArgsBuffer);
}
return Out;
}
void RenderHairPrePass(
FRDGBuilder& GraphBuilder,
FScene* Scene,
const FSceneTextures& SceneTextures,
TArray<FViewInfo>& Views,
FInstanceCullingManager& InstanceCullingManager,
const FHairInstanceCullingResults& CullingResults)
{
// Experimental curve raster pipeline. The function early outs when it is not enabled.
AddRenderCurveRasterPipeline(GraphBuilder, Scene, Views, SceneTextures.Color.Target, SceneTextures.Depth.Target);
TArray<FViewInfo*> ActiveViews;
ActiveViews.Reserve(Views.Num());
for (FViewInfo& View : Views)
{
const bool bIsViewCompatible = IsHairStrandsEnabled(EHairStrandsShaderType::Strands, View.GetShaderPlatform());
if (!View.Family || !bIsViewCompatible || !View.Family->EngineShowFlags.Hair)
continue;
// For stereo rendering, hair groups/voxelization/deep-shadow are only produced once
FVector PreViewStereoCorrection = FVector::ZeroVector;
if (IStereoRendering::IsStereoEyeView(View))
{
// nDisplay uses StereoRendering code path with a mono-view
if (Views.Num() >= 2)
{
PreViewStereoCorrection = Views[0].ViewMatrices.GetPreViewTranslation() - Views[1].ViewMatrices.GetPreViewTranslation();
}
if (IStereoRendering::IsASecondaryView(View))
{
// No need to copy the view state (i.e., HairStrandsViewStateData) as it is only used for
// voxelization feedback (only done for the first view in stereo) and Path-Tracer invalidation
// (not supporting stereo)
Views[1].HairStrandsViewData = Views[0].HairStrandsViewData;
// Render DeepShadow for the second view, as for now the computations are view dependent.
// This needs to be view independent to share result between eyes.
RenderHairStrandsDeepShadows(GraphBuilder, Scene, View, InstanceCullingManager);
return;
}
}
ActiveViews.Add(&View);
// Allocate state/feedback data if needed
if (View.ViewState && !View.ViewState->HairStrandsViewStateData.IsInit())
{
View.ViewState->HairStrandsViewStateData.Init();
}
CreateHairStrandsMacroGroups(GraphBuilder, Scene, View, CullingResults, View.HairStrandsViewData);
// Voxelization and Deep Opacity Maps
VoxelizeHairStrands(GraphBuilder, Scene, View, InstanceCullingManager, PreViewStereoCorrection);
}
// Transit resources to be used by draw passes (e.g., primary/shadow draws).
// Note: this will reset references to instances's imported buffers
for (FViewInfo* View : ActiveViews)
{
if (View->HairStrandsViewData.MacroGroupDatas.Num() > 0)
{
AddMeshDrawTransitionPass(GraphBuilder, *View, View->HairStrandsViewData.MacroGroupDatas);
}
}
for (FViewInfo* View : ActiveViews)
{
RenderHairStrandsDeepShadows(GraphBuilder, Scene, *View, InstanceCullingManager);
}
}
void RenderHairBasePass(
FRDGBuilder& GraphBuilder,
FScene* Scene,
const FSceneTextures& SceneTextures,
TArray<FViewInfo>& Views,
FInstanceCullingManager& InstanceCullingManager)
{
for (FViewInfo& View : Views)
{
const bool bIsViewCompatible = IsHairStrandsEnabled(EHairStrandsShaderType::Strands, View.GetShaderPlatform());
if (View.Family && View.Family->EngineShowFlags.Hair && bIsViewCompatible && View.HairStrandsViewData.MacroGroupDatas.Num() > 0)
{
RenderHairStrandsVisibilityBuffer(
GraphBuilder,
Scene,
View,
SceneTextures.GBufferA,
SceneTextures.GBufferB,
SceneTextures.GBufferC,
SceneTextures.GBufferD,
SceneTextures.GBufferE,
SceneTextures.Color.Resolve,
SceneTextures.Depth.Resolve,
SceneTextures.Velocity,
InstanceCullingManager);
const bool bDebugSamplingEnable = GetHairStrandsSkyLightingDebugEnable();
if (bDebugSamplingEnable)
{
View.HairStrandsViewData.DebugData.PlotData = FHairStrandsDebugData::CreatePlotData(GraphBuilder);
}
}
if (View.HairStrandsViewData.VisibilityData.CoverageTexture)
{
View.HairStrandsViewData.UniformBuffer = InternalCreateHairStrandsViewUniformBuffer(GraphBuilder, &View.HairStrandsViewData.VisibilityData);
View.HairStrandsViewData.bIsValid = true;
}
else
{
View.HairStrandsViewData.UniformBuffer = InternalCreateHairStrandsViewUniformBuffer(GraphBuilder, nullptr);
View.HairStrandsViewData.bIsValid = false;
}
}
}
void FHairStrandsViewStateData::Init()
{
// Voxel adaptive sizing
VoxelFeedbackBuffer = nullptr;
// Track if hair strands positions has changed
PositionsChangedDatas.SetNum(4);
for (FPositionChangedData& Data : PositionsChangedDatas)
{
Data.ReadbackBuffer = new FRHIGPUBufferReadback(TEXT("Hair.PositionsChangedReadback"));
Data.bHasPendingReadback = false;
}
}
void FHairStrandsViewStateData::Release()
{
// Voxel adaptive sizing
VoxelFeedbackBuffer = nullptr;
// Track if hair strands positions has changed
for (FPositionChangedData& Data : PositionsChangedDatas)
{
delete Data.ReadbackBuffer;
Data.ReadbackBuffer = nullptr;
Data.bHasPendingReadback = false;
}
PositionsChangedDatas.Empty();
}
void FHairStrandsViewStateData::EnqueuePositionsChanged(FRDGBuilder& GraphBuilder, FRDGBufferRef InBuffer)
{
for (FPositionChangedData& Data : PositionsChangedDatas)
{
if (!Data.bHasPendingReadback)
{
AddEnqueueCopyPass(GraphBuilder, Data.ReadbackBuffer, InBuffer, 4u);
Data.bHasPendingReadback = true;
break;
}
}
}
bool FHairStrandsViewStateData::ReadPositionsChanged()
{
for (FPositionChangedData& Data : PositionsChangedDatas)
{
if (Data.bHasPendingReadback && Data.ReadbackBuffer->IsReady())
{
const uint32 ReadData = *(uint32*)(Data.ReadbackBuffer->Lock(sizeof(uint32)));
Data.ReadbackBuffer->Unlock();
Data.bHasPendingReadback = false;
return ReadData > 0;
}
}
return false;
}
namespace HairStrands
{
TRDGUniformBufferRef<FHairStrandsViewUniformParameters> CreateDefaultHairStrandsViewUniformBuffer(FRDGBuilder& GraphBuilder, FViewInfo& View)
{
return InternalCreateHairStrandsViewUniformBuffer(GraphBuilder, nullptr);
}
TRDGUniformBufferRef<FHairStrandsViewUniformParameters> BindHairStrandsViewUniformParameters(const FViewInfo& View)
{
return View.HairStrandsViewData.UniformBuffer;
}
TRDGUniformBufferRef<FVirtualVoxelParameters> BindHairStrandsVoxelUniformParameters(const FViewInfo& View)
{
// Voxel uniform buffer exist only if the view has hair strands data
check(View.HairStrandsViewData.bIsValid && View.HairStrandsViewData.VirtualVoxelResources.IsValid());
return View.HairStrandsViewData.VirtualVoxelResources.UniformBuffer;
}
bool HasViewHairStrandsData(const FViewInfo& View)
{
return View.HairStrandsViewData.bIsValid;
}
bool HasViewHairStrandsVoxelData(const FViewInfo& View)
{
return View.HairStrandsViewData.bIsValid && View.HairStrandsViewData.VirtualVoxelResources.IsValid();
}
bool HasViewHairStrandsData(const TArray<FViewInfo>& Views)
{
for (const FViewInfo& View : Views)
{
if (View.HairStrandsViewData.bIsValid)
{
return true;
}
}
return false;
}
bool HasHairStrandsVisible(const TArray<FViewInfo>& Views)
{
for (const FViewInfo& View : Views)
{
if (View.HairStrandsMeshElements.Num() > 0)
{
return true;
}
}
return false;
}
bool HasHairCardsVisible(const TArray<FViewInfo>& Views)
{
for (const FViewInfo& View : Views)
{
if (View.HairCardsMeshElements.Num() > 0)
{
return true;
}
}
return false;
}
bool HasHairInstanceInScene(const FScene& Scene)
{
return Scene.HairStrandsSceneData.RegisteredProxies.Num() > 0;
}
void PostRender(FScene& Scene)
{
// Dellocate transient resourcse
if (Scene.HairStrandsSceneData.TransientResources)
{
*Scene.HairStrandsSceneData.TransientResources = FHairTransientResources();
}
}
} // HairStrands