// Copyright Epic Games, Inc. All Rights Reserved. #include "MaterialRenderItem.h" #include "MaterialBakingStructures.h" #include "EngineModule.h" #include "SceneView.h" #include "StaticMeshAttributes.h" #include "DynamicMeshBuilder.h" #include "MeshPassProcessor.h" #include "CanvasRender.h" #include "RHIStaticStates.h" #include "UnrealClient.h" #include "PrimitiveUniformShaderParametersBuilder.h" #define SHOW_WIREFRAME_MESH 0 FMeshMaterialRenderItem::FMeshMaterialRenderItem( const FIntPoint& InTextureSize, const FMeshData* InMeshSettings, FDynamicMeshBufferAllocator* InDynamicMeshBufferAllocator) : MeshSettings(InMeshSettings) , TextureSize(InTextureSize) , MaterialRenderProxy(nullptr) , ViewFamily(nullptr) , bMeshElementDirty(true) , DynamicMeshBufferAllocator(InDynamicMeshBufferAllocator) { GenerateRenderData(); LCI = new FMeshRenderInfo(InMeshSettings->LightMap, nullptr, nullptr, InMeshSettings->LightmapResourceCluster); } bool FMeshMaterialRenderItem::Render_RenderThread(FCanvasRenderContext& RenderContext, FMeshPassProcessorRenderState& DrawRenderState, const FCanvas* Canvas) { checkSlow(ViewFamily && MeshSettings && MaterialRenderProxy); // current render target set for the canvas const FRenderTarget* CanvasRenderTarget = Canvas->GetRenderTarget(); const FIntRect ViewRect(FIntPoint(0, 0), CanvasRenderTarget->GetSizeXY()); // make a temporary view FSceneViewInitOptions ViewInitOptions; ViewInitOptions.ViewFamily = ViewFamily; ViewInitOptions.SetViewRectangle(ViewRect); ViewInitOptions.ViewOrigin = FVector::ZeroVector; ViewInitOptions.ViewRotationMatrix = FMatrix::Identity; ViewInitOptions.ProjectionMatrix = Canvas->GetTransformStack().Top().GetMatrix(); ViewInitOptions.BackgroundColor = FLinearColor::Black; ViewInitOptions.OverlayColor = FLinearColor::White; FSceneView View(ViewInitOptions); View.FinalPostProcessSettings.bOverride_IndirectLightingIntensity = 1; View.FinalPostProcessSettings.IndirectLightingIntensity = 0.0f; if (Vertices.Num() && Indices.Num()) { FMeshPassProcessorRenderState LocalDrawRenderState; // disable depth test & writes LocalDrawRenderState.SetBlendState(TStaticBlendState::GetRHI()); LocalDrawRenderState.SetDepthStencilState(TStaticDepthStencilState::GetRHI()); QueueMaterial(RenderContext, LocalDrawRenderState, &View); } return true; } bool FMeshMaterialRenderItem::Render_GameThread(const FCanvas* Canvas, FCanvasRenderThreadScope& RenderScope) { RenderScope.EnqueueRenderCommand( [this, Canvas](FCanvasRenderContext& RenderContext) { // Render_RenderThread uses its own render state FMeshPassProcessorRenderState DummyRenderState; Render_RenderThread(RenderContext, DummyRenderState, Canvas); } ); return true; } void FMeshMaterialRenderItem::GenerateRenderData() { TRACE_CPUPROFILER_EVENT_SCOPE(FMeshMaterialRenderItem::GenerateRenderData) // Reset array without resizing Vertices.SetNum(0, EAllowShrinking::No); Indices.SetNum(0, EAllowShrinking::No); if (MeshSettings->MeshDescription) { // Use supplied FMeshDescription data to populate render data PopulateWithMeshData(); } else { // Use simple rectangle PopulateWithQuadData(); } bMeshElementDirty = true; } FMeshMaterialRenderItem::~FMeshMaterialRenderItem() { // Send the release of the buffers to the render thread ENQUEUE_RENDER_COMMAND(ReleaseResources)( [ToRelease = MoveTemp(MeshBuilderResources)](FRHICommandListImmediate& RHICmdList) {} ); } void FMeshMaterialRenderItem::QueueMaterial(FCanvasRenderContext& RenderContext, FMeshPassProcessorRenderState& DrawRenderState, const FSceneView* View) { TRACE_CPUPROFILER_EVENT_SCOPE(FMeshMaterialRenderItem::QueueMaterial) if (bMeshElementDirty) { MeshBuilderResources.Clear(); FDynamicMeshBuilder DynamicMeshBuilder(View->GetFeatureLevel(), MAX_STATIC_TEXCOORDS, MeshSettings->LightMapIndex, false, DynamicMeshBufferAllocator); { TRACE_CPUPROFILER_EVENT_SCOPE(CopyData); DynamicMeshBuilder.AddVertices(Vertices); DynamicMeshBuilder.AddTriangles(Indices); } const FPrimitiveData DefaultPrimitiveData; const FPrimitiveData& PrimitiveData = MeshSettings->PrimitiveData.Get(DefaultPrimitiveData); FPrimitiveUniformShaderParameters PrimitiveParams = FPrimitiveUniformShaderParametersBuilder{} .Defaults() .LocalToWorld(PrimitiveData.LocalToWorld) .ActorWorldPosition(PrimitiveData.ActorPosition) .WorldBounds(PrimitiveData.WorldBounds) .LocalBounds(PrimitiveData.LocalBounds) .PreSkinnedLocalBounds(PrimitiveData.PreSkinnedLocalBounds) .CustomPrimitiveData(PrimitiveData.CustomPrimitiveData) .ReceivesDecals(false) .OutputVelocity(false) .Build(); DynamicMeshBuilder.GetMeshElement(PrimitiveParams, MaterialRenderProxy, SDPG_Foreground, true, 0, MeshBuilderResources, MeshElement); check(MeshBuilderResources.IsValidForRendering()); bMeshElementDirty = false; } MeshElement.MaterialRenderProxy = MaterialRenderProxy; LCI->CreatePrecomputedLightingUniformBuffer_RenderingThread(View->GetFeatureLevel()); MeshElement.LCI = LCI; #if SHOW_WIREFRAME_MESH MeshElement.bWireframe = true; #endif const int32 NumTris = FMath::TruncToInt((float)Indices.Num() / 3); if (NumTris == 0) { // there's nothing to do here return; } // Bake the material out to a tile GetRendererModule().DrawTileMesh(RenderContext, DrawRenderState, *View, MeshElement, false /*bIsHitTesting*/, FHitProxyId()); } void FMeshMaterialRenderItem::PopulateWithQuadData() { // Pre-transform all vertices with the inverse of LocalToWorld to negate its effect during material baking const FMatrix44f WorldToLocal = MeshSettings->PrimitiveData.IsSet() ? FMatrix44f(MeshSettings->PrimitiveData->LocalToWorld.Inverse()) : FMatrix44f::Identity; Vertices.Empty(4); Indices.Empty(6); const float OffsetU = MeshSettings->TextureCoordinateBox.Min.X; const float OffsetV = MeshSettings->TextureCoordinateBox.Min.Y; const float SizeU = MeshSettings->TextureCoordinateBox.Max.X - MeshSettings->TextureCoordinateBox.Min.X; const float SizeV = MeshSettings->TextureCoordinateBox.Max.Y - MeshSettings->TextureCoordinateBox.Min.Y; const float ScaleX = TextureSize.X; const float ScaleY = TextureSize.Y; // add vertices for (int32 VertIndex = 0; VertIndex < 4; VertIndex++) { FDynamicMeshVertex* Vert = new(Vertices)FDynamicMeshVertex(); const int32 X = VertIndex & 1; const int32 Y = (VertIndex >> 1) & 1; Vert->Position = WorldToLocal.TransformPosition(FVector3f(ScaleX * X, ScaleY * Y, 0)); FVector3f TangentX = WorldToLocal.TransformVector(FVector3f(1, 0, 0)); FVector3f TangentZ = WorldToLocal.TransformVector(FVector3f(0, 1, 0)); FVector3f TangentY = WorldToLocal.TransformVector(FVector3f(0, 0, 1)); Vert->SetTangents(TangentX, TangentZ, TangentY); FMemory::Memzero(&Vert->TextureCoordinate, sizeof(Vert->TextureCoordinate)); for (int32 TexcoordIndex = 0; TexcoordIndex < MAX_STATIC_TEXCOORDS; TexcoordIndex++) { Vert->TextureCoordinate[TexcoordIndex].Set(OffsetU + SizeU * X, OffsetV + SizeV * Y); } Vert->Color = FColor::White; } // add indices static const uint32 TriangleIndices[6] = { 0, 2, 1, 2, 3, 1 }; Indices.Append(TriangleIndices, 6); } void FMeshMaterialRenderItem::PopulateWithMeshData() { // Pre-transform all vertices with the inverse of LocalToWorld to negate its effect during material baking const FMatrix44f WorldToLocal = MeshSettings->PrimitiveData.IsSet() ? FMatrix44f(MeshSettings->PrimitiveData->LocalToWorld.Inverse()) : FMatrix44f::Identity; const FMeshDescription* RawMesh = MeshSettings->MeshDescription; FStaticMeshConstAttributes Attributes(*RawMesh); TArrayView VertexPositions = Attributes.GetVertexPositions().GetRawArray(); TArrayView VertexInstanceNormals = Attributes.GetVertexInstanceNormals().GetRawArray(); TArrayView VertexInstanceTangents = Attributes.GetVertexInstanceTangents().GetRawArray(); TArrayView VertexInstanceBinormalSigns = Attributes.GetVertexInstanceBinormalSigns().GetRawArray(); TArrayView VertexInstanceColors = Attributes.GetVertexInstanceColors().GetRawArray(); TVertexInstanceAttributesConstRef VertexInstanceUVs = Attributes.GetVertexInstanceUVs(); const int32 NumVerts = RawMesh->Vertices().Num(); // reserve renderer data Vertices.Empty(NumVerts); Indices.Empty(NumVerts >> 1); // When using arbitrary mesh data (rather than a simple quad), TextureCoordinateBox has to be applied to XY position: const float ScaleX = TextureSize.X / (MeshSettings->TextureCoordinateBox.Max.X - MeshSettings->TextureCoordinateBox.Min.X); const float ScaleY = TextureSize.Y / (MeshSettings->TextureCoordinateBox.Max.Y - MeshSettings->TextureCoordinateBox.Min.Y); const float OffsetX = -MeshSettings->TextureCoordinateBox.Min.X * ScaleX; const float OffsetY = -MeshSettings->TextureCoordinateBox.Min.Y * ScaleY; const static int32 VertexPositionStoredUVChannel = 6; // count number of texture coordinates for this mesh const int32 NumTexcoords = [&]() { return FMath::Min(VertexInstanceUVs.GetNumChannels(), VertexPositionStoredUVChannel); }(); // check if we should use NewUVs or original UV set const bool bUseNewUVs = MeshSettings->CustomTextureCoordinates.Num() > 0; if (bUseNewUVs) { check(MeshSettings->CustomTextureCoordinates.Num() == VertexInstanceUVs.GetNumElements() && VertexInstanceUVs.GetNumChannels() > MeshSettings->TextureCoordinateIndex); } // add vertices int32 VertIndex = 0; int32 FaceIndex = 0; for(const FTriangleID& TriangleID : RawMesh->Triangles().GetElementIDs()) { const FPolygonGroupID PolygonGroupID = RawMesh->GetTrianglePolygonGroup(TriangleID); if (MeshSettings->MaterialIndices.Contains(PolygonGroupID.GetValue())) { const int32 NUM_VERTICES = 3; for (int32 Corner = 0; Corner < NUM_VERTICES; Corner++) { // Swap vertices order if mesh is mirrored const int32 CornerIdx = !MeshSettings->bMirrored ? Corner : NUM_VERTICES - Corner - 1; const int32 SrcVertIndex = FaceIndex * NUM_VERTICES + CornerIdx; const FVertexInstanceID SrcVertexInstanceID = RawMesh->GetTriangleVertexInstance(TriangleID, Corner); const FVertexID SrcVertexID = RawMesh->GetVertexInstanceVertex(SrcVertexInstanceID); // add vertex FDynamicMeshVertex* Vert = new(Vertices)FDynamicMeshVertex(); if (!bUseNewUVs) { // compute vertex position from original UV const FVector2D& UV = FVector2D(VertexInstanceUVs.Get(SrcVertexInstanceID, MeshSettings->TextureCoordinateIndex)); Vert->Position = WorldToLocal.TransformPosition(FVector3f(OffsetX + UV.X * ScaleX, OffsetY + UV.Y * ScaleY, 0)); } else { const FVector2D& UV = MeshSettings->CustomTextureCoordinates[SrcVertIndex]; Vert->Position = WorldToLocal.TransformPosition(FVector3f(OffsetX + UV.X * ScaleX, OffsetY + UV.Y * ScaleY, 0)); } FVector3f TangentX = WorldToLocal.TransformVector(VertexInstanceTangents[SrcVertexInstanceID]); FVector3f TangentZ = WorldToLocal.TransformVector(VertexInstanceNormals[SrcVertexInstanceID]); FVector3f TangentY = FVector3f::CrossProduct(TangentZ, TangentX).GetSafeNormal() * VertexInstanceBinormalSigns[SrcVertexInstanceID]; Vert->SetTangents(TangentX, TangentY, TangentZ); for (int32 TexcoordIndex = 0; TexcoordIndex < NumTexcoords; TexcoordIndex++) { Vert->TextureCoordinate[TexcoordIndex] = VertexInstanceUVs.Get(SrcVertexInstanceID, TexcoordIndex); } if (NumTexcoords < VertexPositionStoredUVChannel) { for (int32 TexcoordIndex = NumTexcoords; TexcoordIndex < VertexPositionStoredUVChannel; TexcoordIndex++) { Vert->TextureCoordinate[TexcoordIndex] = Vert->TextureCoordinate[FMath::Max(NumTexcoords - 1, 0)]; } } // Store original vertex positions in texture coordinate data Vert->TextureCoordinate[6].X = VertexPositions[SrcVertexID].X; Vert->TextureCoordinate[6].Y = VertexPositions[SrcVertexID].Y; Vert->TextureCoordinate[7].X = VertexPositions[SrcVertexID].Z; Vert->TextureCoordinate[7].Y = 0.0f; Vert->Color = FLinearColor(VertexInstanceColors[SrcVertexInstanceID]).ToFColor(true); // add index Indices.Add(VertIndex); VertIndex++; } } FaceIndex++; } }