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

651 lines
23 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
/*=============================================================================
HairStrandsInterface.h: Hair manager implementation.
=============================================================================*/
#include "HairStrandsInterface.h"
#include "HairStrandsRendering.h"
#include "HairStrandsData.h"
#include "DataDrivenShaderPlatformInfo.h"
#include "LightSceneProxy.h"
#include "Rendering/SkeletalMeshRenderData.h"
#include "Rendering/SkinWeightVertexBuffer.h"
#include "CommonRenderResources.h"
#include "Components/SkeletalMeshComponent.h"
#include "SkeletalRenderPublic.h"
#include "SceneRendering.h"
#include "SystemTextures.h"
#include "ShaderPrint.h"
#include "ScenePrivate.h"
DEFINE_LOG_CATEGORY_STATIC(LogHairRendering, Log, All);
static TAutoConsoleVariable<int32> CVarHairStrandsRaytracingEnable(
TEXT("r.HairStrands.Raytracing"), 1,
TEXT("Enable/Disable hair strands raytracing geometry. This is anopt-in option per groom asset/groom instance."),
ECVF_RenderThreadSafe | ECVF_Scalability);
static TAutoConsoleVariable<int32> CVarHairStrandsEnable(
TEXT("r.HairStrands.Strands"), 1,
TEXT("Enable/Disable hair strands rendering"),
ECVF_RenderThreadSafe | ECVF_Scalability);
static TAutoConsoleVariable<int32> CVarHairCardsEnable(
TEXT("r.HairStrands.Cards"), 1,
TEXT("Enable/Disable hair cards rendering. This variable needs to be turned on when the engine starts."),
ECVF_RenderThreadSafe | ECVF_Scalability);
static TAutoConsoleVariable<int32> CVarHairMeshesEnable(
TEXT("r.HairStrands.Meshes"), 1,
TEXT("Enable/Disable hair meshes rendering. This variable needs to be turned on when the engine starts."),
ECVF_RenderThreadSafe | ECVF_Scalability);
static TAutoConsoleVariable<int32> CVarHairStrandsBinding(
TEXT("r.HairStrands.Binding"), 1,
TEXT("Enable/Disable hair binding, i.e., hair attached to skeletal meshes."),
ECVF_RenderThreadSafe | ECVF_Scalability);
static TAutoConsoleVariable<int32> CVarHairStrandsSimulation(
TEXT("r.HairStrands.Simulation"), 1,
TEXT("Enable/disable hair simulation"),
ECVF_RenderThreadSafe | ECVF_Scalability);
static TAutoConsoleVariable<int32> CVarHairStrandsNonVisibleShadowCasting(
TEXT("r.HairStrands.Shadow.CastShadowWhenNonVisible"), 1,
TEXT("Enable shadow casting for hair strands even when culled out from the primary view"),
ECVF_RenderThreadSafe);
static TAutoConsoleVariable<float> CVarHairStrandsNonVisibleShadowCasting_CullDistance(
TEXT("r.HairStrands.Visibility.NonVisibleShadowCasting.CullDistance"), 2000,
TEXT("Cull distance at which shadow casting starts to be disabled for non-visible hair strands instances."),
ECVF_RenderThreadSafe);
static TAutoConsoleVariable<int32> CVarHairStrandsNonVisibleShadowCasting_Debug(
TEXT("r.HairStrands.Visibility.NonVisibleShadowCasting.Debug"), 0,
TEXT("Enable debug rendering for non-visible hair strands instance, casting shadow."),
ECVF_RenderThreadSafe);
static TAutoConsoleVariable<int32> CVarHairStrandsContinuousDecimationReordering(
TEXT("r.HairStrands.ContinuousDecimationReordering"), 0,
TEXT("Enable strand reordering to allow Continuous LOD. Experimental"),
ECVF_RenderThreadSafe | ECVF_ReadOnly);
static TAutoConsoleVariable<int32> CVarHairStrandsVisibilityComputeRaster(
TEXT("r.HairStrands.Visibility.ComputeRaster"), 0,
TEXT("Hair Visiblity uses raster compute. Experimental"),
ECVF_RenderThreadSafe);
static TAutoConsoleVariable<int32> CVarHairStrandsVisibilityComputeRaster_ContinuousLOD(
TEXT("r.HairStrands.Visibility.ComputeRaster.ContinuousLOD"), 1,
TEXT("Enable Continuos LOD when using compute rasterization. Experimental"),
ECVF_RenderThreadSafe);
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Import/export utils function for hair resources
void FRDGExternalBuffer::Release()
{
Buffer = nullptr;
SRV = nullptr;
UAV = nullptr;
}
FRDGImportedBuffer Register(FRDGBuilder& GraphBuilder, const FRDGExternalBuffer& In, ERDGImportedBufferFlags Flags, ERDGUnorderedAccessViewFlags UAVFlags)
{
FRDGImportedBuffer Out;
if (!In.Buffer)
{
return Out;
}
const uint32 uFlags = uint32(Flags);
Out.Buffer = GraphBuilder.RegisterExternalBuffer(In.Buffer);
if (In.Format != PF_Unknown)
{
if (uFlags & uint32(ERDGImportedBufferFlags::CreateSRV)) { Out.SRV = GraphBuilder.CreateSRV(Out.Buffer, In.Format); }
if (uFlags & uint32(ERDGImportedBufferFlags::CreateUAV)) { Out.UAV = GraphBuilder.CreateUAV(FRDGBufferUAVDesc(Out.Buffer, In.Format), UAVFlags); }
}
else
{
if (uFlags & uint32(ERDGImportedBufferFlags::CreateSRV)) { Out.SRV = GraphBuilder.CreateSRV(Out.Buffer); }
if (uFlags & uint32(ERDGImportedBufferFlags::CreateUAV)) { Out.UAV = GraphBuilder.CreateUAV(FRDGBufferUAVDesc(Out.Buffer), UAVFlags); }
}
return Out;
}
FRDGBufferSRVRef RegisterAsSRV(FRDGBuilder& GraphBuilder, const FRDGExternalBuffer& In)
{
if (!In.Buffer)
{
return nullptr;
}
FRDGBufferSRVRef Out = nullptr;
FRDGBufferRef Buffer = GraphBuilder.RegisterExternalBuffer(In.Buffer);
if (In.Format != PF_Unknown)
{
Out = GraphBuilder.CreateSRV(Buffer, In.Format);
}
else
{
Out = GraphBuilder.CreateSRV(Buffer);
}
return Out;
}
FRDGBufferUAVRef RegisterAsUAV(FRDGBuilder& GraphBuilder, const FRDGExternalBuffer& In, ERDGUnorderedAccessViewFlags Flags)
{
if (!In.Buffer)
{
return nullptr;
}
FRDGBufferUAVRef Out = nullptr;
FRDGBufferRef Buffer = GraphBuilder.RegisterExternalBuffer(In.Buffer);
if (In.Format != PF_Unknown)
{
Out = GraphBuilder.CreateUAV(FRDGBufferUAVDesc(Buffer, In.Format), Flags);
}
else
{
Out = GraphBuilder.CreateUAV(FRDGBufferUAVDesc(Buffer), Flags);
}
return Out;
}
bool IsHairRayTracingEnabled()
{
if (GIsRHIInitialized && !IsRunningCookCommandlet())
{
return IsRayTracingEnabled() && CVarHairStrandsRaytracingEnable.GetValueOnAnyThread();
}
else
{
return false;
}
}
bool IsHairStrandsSupported(EHairStrandsShaderType Type, EShaderPlatform Platform)
{
if (!IsGroomEnabled()) return false;
const bool Cards_Meshes_All = true;
switch (Type)
{
case EHairStrandsShaderType::Strands: return IsHairStrandsGeometrySupported(Platform);
case EHairStrandsShaderType::Cards: return Cards_Meshes_All;
case EHairStrandsShaderType::Meshes: return Cards_Meshes_All;
case EHairStrandsShaderType::Tool: return (IsD3DPlatform(Platform) || IsVulkanPlatform(Platform)) && IsPCPlatform(Platform) && IsFeatureLevelSupported(Platform, ERHIFeatureLevel::SM5);
case EHairStrandsShaderType::All: return Cards_Meshes_All;
}
return false;
}
bool IsHairStrandsEnabled(EHairStrandsShaderType Type, EShaderPlatform Platform)
{
const bool HairStrandsGlobalEnable = IsGroomEnabled();
if (!HairStrandsGlobalEnable) return false;
const int32 HairStrandsEnable = CVarHairStrandsEnable.GetValueOnAnyThread();
const int32 HairCardsEnable = CVarHairCardsEnable.GetValueOnAnyThread();
const int32 HairMeshesEnable = CVarHairMeshesEnable.GetValueOnAnyThread();
switch (Type)
{
case EHairStrandsShaderType::Strands: return HairStrandsEnable > 0 && (Platform != EShaderPlatform::SP_NumPlatforms ? IsHairStrandsGeometrySupported(Platform) : true);
case EHairStrandsShaderType::Cards: return HairCardsEnable > 0;
case EHairStrandsShaderType::Meshes: return HairMeshesEnable > 0;
#if PLATFORM_DESKTOP && PLATFORM_WINDOWS
case EHairStrandsShaderType::Tool: return (HairCardsEnable > 0 || HairMeshesEnable > 0 || HairStrandsEnable > 0);
#else
case EHairStrandsShaderType::Tool: return false;
#endif
case EHairStrandsShaderType::All : return HairStrandsGlobalEnable && (HairCardsEnable > 0 || HairMeshesEnable > 0 || HairStrandsEnable > 0);
}
return false;
}
bool IsHairStrandsBindingEnable()
{
return CVarHairStrandsBinding.GetValueOnAnyThread() > 0;
}
bool IsHairStrandsSimulationEnable()
{
return CVarHairStrandsSimulation.GetValueOnAnyThread() > 0;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// FHairTransientResources
FVector3f FHairTransientResources::GetTranslatedWorldOffsetCorrection(const FViewInfo& View) const
{
return FVector3f(View.ViewMatrices.GetPreViewTranslation() - TranslatedWorldOffsetView0);
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// FHairInstanceCullingResults
void FHairInstanceCullingResults::InitVisibility(uint32 InstanceCount)
{
InstancesVisibilityType.Init(0u, InstanceCount);
}
void FHairInstanceCullingResults::Add(const FViewInfo& View, const FHairStrandsInstance& Instance, EHairInstanceVisibilityType Flag)
{
if (InstancesVisibilityType.IsValidIndex(Instance.RegisteredIndex))
{
const uint32 ViewIndex = FMath::Min(View.SceneRendererPrimaryViewId, 7);
const uint32 ViewMask = 0xF << (ViewIndex * 4u);
const uint32 BitsToAdd = uint32(Flag) << (ViewIndex * 4u);
InstancesVisibilityType[Instance.RegisteredIndex] &= ~ViewMask;
InstancesVisibilityType[Instance.RegisteredIndex] |= BitsToAdd;
}
}
EHairInstanceVisibilityType FHairInstanceCullingResults::Is(const FViewInfo& View, const FHairStrandsInstance& Instance) const
{
EHairInstanceVisibilityType Out = EHairInstanceVisibilityType::NotVisible;
if (InstancesVisibilityType.IsValidIndex(Instance.RegisteredIndex))
{
const uint32 ViewIndex = FMath::Min(View.SceneRendererPrimaryViewId, 7);
const uint32 ViewBits = (InstancesVisibilityType[Instance.RegisteredIndex] >> (4u * ViewIndex)) & 0xF;
// const uint32 BitIndex = 31u - FMath::CountLeadingZeros(ViewBits);
Out = EHairInstanceVisibilityType(ViewBits);
}
return Out;
}
EHairInstanceVisibilityType FHairInstanceCullingResults::Is(const FSceneView& View, const FHairStrandsInstance& Instance) const
{
return Is(*(FViewInfo*)(&View), Instance);
}
bool FHairInstanceCullingResults::Is(const FViewInfo& View, const FHairStrandsInstance& Instance, EHairInstanceVisibilityType Flag) const
{
return Is(View, Instance) == Flag;
}
bool FHairInstanceCullingResults::IsVisibleInAnyViews(const FHairStrandsInstance& Instance) const
{
return InstancesVisibilityType.IsValidIndex(Instance.RegisteredIndex) && InstancesVisibilityType[Instance.RegisteredIndex] != 0;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
FHairGroupPublicData::FHairGroupPublicData(uint32 InGroupIndex, const FName& InOwnerName)
{
GroupIndex = InGroupIndex;
}
///////////////////////////////////////////////////////////////////////////////////////////////////
bool IsHairStrandsNonVisibleShadowCastingEnable()
{
return CVarHairStrandsNonVisibleShadowCasting.GetValueOnAnyThread() > 0;
}
bool IsHairStrandsVisibleInShadows(const FViewInfo& View, const FHairStrandsInstance& Instance)
{
const bool bDebugEnable = CVarHairStrandsNonVisibleShadowCasting_Debug.GetValueOnRenderThread() > 0;
FHairStrandsDebugData::FCullData* CullingData = nullptr;
if (bDebugEnable)
{
CullingData = const_cast<FHairStrandsDebugData::FCullData*>(&View.HairStrandsViewData.DebugData.CullData);
CullingData->bIsValid = true;
CullingData->ViewFrustum = View.ViewFrustum;
}
bool bIsVisibleInShadow = false;
if (const FHairGroupPublicData* HairData = Instance.GetHairData())
{
// Run simulation if the instance is either Strands geometry, have simulation, or has global interpolation enabled.
// This ensures that the groom is correctly updated if visible in shadows
const bool bNeedUpdate =
HairData->LODIndex >= 0 &&
(Instance.GetHairGeometry() == EHairGeometryType::Strands ||
HairData->IsSimulationEnable(HairData->LODIndex) ||
HairData->IsGlobalInterpolationEnable(HairData->LODIndex));
if (!bNeedUpdate)
{
return false;
}
const FBoxSphereBounds& Bounds = Instance.GetBounds();
{
// Local lights
for (const FLightSceneInfo* LightInfo : View.HairStrandsViewData.VisibleShadowCastingLights)
{
if (LightInfo->Proxy->AffectsBounds(Bounds))
{
bIsVisibleInShadow = true;
break;
}
}
// Directional lights
if (!bIsVisibleInShadow)
{
const float CullDistance = FMath::Max(0.f, CVarHairStrandsNonVisibleShadowCasting_CullDistance.GetValueOnAnyThread());
for (const FHairStrandsViewData::FDirectionalLightCullData& CullData : View.HairStrandsViewData.VisibleShadowCastingDirectionalLights)
{
// Transform frustum into light space
const FMatrix& WorldToLight = CullData.LightInfo->Proxy->GetWorldToLight();
// Transform groom bound into light space, and extend it along the light direction
FBoxSphereBounds BoundsInLightSpace = Bounds.TransformBy(WorldToLight);
BoundsInLightSpace.BoxExtent.X += CullDistance;
const bool bIntersect = CullData.ViewFrustumInLightSpace.IntersectBox(BoundsInLightSpace.Origin, BoundsInLightSpace.BoxExtent);
if (bDebugEnable)
{
FHairStrandsDebugData::FCullData::FLight& LightData = CullingData->DirectionalLights.AddDefaulted_GetRef();
LightData.WorldToLight = WorldToLight;
LightData.LightToWorld = CullData.LightInfo->Proxy->GetLightToWorld();
LightData.Center = FVector3f(CullData.LightInfo->Proxy->GetBoundingSphere().Center);
LightData.Extent = FVector3f(CullData.LightInfo->Proxy->GetBoundingSphere().W);
LightData.ViewFrustumInLightSpace = CullData.ViewFrustumInLightSpace;
LightData.InstanceBoundInLightSpace.Add({FVector3f(BoundsInLightSpace.GetBox().Min), FVector3f(BoundsInLightSpace.GetBox().Max)});
LightData.InstanceBoundInWorldSpace.Add({ FVector3f(Bounds.GetBox().Min), FVector3f(Bounds.GetBox().Max)});
LightData.InstanceIntersection.Add(bIntersect ? 1u : 0u);
}
// Ensure the extended groom bound interest the frustum
if (bIntersect)
{
bIsVisibleInShadow = true;
if (!bDebugEnable)
{
break;
}
}
}
}
}
}
return bIsVisibleInShadow;
}
bool IsHairStrandContinuousDecimationReorderingEnabled()
{
//NB this is a readonly cvar - and causes changes in platform data and runtime allocations
return CVarHairStrandsContinuousDecimationReordering.GetValueOnAnyThread() > 0;
}
bool IsHairVisibilityComputeRasterEnabled()
{
return CVarHairStrandsVisibilityComputeRaster.GetValueOnAnyThread() == 1;
}
bool IsHairVisibilityComputeRasterForwardEnabled(EShaderPlatform InPlatform)
{
return IsFeatureLevelSupported(InPlatform, ERHIFeatureLevel::SM6) && CVarHairStrandsVisibilityComputeRaster.GetValueOnAnyThread() == 2;
}
bool IsHairVisibilityComputeRasterContinuousLODEnabled()
{
return IsHairStrandContinuousDecimationReorderingEnabled() && IsHairVisibilityComputeRasterEnabled() && (CVarHairStrandsVisibilityComputeRaster_ContinuousLOD.GetValueOnAnyThread() > 0);
}
uint32 FHairGroupPublicData::GetActiveStrandsPointCount(bool bPrevious) const
{
return bPrevious ? ContinuousLODPreviousPointCount : ContinuousLODPointCount;
}
uint32 FHairGroupPublicData::GetActiveStrandsCurveCount(bool bPrevious) const
{
return bPrevious ? ContinuousLODPreviousCurveCount : ContinuousLODCurveCount;
}
float FHairGroupPublicData::GetActiveStrandsCoverageScale() const
{
return ContinuousLODCoverageScale;
}
float FHairGroupPublicData::GetActiveStrandsRadiusScale() const
{
return ContinuousLODRadiusScale;
}
///////////////////////////////////////////////////////////////////////////////////////////////////
// Bookmark API
THairStrandsBookmarkFunction GHairStrandsBookmarkFunction = nullptr;
void RegisterBookmarkFunction(THairStrandsBookmarkFunction Bookmark)
{
if (Bookmark)
{
GHairStrandsBookmarkFunction = Bookmark;
}
}
void RunHairStrandsBookmark(FRDGBuilder& GraphBuilder, EHairStrandsBookmark Bookmark, FHairStrandsBookmarkParameters& Parameters)
{
if (GHairStrandsBookmarkFunction)
{
GHairStrandsBookmarkFunction(&GraphBuilder, Bookmark, Parameters);
}
}
void RunHairStrandsBookmark(EHairStrandsBookmark Bookmark, FHairStrandsBookmarkParameters& Parameters)
{
if (GHairStrandsBookmarkFunction)
{
GHairStrandsBookmarkFunction(nullptr, Bookmark, Parameters);
}
}
void CullHairInstance(FScene* Scene, FViewInfo& View, FHairInstanceCullingResults& Out)
{
// Only compute visible instances when required as this is expensive.
{
const int32 ActiveInstanceCount = Scene->HairStrandsSceneData.RegisteredProxies.Num();
Out.InitVisibility(ActiveInstanceCount);
// 1. Strands - Add all visible strands instances
Out.VisibleInAnyViews_Strands.Reserve(View.HairStrandsMeshElements.Num());
for (const FMeshBatchAndRelevance& MeshBatch : View.HairStrandsMeshElements)
{
check(MeshBatch.PrimitiveSceneProxy && MeshBatch.PrimitiveSceneProxy->ShouldRenderInMainPass());
if (MeshBatch.Mesh && MeshBatch.Mesh->Elements.Num() > 0)
{
FHairGroupPublicData* HairData = HairStrands::GetHairData(MeshBatch.Mesh);
if (HairData && HairData->Instance)
{
if (!Out.IsVisibleInAnyViews(*HairData->Instance))
{
Out.VisibleInAnyViews_Strands.Add(HairData->Instance);
}
Out.Add(View, *HairData->Instance, EHairInstanceVisibilityType::StrandsPrimaryView);
}
}
}
// 2. Cards/Meshes - Add all visible cards instances
Out.VisibleInAnyViews_CardsOrMeshes_Primary.Reserve(View.HairCardsMeshElements.Num());
for (const FMeshBatchAndRelevance& MeshBatch : View.HairCardsMeshElements)
{
check(MeshBatch.PrimitiveSceneProxy && MeshBatch.PrimitiveSceneProxy->ShouldRenderInMainPass());
if (MeshBatch.Mesh && MeshBatch.Mesh->Elements.Num() > 0)
{
FHairGroupPublicData* HairData = HairStrands::GetHairData(MeshBatch.Mesh);
if (HairData && HairData->Instance)
{
if (!Out.IsVisibleInAnyViews(*HairData->Instance))
{
Out.VisibleInAnyViews_CardsOrMeshes_Primary.Add(HairData->Instance);
}
Out.Add(View, *HairData->Instance, EHairInstanceVisibilityType::CardsOrMeshesPrimaryView);
}
}
}
}
}
void CullHairInstanceShadow(FScene* Scene, TArray<FViewInfo>& Views, FHairInstanceCullingResults& Out)
{
const int32 ActiveInstanceCount = Scene->HairStrandsSceneData.RegisteredProxies.Num();
for (FHairStrandsInstance* Instance : Scene->HairStrandsSceneData.RegisteredProxies)
{
// 2. Strands - Add all instances non-visible primary view(s) but visible in shadow view(s)
const bool bStrands = Instance->GetHairGeometry() == EHairGeometryType::Strands;
const bool bCardsOrMeshes = Instance->GetHairGeometry() == EHairGeometryType::Cards || Instance->GetHairGeometry() == EHairGeometryType::Meshes;
const bool bCompatible = bStrands || bCardsOrMeshes;
if (bCompatible)
{
for (const FViewInfo& View : Views)
{
if (Out.Is(View, *Instance) == EHairInstanceVisibilityType::NotVisible && IsHairStrandsVisibleInShadows(View, *Instance))
{
if (bStrands)
{
if (!Out.IsVisibleInAnyViews(*Instance))
{
Out.VisibleInAnyViews_Strands.Add(Instance);
}
Out.Add(View, *Instance, EHairInstanceVisibilityType::StrandsShadowView);
}
else if (bCardsOrMeshes)
{
if (!Out.IsVisibleInAnyViews(*Instance))
{
Out.VisibleInAnyViews_CardsOrMeshes_Shadow.Add(Instance);
}
Out.Add(View, *Instance, EHairInstanceVisibilityType::CardsOrMeshesShadowView);
}
}
}
}
}
}
void CreateHairStrandsBookmarkParameters(FScene* Scene, FViewInfo& View, FHairStrandsBookmarkParameters& Out, bool bComputeVisibleInstances)
{
// Only compute visible instances when required as this is expensive.
if (bComputeVisibleInstances)
{
CullHairInstance(Scene, View, Out.CullingResults);
}
Out.ShaderPrintData = ShaderPrint::IsEnabled(View.ShaderPrintData) ? &View.ShaderPrintData : nullptr;
Out.ShaderMap = View.ShaderMap;
Out.Instances = &Scene->HairStrandsSceneData.RegisteredProxies;
Out.View = &View;
Out.ViewRect = View.ViewRect;
Out.ViewUniqueID = View.ViewState ? View.ViewState->UniqueID : ~0;
Out.SceneColorTexture = nullptr;
Out.SceneDepthTexture = nullptr;
Out.Scene = Scene;
Out.TransientResources = Scene->HairStrandsSceneData.TransientResources;
}
void UpdateHairStrandsBookmarkParameters(FScene* Scene, TArray<FViewInfo>& Views, FHairStrandsBookmarkParameters& Out)
{
if (Views.Num() == 0)
{
return;
}
if (IsHairStrandsNonVisibleShadowCastingEnable())
{
CullHairInstanceShadow(Scene, Views, Out.CullingResults);
}
}
void CreateHairStrandsBookmarkParameters(FScene* Scene, TArray<FViewInfo>& Views, TConstArrayView<FViewInfo*> AllViews, FHairStrandsBookmarkParameters& Out, bool bComputeVisibleInstances)
{
CreateHairStrandsBookmarkParameters(Scene, Views[0], Out, bComputeVisibleInstances);
Out.AllViews.Reserve(AllViews.Num());
for (FViewInfo* View : AllViews)
{
Out.AllViews.Emplace(View);
}
}
namespace HairStrands
{
bool IsHairStrandsVF(const FMeshBatch* Mesh)
{
if (Mesh)
{
static const FHashedName& VFTypeRef = FVertexFactoryType::GetVFByName(TEXT("FHairStrandsVertexFactory"))->GetHashedName();
const FHashedName& VFType = Mesh->VertexFactory->GetType()->GetHashedName();
return VFType == VFTypeRef;
}
return false;
}
bool IsHairCardsVF(const FMeshBatch* Mesh)
{
if (Mesh)
{
static const FHashedName& VFTypeRef = FVertexFactoryType::GetVFByName(TEXT("FHairCardsVertexFactory"))->GetHashedName();
const FHashedName& VFType = Mesh->VertexFactory->GetType()->GetHashedName();
return VFType == VFTypeRef;
}
return false;
}
bool IsHairCompatible(const FMeshBatch* Mesh)
{
return IsHairStrandsVF(Mesh) || IsHairCardsVF(Mesh);
}
bool IsHairVisible(const FMeshBatchAndRelevance& MeshBatch, bool bCheckLengthScale)
{
if (MeshBatch.Mesh && MeshBatch.PrimitiveSceneProxy && MeshBatch.PrimitiveSceneProxy->ShouldRenderInMainPass())
{
const FHairGroupPublicData* Data = HairStrands::GetHairData(MeshBatch.Mesh);
switch (Data->VFInput.GeometryType)
{
case EHairGeometryType::Strands: return bCheckLengthScale ? Data->VFInput.Strands.Common.LengthScale > 0 : true;
case EHairGeometryType::Cards: return true;
case EHairGeometryType::Meshes: return true;
}
}
return false;
}
FHairGroupPublicData* GetHairData(const FMeshBatch* Mesh)
{
return reinterpret_cast<FHairGroupPublicData*>(Mesh->Elements[0].VertexFactoryUserData);
}
void AddVisibleShadowCastingLight(const FScene& Scene, TArray<FViewInfo>& Views, const FLightSceneInfo* LightSceneInfo)
{
const uint8 LightType = LightSceneInfo->Proxy->GetLightType();
for (FViewInfo& View : Views)
{
// If any hair data are registered, track which lights are visible so that hair strands can cast shadow even if not visible in primary view
//
// Actual intersection test is done in IsHairStrandsVisibleInShadows():
// * For local lights, shadow casting is determined based on light influence radius
// * For directional light, view frustum and hair instance bounds are transformed in light space to determined potential intersections
if (Scene.HairStrandsSceneData.RegisteredProxies.Num() > 0)
{
if (LightType == ELightComponentType::LightType_Directional)
{
FHairStrandsViewData::FDirectionalLightCullData& Data = View.HairStrandsViewData.VisibleShadowCastingDirectionalLights.AddDefaulted_GetRef();
// Transform view frustum into light space
const FMatrix& WorldToLight = LightSceneInfo->Proxy->GetWorldToLight();
const uint32 PlaneCount = View.ViewFrustum.Planes.Num();
Data.ViewFrustumInLightSpace.Planes.SetNum(PlaneCount);
for (uint32 PlaneIt = 0; PlaneIt < PlaneCount; ++PlaneIt)
{
Data.ViewFrustumInLightSpace.Planes[PlaneIt] = View.ViewFrustum.Planes[PlaneIt].TransformBy(WorldToLight);
}
Data.ViewFrustumInLightSpace.Init();
Data.LightInfo = LightSceneInfo;
}
else
{
View.HairStrandsViewData.VisibleShadowCastingLights.Add(LightSceneInfo);
}
}
}
}
} // namespace HairStrands