// Copyright Epic Games, Inc. All Rights Reserved. #include "GroomDataflowRendering.h" #include "GroomCollectionFacades.h" #include "Engine/SkeletalMesh.h" #include "GeometryCollection/GeometryCollection.h" #include "GeometryCollection/Facades/CollectionRenderingFacade.h" namespace UE::Groom { int32 GGroomDataflowDebugDraw = 0; static FAutoConsoleVariableRef CVarGroomDataflowDebugDraw( TEXT("p.Groom.Dataflow.DebugDraw"), GGroomDataflowDebugDraw, TEXT("Type of information we want to draw for debug (0-DefaultColor, 1-SkinWeights, 2-GuidesLods)") ); FString GGroomDataflowBoneName = TEXT(""); static FAutoConsoleVariableRef CVarGroomDataflowBoneName( TEXT("p.Groom.Dataflow.BoneName"), GGroomDataflowBoneName, TEXT("Bone name we want to visualize the skin weights") ); int32 GGroomDataflowGuidesLod = 0; static FAutoConsoleVariableRef CVarGroomDataflowGuidesLod( TEXT("p.Groom.Dataflow.GuidesLod"), GGroomDataflowGuidesLod, TEXT("Guides Lod we want to display") ); int32 GGroomDataflowStrandsLod = 0; static FAutoConsoleVariableRef CVarGroomDataflowStrandsLod( TEXT("p.Groom.Dataflow.StrandsLod"), GGroomDataflowStrandsLod, TEXT("Strands Lod we want to display") ); float GGroomDataflowRenderingThickness = 0.5f; static FAutoConsoleVariableRef CVarRestGuidesRenderingThickness( TEXT("p.Groom.Dataflow.RenderingThickness"), GGroomDataflowRenderingThickness, TEXT("Thickness used to render the groom in dataflow editor.") ); FORCEINLINE void BuildVertexPositions(const int32 PointIndex, const FVector3f& PointPosition, const FVector3f& SideVector, const float RenderThickness, TArray& VertexPositions) { const int32 VertexIndex = 2 * PointIndex; const FVector3f PointPositionA = PointPosition + SideVector * RenderThickness; const FVector3f PointPositionB = PointPosition - SideVector * RenderThickness; VertexPositions[VertexIndex] = PointPositionA; VertexPositions[VertexIndex+1] = PointPositionB; } FORCEINLINE void BuildFaceVertices(const int32 PointIndex, const int32 CurveIndex, TArray& FaceVertices) { const int32 VertexIndex = 2 * PointIndex; const int32 FaceIndex = 2 * (PointIndex - CurveIndex); FaceVertices[FaceIndex] = FIntVector(VertexIndex, VertexIndex + 1, VertexIndex + 3); FaceVertices[FaceIndex+1] = FIntVector(VertexIndex, VertexIndex + 3, VertexIndex + 2); } FORCEINLINE void BuildVertexNormals(const int32 PointIndex, const FVector3f& EdgeNormal, TArray& VertexNormals) { const int32 VertexIndex = 2 * PointIndex; VertexNormals[VertexIndex] = EdgeNormal; VertexNormals[VertexIndex+1] = EdgeNormal; } template FORCEINLINE void BuildRenderingDatas(const FacadeType& GroomFacade, TArray& VertexPositions, TArray& VertexNormals, TArray& FaceVertices) { const int32 NumPoints = GroomFacade.GetNumPoints(); const int32 NumEdges = GroomFacade.GetNumEdges(); const int32 NumCurves = GroomFacade.GetNumCurves(); VertexPositions.SetNum(NumPoints * 2); VertexNormals.SetNum(NumPoints * 2); FaceVertices.SetNum(NumEdges * 2); int32 PointOffset = 0; for(int32 CurveIndex = 0; CurveIndex < NumCurves; ++CurveIndex) { for (int32 PointIndex = PointOffset, PointEnd = GroomFacade.GetCurvePointOffsets()[CurveIndex]; PointIndex < PointEnd; ++PointIndex) { // Build 2 vertices for each points const int32 EdgeIndex = (PointIndex == (PointEnd-1)) ? PointIndex-CurveIndex-1 : PointIndex-CurveIndex; const FVector3f SideVector = GroomFacade.GetEdgeRestOrientations()[EdgeIndex].GetAxisX(); BuildVertexPositions(PointIndex, GroomFacade.GetPointRestPositions()[PointIndex], SideVector, GGroomDataflowRenderingThickness, VertexPositions); const FVector3f NormalVector = GroomFacade.GetEdgeRestOrientations()[EdgeIndex].GetAxisZ(); BuildVertexNormals(PointIndex, NormalVector, VertexNormals); // Build 2 faces for each edges perpendicular to the up-vector curve if(PointIndex < (PointEnd-1)) { BuildFaceVertices(PointIndex, CurveIndex, FaceVertices); } } PointOffset = GroomFacade.GetCurvePointOffsets()[CurveIndex]; } } template static void BuildRenderingGroups(const FacadeType& GroomFacade, TArray>& GroupVertexPositions, TArray>& GroupVertexNormals, TArray>& GroupFaceVertices, const FString& GroupAttribute) { const int32 NumCurves = GroomFacade.GetNumCurves(); const FManagedArrayCollection& GroomCollection = GroomFacade.GetManagedArrayCollection(); if(GroomCollection.HasAttribute(FName(GroupAttribute), FacadeType::CurvesGroup)) { const TManagedArray& CurveGroups = GroomCollection.GetAttribute(FName(GroupAttribute), FacadeType::CurvesGroup); int32 NumGroups = INDEX_NONE; for(int32 CurveIndex = 0; CurveIndex < NumCurves; ++CurveIndex) { NumGroups = FMath::Max(NumGroups, CurveGroups[CurveIndex]); } ++NumGroups; if(NumGroups > 0) { GroupVertexPositions.SetNum(NumGroups); GroupVertexNormals.SetNum(NumGroups); GroupFaceVertices.SetNum(NumGroups); TArray GroupsPoints; GroupsPoints.Init(0, NumGroups); TArray GroupsCurves; GroupsCurves.Init(0, NumGroups); int32 PointOffset = 0; for(int32 CurveIndex = 0; CurveIndex < NumCurves; ++CurveIndex) { const int32 GroupIndex = CurveGroups[CurveIndex]; if(GroupsPoints.IsValidIndex(GroupIndex) && GroupsCurves.IsValidIndex(GroupIndex)) { const int32 CurvePoints = GroomFacade.GetCurvePointOffsets()[CurveIndex] - PointOffset; GroupsPoints[GroupIndex] += CurvePoints; ++GroupsCurves[GroupIndex]; } PointOffset = GroomFacade.GetCurvePointOffsets()[CurveIndex]; } for(int32 GroupIndex = 0; GroupIndex < NumGroups; ++GroupIndex) { GroupVertexPositions[GroupIndex].SetNum(GroupsPoints[GroupIndex] * 2); GroupVertexNormals[GroupIndex].SetNum(GroupsPoints[GroupIndex] * 2); GroupFaceVertices[GroupIndex].SetNum((GroupsPoints[GroupIndex]-GroupsCurves[GroupIndex]) * 2); } GroupsPoints.Init(0, NumGroups); GroupsCurves.Init(0, NumGroups); PointOffset = 0; for(int32 CurveIndex = 0; CurveIndex < NumCurves; ++CurveIndex) { const int32 GroupIndex = CurveGroups[CurveIndex]; if (GroupsPoints.IsValidIndex(GroupIndex) && GroupsCurves.IsValidIndex(GroupIndex)) { TArray& VertexPositions = GroupVertexPositions[GroupIndex]; TArray& VertexNormals = GroupVertexNormals[GroupIndex]; TArray& FaceVertices = GroupFaceVertices[GroupIndex]; for (int32 PointIndex = PointOffset, PointEnd = GroomFacade.GetCurvePointOffsets()[CurveIndex]; PointIndex < PointEnd; ++PointIndex) { // Build 2 vertices for each points const int32 EdgeIndex = (PointIndex == (PointEnd-1)) ? PointIndex-CurveIndex-1 : PointIndex-CurveIndex; const FVector3f SideVector = GroomFacade.GetEdgeRestOrientations()[EdgeIndex].GetAxisX(); BuildVertexPositions(GroupsPoints[GroupIndex], GroomFacade.GetPointRestPositions()[PointIndex], SideVector, GGroomDataflowRenderingThickness, VertexPositions); const FVector3f NormalVector = GroomFacade.GetEdgeRestOrientations()[EdgeIndex].GetAxisZ(); BuildVertexNormals(GroupsPoints[GroupIndex], NormalVector, VertexNormals); // Build 2 faces for each edges perpendicular to the up-vector curve if(PointIndex < (PointEnd-1)) { BuildFaceVertices(GroupsPoints[GroupIndex], GroupsCurves[GroupIndex], FaceVertices); } ++GroupsPoints[GroupIndex]; } ++GroupsCurves[GroupIndex]; } PointOffset = GroomFacade.GetCurvePointOffsets()[CurveIndex]; } } } } template FORCEINLINE void BuildGeometryDatas(const FacadeType& GroomFacade, TArray& VertexPositions, TArray& FaceVertices, TArray& ObjectFaceOffsets, TArray& ObjectVertexOffsets) { TArray RenderingPositions, RenderingNormals; TArray RenderingVertices; BuildRenderingDatas(GroomFacade, RenderingPositions, RenderingNormals, RenderingVertices); VertexPositions.SetNum(RenderingPositions.Num() * 3); FaceVertices.SetNum(RenderingVertices.Num() * 3); int32 VertexIndex = 0; for(const FVector3f& VertexPosition : RenderingPositions) { VertexPositions[VertexIndex++] = VertexPosition.X; VertexPositions[VertexIndex++] = VertexPosition.Y; VertexPositions[VertexIndex++] = VertexPosition.Z; } int32 FaceIndex = 0; for(const FIntVector& FaceVertex : RenderingVertices) { FaceVertices[FaceIndex++] = FaceVertex.X; FaceVertices[FaceIndex++] = FaceVertex.Y; FaceVertices[FaceIndex++] = FaceVertex.Z; } const int32 NumObjects = GroomFacade.GetNumObjects(); ObjectFaceOffsets.SetNum(NumObjects); ObjectVertexOffsets.SetNum(NumObjects); for(int32 ObjectIndex = 0; ObjectIndex < NumObjects; ++ObjectIndex) { const int32 CurveOffset = GroomFacade.GetObjectCurveOffsets()[ObjectIndex]-1; ObjectVertexOffsets[ObjectIndex] = 2 * GroomFacade.GetCurvePointOffsets()[CurveOffset]; ObjectFaceOffsets[ObjectIndex] = ObjectVertexOffsets[ObjectIndex] - 2 * CurveOffset; } } template FORCEINLINE void SetupGeometryCollection(const FacadeType& GroomFacade, FGeometryCollection& GeometryCollection) { TArray RawVertexPositions; TArray RawFaceIndices; TArray ObjectFaceOffsets; TArray ObjectVertexOffsets; BuildGeometryDatas(GroomFacade, RawVertexPositions, RawFaceIndices, ObjectFaceOffsets, ObjectVertexOffsets); FGeometryCollection::Init(&GeometryCollection, RawVertexPositions, RawFaceIndices, false); // add a material section TManagedArray& Sections = GeometryCollection.Sections; GeometryCollection.Resize(GroomFacade.GetNumObjects(), FGeometryCollection::MaterialGroup); int32 FaceOffset = 0; int32 VertexOffset = 0; for(int32 SectionIndex = 0; SectionIndex < GroomFacade.GetNumObjects(); ++SectionIndex) { Sections[SectionIndex].MaterialID = SectionIndex; Sections[SectionIndex].FirstIndex = FaceOffset; Sections[SectionIndex].NumTriangles = ObjectFaceOffsets[SectionIndex] - FaceOffset; Sections[SectionIndex].MinVertexIndex = VertexOffset; Sections[SectionIndex].MaxVertexIndex = ObjectVertexOffsets[SectionIndex] - 1; FaceOffset = ObjectFaceOffsets[SectionIndex]; VertexOffset = ObjectVertexOffsets[SectionIndex]; } } template FORCEINLINE void RenderGroomCollection(GeometryCollection::Facades::FRenderingFacade& RenderCollection, const Dataflow::FGraphRenderingState& State, const TFunction&)>& ColorLambda) { if (State.GetRenderOutputs().Num()) { const FManagedArrayCollection Default; checkf(State.GetRenderOutputs().Num() == 1, TEXT("Expected FGraphRenderingState object to have one render output")); const FName PrimaryOutput = State.GetRenderOutputs()[0]; const FManagedArrayCollection& Collection = State.GetValue(PrimaryOutput, Default); const FacadeType GroomFacade(Collection); if (GroomFacade.IsValid()) { TArray VertexPositions; TArray VertexNormals; TArray FaceIndices; if (State.GetViewMode().GetName() == Dataflow::FDataflowConstruction3DViewMode::Name) { BuildRenderingDatas(GroomFacade, VertexPositions, VertexNormals, FaceIndices); } else { checkf(false, TEXT("Invalid View Mode for FClothCollection rendering")); } TArray Colors; Colors.Init(FLinearColor(0.2,0.6,1.0), VertexPositions.Num()); ColorLambda(GroomFacade, Colors); const FString GeometryName = GroomFacade.GetObjectGroupNames().Last(); const int32 GeometryIndex = RenderCollection.StartGeometryGroup(GeometryName); RenderCollection.AddSurface(MoveTemp(VertexPositions), MoveTemp(FaceIndices), MoveTemp(VertexNormals), MoveTemp(Colors)); RenderCollection.EndGeometryGroup(GeometryIndex); } } } template static void RenderGroupCollection(GeometryCollection::Facades::FRenderingFacade& RenderCollection, const Dataflow::FGraphRenderingState& State, const TFunction& GroupLambda) { if (State.GetRenderOutputs().Num()) { const FManagedArrayCollection Default; checkf(State.GetRenderOutputs().Num() == 1, TEXT("Expected FGraphRenderingState object to have one render output")); const FName PrimaryOutput = State.GetRenderOutputs()[0]; const FManagedArrayCollection& Collection = State.GetValue(PrimaryOutput, Default); const FacadeType GroomFacade(Collection); if (GroomFacade.IsValid()) { FString GroupAttribute, GroupName; const int32 NumGroups = GroupLambda(GroomFacade, GroupAttribute, GroupName); TArray> VertexPositions; TArray> VertexNormals; TArray> FaceIndices; if (State.GetViewMode().GetName() == Dataflow::FDataflowConstruction3DViewMode::Name) { BuildRenderingGroups(GroomFacade, VertexPositions, VertexNormals, FaceIndices, GroupAttribute); } else { UE_LOG(LogTemp, Error, TEXT("Invalid View Mode for Groom dataflow rendering")); } if(VertexPositions.Num() == NumGroups && VertexNormals.Num() == NumGroups && FaceIndices.Num() == NumGroups) { for (int32 GroupIndex = 0; GroupIndex < NumGroups; ++GroupIndex) { const FLinearColor GroupColor = FLinearColor::IntToDistinctColor(GroupIndex, 0.75f, 1.0f, 90.0f); TArray VertexColors; VertexColors.Init(GroupColor, VertexPositions[GroupIndex].Num()); const FString GeometryName = GroomFacade.GetObjectGroupNames().Last() + TEXT("_") + GroupName + TEXT("_") + FString::FromInt(GroupIndex); const int32 GeometryIndex = RenderCollection.StartGeometryGroup(GeometryName); RenderCollection.AddSurface(MoveTemp(VertexPositions[GroupIndex]), MoveTemp(FaceIndices[GroupIndex]), MoveTemp(VertexNormals[GroupIndex]), MoveTemp(VertexColors)); RenderCollection.EndGeometryGroup(GeometryIndex); } } } } } Dataflow::FRenderKey FGroomGuidesRenderingCallbacks::RenderKey = { "GuidesRender", FName("FGroomCollection") }; void RenderBoneWeights(const FGroomGuidesFacade& GuidesFacade, TArray& VertexColors) { if(GuidesFacade.GetManagedArrayCollection().HasAttribute( UE::Groom::FGroomGuidesFacade::ObjectSkeletalMeshesAttribute, UE::Groom::FGroomGuidesFacade::ObjectsGroup)) { const TManagedArray>& ObjectSkeletalMeshes = GuidesFacade.GetManagedArrayCollection().GetAttribute>( UE::Groom::FGroomGuidesFacade::ObjectSkeletalMeshesAttribute, UE::Groom::FGroomGuidesFacade::ObjectsGroup); for(int32 PointIndex = 0, PointEnd = GuidesFacade.GetNumPoints(); PointIndex < PointEnd; ++PointIndex) { const FIntVector4& BoneIndices = GuidesFacade.GetPointBoneIndices(PointIndex); const FVector4f& BoneWeights = GuidesFacade.GetPointBoneWeights(PointIndex); const int32 CurveIndex = GuidesFacade.GetPointCurveIndices()[PointIndex]; const int32 ObjectIndex = GuidesFacade.GetCurveObjectIndices()[CurveIndex]; if(USkeletalMesh* SkeletalMesh = Cast(ObjectSkeletalMeshes[ObjectIndex])) { for(int32 BoneIndex = 0, BoneEnd = BoneIndices.Num(); BoneIndex < BoneEnd; ++BoneIndex) { const int32 SkeletonBone = BoneIndices[BoneIndex]; if(SkeletonBone != INDEX_NONE) { const FName BoneName = SkeletalMesh->GetRefSkeleton().GetRawRefBoneInfo()[SkeletonBone].Name; if(BoneName == GGroomDataflowBoneName) { VertexColors[2*PointIndex] = FLinearColor::LerpUsingHSV( FLinearColor::Black, FLinearColor::Yellow, BoneWeights[BoneIndex]); VertexColors[2*PointIndex+1] = VertexColors[2*PointIndex]; } } } } } } } void RenderGuidesLods(const FGroomGuidesFacade& GuidesFacade, TArray& VertexColors) { const TArray& ParentIndices = GuidesFacade.GetCurveParentIndices(); const TArray& LodIndices = GuidesFacade.GetCurveLodIndices(); TArray LodColors; LodColors.Init(FLinearColor::Black, GuidesFacade.GetNumCurves()); for(int32 CurveIndex = 0, CurveEnd = GuidesFacade.GetNumCurves(); CurveIndex < CurveEnd; ++CurveIndex) { if(LodIndices[CurveIndex] >= GGroomDataflowGuidesLod) { LodColors[CurveIndex] = FLinearColor::IntToDistinctColor(CurveIndex, 0.75f, 1.0f, 90.0f); } } for(int32 CurveIndex = 0, CurveEnd = GuidesFacade.GetNumCurves(); CurveIndex < CurveEnd; ++CurveIndex) { if((LodIndices[CurveIndex] != INDEX_NONE) && (LodIndices[CurveIndex] < GGroomDataflowGuidesLod)) { int32 ParentIndex = CurveIndex; int32 ParentLod = LodIndices[ParentIndex]; while((ParentLod < GGroomDataflowGuidesLod) && ParentIndex != INDEX_NONE) { ParentIndex = ParentIndices[ParentIndex]; ParentLod = LodIndices[ParentIndex]; } LodColors[CurveIndex] = LodColors[ParentIndex]; } } for(int32 PointIndex = 0, PointEnd = GuidesFacade.GetNumPoints(); PointIndex < PointEnd; ++PointIndex) { const int32 CurveIndex = GuidesFacade.GetPointCurveIndices()[PointIndex]; VertexColors[2*PointIndex] = LodColors[CurveIndex]; VertexColors[2*PointIndex+1] = LodColors[CurveIndex]; } } void FGroomGuidesRenderingCallbacks::Render(GeometryCollection::Facades::FRenderingFacade& RenderCollection, const Dataflow::FGraphRenderingState& State) { RenderGroupCollection(RenderCollection, State, [this](const FGroomGuidesFacade& GuidesFacade, FString& GroupAttribute, FString& GroupName) -> int32 { return GetGroupAttribute(GuidesFacade, GroupAttribute, GroupName); }); } int32 FGroomGuidesRenderingCallbacks::GetGroupAttribute(const FGroomGuidesFacade& StrandsFacade, FString& GroupAttribute, FString& GroupName) const { GroupAttribute = FGroomGuidesFacade::CurveObjectIndicesAttribute.ToString(); GroupName = FString("Group"); return StrandsFacade.GetNumObjects(); } void FGroomGuidesRenderingCallbacks::ComputeVertexColors(const FGroomGuidesFacade& GuidesFacade, TArray& VertexColors) const { if(GGroomDataflowDebugDraw == 1) { RenderBoneWeights(GuidesFacade, VertexColors); } if(GGroomDataflowDebugDraw == 2) { RenderGuidesLods(GuidesFacade, VertexColors); } } Dataflow::FRenderKey FGroomStrandsRenderingCallbacks::RenderKey = { "StrandsRender", FName("FGroomCollection") }; void FGroomStrandsRenderingCallbacks::Render(GeometryCollection::Facades::FRenderingFacade& RenderCollection, const Dataflow::FGraphRenderingState& State) { RenderGroupCollection(RenderCollection, State, [this](const FGroomStrandsFacade& StrandsFacade, FString& GroupAttribute, FString& GroupName) -> int32 { return GetGroupAttribute(StrandsFacade, GroupAttribute, GroupName); }); } int32 FGroomStrandsRenderingCallbacks::GetGroupAttribute(const FGroomStrandsFacade& StrandsFacade, FString& GroupAttribute, FString& GroupName) const { GroupAttribute = FGroomStrandsFacade::CurveObjectIndicesAttribute.ToString(); GroupName = FString("Group"); return StrandsFacade.GetNumObjects(); } void FGroomStrandsRenderingCallbacks::ComputeVertexColors(const FGroomStrandsFacade& StrandsFacade, TArray& VertexColors) const {} void RegisterRenderingCallbacks() { Dataflow::FRenderingFactory::GetInstance()->RegisterCallbacks(MakeUnique()); Dataflow::FRenderingFactory::GetInstance()->RegisterCallbacks(MakeUnique()); } void DeregisterRenderingCallbacks() { Dataflow::FRenderingFactory::GetInstance()->DeregisterCallbacks(FGroomGuidesRenderingCallbacks::RenderKey); Dataflow::FRenderingFactory::GetInstance()->DeregisterCallbacks(FGroomStrandsRenderingCallbacks::RenderKey); } }