// Copyright Epic Games, Inc. All Rights Reserved. #include "Drawing/MeshWireframeComponent.h" #include "Engine/CollisionProfile.h" #include "LocalVertexFactory.h" #include "MaterialDomain.h" #include "MaterialShared.h" #include "Materials/MaterialInterface.h" #include "Materials/Material.h" #include "PrimitiveSceneProxy.h" #include "PrimitiveViewRelevance.h" #include "DynamicMeshBuilder.h" #include "SceneInterface.h" #include "StaticMeshResources.h" #include "IndexTypes.h" #include "Async/ParallelFor.h" #include UE_INLINE_GENERATED_CPP_BY_NAME(MeshWireframeComponent) using UE::Geometry::FIndex4i; struct FWireframeLinesMeshBatchData { FWireframeLinesMeshBatchData() : MaterialProxy(nullptr) {} FMaterialRenderProxy* MaterialProxy; int32 StartIndex; int32 NumPrimitives; int32 MinVertexIndex; int32 MaxVertexIndex; }; /** Class for the MeshWireframeComponent data passed to the render thread. */ class FMeshWireframeSceneProxy final : public FPrimitiveSceneProxy { public: FMeshWireframeSceneProxy(UMeshWireframeComponent* Component, const IMeshWireframeSource* WireSource) : FPrimitiveSceneProxy(Component), MaterialRelevance(Component->GetMaterialRelevance(GetScene().GetFeatureLevel())), VertexFactory(GetScene().GetFeatureLevel(), "FPointSetSceneProxy") { TRACE_CPUPROFILER_EVENT_SCOPE(FMeshWireframeSceneProxy); if (!ensure(WireSource && WireSource->IsValid())) return; if (WireSource->GetEdgeCount() <= 0) return; // Create local copies to avoid repeated indirect look-up through the Component pointer in loops. const bool bEnableWireframe = Component->bEnableWireframe; const bool bEnableBoundaryEdges = Component->bEnableBoundaryEdges; const bool bEnableUVSeams = Component->bEnableUVSeams; const bool bEnableNormalSeams = Component->bEnableNormalSeams; const bool bEnableTangentSeams = Component->bEnableTangentSeams; const bool bEnableColorSeams = Component->bEnableColorSeams; // Setup edge batches to process in parallel. const int32 NumSourceEdges = WireSource->GetMaxEdgeIndex(); constexpr int32 BatchSize = 16384; const int32 NumBatches = FMath::DivideAndRoundUp(NumSourceEdges, BatchSize); struct EdgeBatch { int32 Offset; TArray Edges; }; TArray VisibleEdgeBatches; VisibleEdgeBatches.SetNum(NumBatches); // count visible edges and remap so we can build in parallel below int32 NumVisibleEdges = 0; ParallelFor(NumBatches, [&](int32 BatchIndex) { TArray& VisibleEdgeBatch = VisibleEdgeBatches[BatchIndex].Edges; const int32 CurrentBatchSize = (BatchIndex < NumBatches - 1) || (NumSourceEdges % BatchSize == 0) ? BatchSize : NumSourceEdges % BatchSize; VisibleEdgeBatch.Reserve(CurrentBatchSize); for (int32 EdgeIndex = BatchIndex * BatchSize, BatchEnd = BatchIndex * BatchSize + CurrentBatchSize; EdgeIndex < BatchEnd; ++EdgeIndex) { if (WireSource->IsEdge(EdgeIndex) == false) continue; int32 VertIndexA, VertIndexB; IMeshWireframeSource::EMeshEdgeType EdgeType; WireSource->GetEdge(EdgeIndex, VertIndexA, VertIndexB, EdgeType); if (bEnableWireframe || (bEnableBoundaryEdges && (static_cast(EdgeType) & static_cast(IMeshWireframeSource::EMeshEdgeType::MeshBoundary)) != 0) || (bEnableUVSeams && (static_cast(EdgeType) & static_cast(IMeshWireframeSource::EMeshEdgeType::UVSeam )) != 0) || (bEnableNormalSeams && (static_cast(EdgeType) & static_cast(IMeshWireframeSource::EMeshEdgeType::NormalSeam )) != 0) || (bEnableTangentSeams && (static_cast(EdgeType) & static_cast(IMeshWireframeSource::EMeshEdgeType::TangentSeam )) != 0) || (bEnableColorSeams && (static_cast(EdgeType) & static_cast(IMeshWireframeSource::EMeshEdgeType::ColorSeam )) != 0)) { VisibleEdgeBatch.Add(UE::Geometry::FIndex3i(VertIndexA, VertIndexB, static_cast(EdgeType))); } } }); for (int32 BatchIndex = 0; BatchIndex < NumBatches; ++BatchIndex) { VisibleEdgeBatches[BatchIndex].Offset = NumVisibleEdges; NumVisibleEdges += VisibleEdgeBatches[BatchIndex].Edges.Num(); } if (NumVisibleEdges == 0) { return; } const int32 NumLineVertices = NumVisibleEdges * 4; const int32 NumLineIndices = NumVisibleEdges * 6; constexpr int32 NumTextureCoordinates = 1; VertexBuffers.PositionVertexBuffer.Init(NumLineVertices); VertexBuffers.StaticMeshVertexBuffer.Init(NumLineVertices, NumTextureCoordinates); VertexBuffers.ColorVertexBuffer.Init(NumLineVertices); IndexBuffer.Indices.SetNumUninitialized(NumLineIndices); MeshBatchDatas.Emplace(); FWireframeLinesMeshBatchData& MeshBatchData = MeshBatchDatas.Last(); MeshBatchData.MinVertexIndex = 0; MeshBatchData.MaxVertexIndex = 0 + NumLineVertices - 1; MeshBatchData.StartIndex = 0; MeshBatchData.NumPrimitives = NumVisibleEdges * 2; if (Component->GetMaterial(0) != nullptr) { MeshBatchData.MaterialProxy = Component->GetMaterial(0)->GetRenderProxy(); } else { MeshBatchData.MaterialProxy = UMaterial::GetDefaultMaterial(MD_Surface)->GetRenderProxy(); } const FColor RegularEdgeColor = FLinearColor::FromSRGBColor(Component->WireframeColor).ToFColor(false); const float RegularEdgeThickness = Component->ThicknessScale * Component->WireframeThickness; const FColor BoundaryEdgeColor = FLinearColor::FromSRGBColor(Component->BoundaryEdgeColor).ToFColor(false); const float BoundaryEdgeThickness = Component->ThicknessScale * Component->BoundaryEdgeThickness; const FColor UVSeamColor = FLinearColor::FromSRGBColor(Component->UVSeamColor).ToFColor(false); const float UVSeamThickness = Component->ThicknessScale * Component->UVSeamThickness; const FColor NormalSeamColor = FLinearColor::FromSRGBColor(Component->NormalSeamColor).ToFColor(false); const float NormalSeamThickness = Component->ThicknessScale * Component->NormalSeamThickness; const FColor TangentSeamColor = FLinearColor::FromSRGBColor(Component->TangentSeamColor).ToFColor(false); const float TangentSeamThickness = Component->ThicknessScale * Component->TangentSeamThickness; const FColor ColorSeamColor = FLinearColor::FromSRGBColor(Component->ColorSeamColor).ToFColor(false); const float ColorSeamThickness = Component->ThicknessScale * Component->ColorSeamThickness; const float LineDepthBias = Component->LineDepthBias * Component->LineDepthBiasSizeScale; // Initialize lines. // Lines are represented as two tris of zero thickness. The UV's stored at vertices are actually (lineThickness, depthBias), // which the material unpacks and uses to thicken the polygons and set the pixel depth bias. ParallelFor(NumBatches, [&](int32 BatchIndex) { const int32 Offset = VisibleEdgeBatches[BatchIndex].Offset; const TArray& VisibleEdgeBatch = VisibleEdgeBatches[BatchIndex].Edges; for (int32 VisibleEdgeIndex = 0, Num = VisibleEdgeBatch.Num(); VisibleEdgeIndex < Num; ++VisibleEdgeIndex) { const int32 VertexBufferIndex = (Offset + VisibleEdgeIndex) * 4; const int32 IndexBufferIndex = (Offset + VisibleEdgeIndex) * 6; const UE::Geometry::FIndex3i EdgeInfo = VisibleEdgeBatch[VisibleEdgeIndex]; IMeshWireframeSource::EMeshEdgeType EdgeType = static_cast(EdgeInfo.C); float UseThickness = RegularEdgeThickness; FColor UseColor = RegularEdgeColor; if (EdgeType != IMeshWireframeSource::EMeshEdgeType::Regular) { const bool bIsBoundaryEdge = ((static_cast(EdgeType) & static_cast(IMeshWireframeSource::EMeshEdgeType::MeshBoundary)) != 0); if (bEnableBoundaryEdges && bIsBoundaryEdge) { UseThickness = BoundaryEdgeThickness; UseColor = BoundaryEdgeColor; } else if (bEnableUVSeams && (static_cast(EdgeType) & static_cast(IMeshWireframeSource::EMeshEdgeType::UVSeam)) != 0) { UseThickness = (bIsBoundaryEdge) ? BoundaryEdgeThickness : UVSeamThickness; UseColor = UVSeamColor; } else if (bEnableNormalSeams && (static_cast(EdgeType) & static_cast(IMeshWireframeSource::EMeshEdgeType::NormalSeam)) != 0) { UseThickness = (bIsBoundaryEdge) ? BoundaryEdgeThickness : NormalSeamThickness; UseColor = NormalSeamColor; } else if (bEnableTangentSeams && (static_cast(EdgeType) & static_cast(IMeshWireframeSource::EMeshEdgeType::TangentSeam)) != 0) { UseThickness = (bIsBoundaryEdge) ? BoundaryEdgeThickness : TangentSeamThickness; UseColor = TangentSeamColor; } else if (bEnableColorSeams && (static_cast(EdgeType) & static_cast(IMeshWireframeSource::EMeshEdgeType::ColorSeam)) != 0) { UseThickness = (bIsBoundaryEdge) ? BoundaryEdgeThickness : ColorSeamThickness; UseColor = ColorSeamColor; } } const FVector A = WireSource->GetVertex(EdgeInfo.A); const FVector B = WireSource->GetVertex(EdgeInfo.B); const FVector LineDirection = (B - A).GetSafeNormal(); const FVector2f UV(UseThickness, LineDepthBias); VertexBuffers.PositionVertexBuffer.VertexPosition(VertexBufferIndex + 0) = static_cast(A); VertexBuffers.PositionVertexBuffer.VertexPosition(VertexBufferIndex + 1) = static_cast(B); VertexBuffers.PositionVertexBuffer.VertexPosition(VertexBufferIndex + 2) = static_cast(B); VertexBuffers.PositionVertexBuffer.VertexPosition(VertexBufferIndex + 3) = static_cast(A); VertexBuffers.StaticMeshVertexBuffer.SetVertexTangents(VertexBufferIndex + 0, FVector3f::ZeroVector, FVector3f::ZeroVector, static_cast(-LineDirection)); VertexBuffers.StaticMeshVertexBuffer.SetVertexTangents(VertexBufferIndex + 1, FVector3f::ZeroVector, FVector3f::ZeroVector, static_cast(-LineDirection)); VertexBuffers.StaticMeshVertexBuffer.SetVertexTangents(VertexBufferIndex + 2, FVector3f::ZeroVector, FVector3f::ZeroVector, static_cast(LineDirection)); VertexBuffers.StaticMeshVertexBuffer.SetVertexTangents(VertexBufferIndex + 3, FVector3f::ZeroVector, FVector3f::ZeroVector, static_cast(LineDirection)); VertexBuffers.StaticMeshVertexBuffer.SetVertexUV(VertexBufferIndex + 0, 0, UV); VertexBuffers.StaticMeshVertexBuffer.SetVertexUV(VertexBufferIndex + 1, 0, UV); VertexBuffers.StaticMeshVertexBuffer.SetVertexUV(VertexBufferIndex + 2, 0, UV); VertexBuffers.StaticMeshVertexBuffer.SetVertexUV(VertexBufferIndex + 3, 0, UV); // The color stored in the vertices actually gets interpreted as a linear color by the material, // whereas it is more convenient for the user of the MeshWireframe to specify colors as sRGB. So we actually // have to convert it back to linear. The ToFColor(false) call just scales back into 0-255 space. VertexBuffers.ColorVertexBuffer.VertexColor(VertexBufferIndex + 0) = UseColor; VertexBuffers.ColorVertexBuffer.VertexColor(VertexBufferIndex + 1) = UseColor; VertexBuffers.ColorVertexBuffer.VertexColor(VertexBufferIndex + 2) = UseColor; VertexBuffers.ColorVertexBuffer.VertexColor(VertexBufferIndex + 3) = UseColor; IndexBuffer.Indices[IndexBufferIndex + 0] = VertexBufferIndex + 0; IndexBuffer.Indices[IndexBufferIndex + 1] = VertexBufferIndex + 1; IndexBuffer.Indices[IndexBufferIndex + 2] = VertexBufferIndex + 2; IndexBuffer.Indices[IndexBufferIndex + 3] = VertexBufferIndex + 2; IndexBuffer.Indices[IndexBufferIndex + 4] = VertexBufferIndex + 3; IndexBuffer.Indices[IndexBufferIndex + 5] = VertexBufferIndex + 0; } }); ENQUEUE_RENDER_COMMAND(MeshWireframeVertexBuffersInit)( [this](FRHICommandListImmediate& RHICmdList) { VertexBuffers.PositionVertexBuffer.InitResource(RHICmdList); VertexBuffers.StaticMeshVertexBuffer.InitResource(RHICmdList); VertexBuffers.ColorVertexBuffer.InitResource(RHICmdList); FLocalVertexFactory::FDataType Data; VertexBuffers.PositionVertexBuffer.BindPositionVertexBuffer(&VertexFactory, Data); VertexBuffers.StaticMeshVertexBuffer.BindTangentVertexBuffer(&VertexFactory, Data); VertexBuffers.StaticMeshVertexBuffer.BindTexCoordVertexBuffer(&VertexFactory, Data); VertexBuffers.ColorVertexBuffer.BindColorVertexBuffer(&VertexFactory, Data); VertexFactory.SetData(RHICmdList, Data); VertexFactory.InitResource(RHICmdList); IndexBuffer.InitResource(RHICmdList); }); } virtual ~FMeshWireframeSceneProxy() override { VertexBuffers.PositionVertexBuffer.ReleaseResource(); VertexBuffers.StaticMeshVertexBuffer.ReleaseResource(); VertexBuffers.ColorVertexBuffer.ReleaseResource(); IndexBuffer.ReleaseResource(); VertexFactory.ReleaseResource(); } virtual void GetDynamicMeshElements(const TArray& Views, const FSceneViewFamily& ViewFamily, uint32 VisibilityMap, FMeshElementCollector& Collector) const override { QUICK_SCOPE_CYCLE_COUNTER(STAT_OverlaySceneProxy_GetDynamicMeshElements); for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++) { if (VisibilityMap & (1 << ViewIndex)) { for (const FWireframeLinesMeshBatchData& MeshBatchData : MeshBatchDatas) { FMeshBatch& Mesh = Collector.AllocateMesh(); FMeshBatchElement& BatchElement = Mesh.Elements[0]; BatchElement.IndexBuffer = &IndexBuffer; Mesh.bWireframe = false; Mesh.VertexFactory = &VertexFactory; Mesh.MaterialRenderProxy = MeshBatchData.MaterialProxy; FDynamicPrimitiveUniformBuffer& DynamicPrimitiveUniformBuffer = Collector.AllocateOneFrameResource(); DynamicPrimitiveUniformBuffer.Set(Collector.GetRHICommandList(), GetLocalToWorld(), GetLocalToWorld(), GetBounds(), GetLocalBounds(), false, false, AlwaysHasVelocity()); BatchElement.PrimitiveUniformBufferResource = &DynamicPrimitiveUniformBuffer.UniformBuffer; BatchElement.FirstIndex = MeshBatchData.StartIndex; BatchElement.NumPrimitives = MeshBatchData.NumPrimitives; BatchElement.MinVertexIndex = MeshBatchData.MinVertexIndex; BatchElement.MaxVertexIndex = MeshBatchData.MaxVertexIndex; Mesh.ReverseCulling = IsLocalToWorldDeterminantNegative(); Mesh.Type = PT_TriangleList; Mesh.DepthPriorityGroup = SDPG_World; Mesh.bCanApplyViewModeOverrides = false; Collector.AddMesh(ViewIndex, Mesh); } } } } virtual FPrimitiveViewRelevance GetViewRelevance(const FSceneView* View) const override { FPrimitiveViewRelevance Result; Result.bDrawRelevance = IsShown(View); Result.bShadowRelevance = IsShadowCast(View); Result.bDynamicRelevance = true; Result.bRenderInMainPass = ShouldRenderInMainPass(); Result.bUsesLightingChannels = GetLightingChannelMask() != GetDefaultLightingChannelMask(); Result.bRenderCustomDepth = ShouldRenderCustomDepth(); Result.bTranslucentSelfShadow = bCastVolumetricTranslucentShadow; MaterialRelevance.SetPrimitiveViewRelevance(Result); Result.bVelocityRelevance = DrawsVelocity() && Result.bOpaque && Result.bRenderInMainPass; return Result; } virtual bool CanBeOccluded() const override { return !MaterialRelevance.bDisableDepthTest; } virtual uint32 GetMemoryFootprint() const override { return sizeof(*this) + GetAllocatedSize(); } uint32 GetAllocatedSize() const { return FPrimitiveSceneProxy::GetAllocatedSize(); } virtual SIZE_T GetTypeHash() const override { static SIZE_T UniquePointer; return reinterpret_cast(&UniquePointer); } private: TArray MeshBatchDatas; FMaterialRelevance MaterialRelevance; FLocalVertexFactory VertexFactory; FStaticMeshVertexBuffers VertexBuffers; FDynamicMeshIndexBuffer32 IndexBuffer; }; UMeshWireframeComponent::UMeshWireframeComponent() { CastShadow = false; bSelectable = false; PrimaryComponentTick.bCanEverTick = false; UPrimitiveComponent::SetCollisionProfileName(UCollisionProfile::NoCollision_ProfileName); } void UMeshWireframeComponent::SetWireframeSourceProvider(TSharedPtr Provider) { SourceProvider = Provider; UpdateWireframe(); } void UMeshWireframeComponent::UpdateWireframe() { if (SourceProvider) { SourceProvider->AccessMesh([&](const IMeshWireframeSource& Source) { this->LocalBounds = Source.GetBounds(); }); } MarkRenderStateDirty(); } void UMeshWireframeComponent::SetLineMaterial(UMaterialInterface* InLineMaterial) { LineMaterial = InLineMaterial; SetMaterial(0, InLineMaterial); } FPrimitiveSceneProxy* UMeshWireframeComponent::CreateSceneProxy() { if (SourceProvider) { FMeshWireframeSceneProxy* NewProxy = nullptr; SourceProvider->AccessMesh([this, &NewProxy](const IMeshWireframeSource& Source) { NewProxy = new FMeshWireframeSceneProxy(this, &Source); }); return NewProxy; } return nullptr; } int32 UMeshWireframeComponent::GetNumMaterials() const { return 1; } FBoxSphereBounds UMeshWireframeComponent::CalcBounds(const FTransform& LocalToWorld) const { return LocalBounds.TransformBy(LocalToWorld); }