Files
UnrealEngine/Engine/Source/Runtime/GeometryFramework/Public/Components/MeshRenderBufferSet.h
2025-05-18 13:04:45 +08:00

767 lines
25 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "Async/ParallelFor.h"
#include "Components/BaseDynamicMeshComponent.h"
#include "Components/DynamicMeshComponent.h"
#include "DynamicMeshBuilder.h"
#include "DynamicMesh/DynamicMesh3.h"
#include "DynamicMesh/DynamicMeshAttributeSet.h"
#include "LocalVertexFactory.h"
#include "Rendering/StaticMeshVertexBuffer.h"
#include "Rendering/PositionVertexBuffer.h"
#include "Rendering/ColorVertexBuffer.h"
#include "RHICommandList.h"
#include "RayTracingGeometry.h"
#include "VertexFactory.h"
using UE::Geometry::FDynamicMesh3;
using UE::Geometry::FDynamicMeshAttributeSet;
using UE::Geometry::FDynamicMeshUVOverlay;
using UE::Geometry::FDynamicMeshNormalOverlay;
using UE::Geometry::FDynamicMeshColorOverlay;
using UE::Geometry::FDynamicMeshMaterialAttribute;
class UMaterialInterface;
/**
* FMeshRenderBufferSet stores a set of RenderBuffers for a mesh
*/
class FMeshRenderBufferSet
{
public:
/** Number of triangles in this renderbuffer set. Note that triangles may be split between IndexBuffer and SecondaryIndexBuffer. */
int TriangleCount = 0;
/** The buffer containing vertex data. */
FStaticMeshVertexBuffer StaticMeshVertexBuffer;
/** The buffer containing the position vertex data. */
FPositionVertexBuffer PositionVertexBuffer;
/** The buffer containing the vertex color data. */
FColorVertexBuffer ColorVertexBuffer;
/** triangle indices */
FDynamicMeshIndexBuffer32 IndexBuffer;
/** vertex factory */
FLocalVertexFactory VertexFactory;
/** Material to draw this mesh with */
UMaterialInterface* Material = nullptr;
/**
* Optional list of triangles stored in this buffer. Storing this allows us
* to rebuild the buffers if vertex data changes.
*/
TOptional<TArray<int>> Triangles;
/**
* If secondary index buffer is enabled, we populate this index buffer with additional triangles indexing into the same vertex buffers
*/
bool bEnableSecondaryIndexBuffer = false;
/**
* partition or subset of IndexBuffer that indexes into same vertex buffers
*/
FDynamicMeshIndexBuffer32 SecondaryIndexBuffer;
/**
* configure whether raytracing should be enabled for this RenderBufferSet
*/
bool bEnableRaytracing = false;
#if RHI_RAYTRACING
/**
* Raytracing buffers
*/
FRayTracingGeometry PrimaryRayTracingGeometry;
FRayTracingGeometry SecondaryRayTracingGeometry;
bool bIsRayTracingDataValid = false;
#endif
/**
* In situations where we want to *update* the existing Vertex or Index buffers, we need to synchronize
* access between the Game and Render threads. We use this lock to do that.
*/
FCriticalSection BuffersLock;
FMeshRenderBufferSet(ERHIFeatureLevel::Type FeatureLevelType)
: VertexFactory(FeatureLevelType, "FMeshRenderBufferSet")
{
StaticMeshVertexBuffer.SetUseFullPrecisionUVs(true);
StaticMeshVertexBuffer.SetUseHighPrecisionTangentBasis(true);
#if RHI_RAYTRACING
EnumAddFlags(IndexBuffer.UsageFlags, EBufferUsageFlags::ShaderResource);
EnumAddFlags(SecondaryIndexBuffer.UsageFlags, EBufferUsageFlags::ShaderResource);
#endif // RHI_RAYTRACING
}
virtual ~FMeshRenderBufferSet()
{
if (TriangleCount > 0)
{
PositionVertexBuffer.ReleaseResource();
StaticMeshVertexBuffer.ReleaseResource();
ColorVertexBuffer.ReleaseResource();
VertexFactory.ReleaseResource();
if (IndexBuffer.IsInitialized())
{
IndexBuffer.ReleaseResource();
}
if (SecondaryIndexBuffer.IsInitialized())
{
SecondaryIndexBuffer.ReleaseResource();
}
#if RHI_RAYTRACING
if (bEnableRaytracing)
{
PrimaryRayTracingGeometry.ReleaseResource();
SecondaryRayTracingGeometry.ReleaseResource();
}
#endif
}
}
/**
* Upload initialized mesh buffers.
* @warning This can only be called on the Rendering Thread.
*/
GEOMETRYFRAMEWORK_API void Upload();
/**
* Fast path to only update the primary and secondary index buffers. This can be used
* when (eg) the secondary index buffer is being used to highlight/hide a subset of triangles.
* @warning This can only be called on the Rendering Thread.
*/
void UploadIndexBufferUpdate()
{
// todo: can this be done with RHI locking and memcpy, like in TransferVertexUpdateToGPU?
FRHICommandListBase& RHICmdList = FRHICommandListImmediate::Get();
if (IndexBuffer.Indices.Num() > 0)
{
InitOrUpdateResource(RHICmdList, &IndexBuffer);
}
if (bEnableSecondaryIndexBuffer && SecondaryIndexBuffer.Indices.Num() > 0)
{
InitOrUpdateResource(RHICmdList, &SecondaryIndexBuffer);
}
InvalidateRayTracingData();
ValidateRayTracingData(); // currently we are immediately validating. This may be revisited in future.
}
/**
* Fast path to only update vertex buffers. This path rebuilds all the
* resources and reconfigures the vertex factory, so the counts/etc could be modified.
* @warning This can only be called on the Rendering Thread.
*/
GEOMETRYFRAMEWORK_API void UploadVertexUpdate(bool bPositions, bool bMeshAttribs, bool bColors);
/**
* Fast path to update various vertex buffers. This path does not support changing the
* size/counts of any of the sub-buffers, a direct memcopy from the CPU-side buffer to the RHI buffer is used.
* @warning This can only be called on the Rendering Thread.
*/
GEOMETRYFRAMEWORK_API void TransferVertexUpdateToGPU(FRHICommandListBase& RHICmdList, bool bPositions, bool bNormals, bool bTexCoords, bool bColors);
void InvalidateRayTracingData()
{
#if RHI_RAYTRACING
bIsRayTracingDataValid = false;
#endif
}
// Verify that valid raytracing data is available. This will cause a rebuild of the
// raytracing data if any of our buffers have been modified. Currently this is called
// by GetDynamicRayTracingInstances to ensure the RT data is available when needed.
void ValidateRayTracingData()
{
#if RHI_RAYTRACING
if (bIsRayTracingDataValid == false && IsRayTracingEnabled() && bEnableRaytracing)
{
UpdateRaytracingGeometryIfEnabled();
bIsRayTracingDataValid = true;
}
#endif
}
protected:
// rebuild raytracing data for current buffers
void UpdateRaytracingGeometryIfEnabled()
{
#if RHI_RAYTRACING
// do we always want to do this?
PrimaryRayTracingGeometry.ReleaseResource();
SecondaryRayTracingGeometry.ReleaseResource();
FRHICommandListBase& RHICmdList = FRHICommandListImmediate::Get();
for (int32 k = 0; k < 2; ++k)
{
FDynamicMeshIndexBuffer32& UseIndexBuffer = (k == 0) ? IndexBuffer : SecondaryIndexBuffer;
if (UseIndexBuffer.Indices.Num() == 0)
{
continue;
}
FRayTracingGeometry& RayTracingGeometry = (k == 0) ? PrimaryRayTracingGeometry : SecondaryRayTracingGeometry;
FRayTracingGeometryInitializer Initializer;
Initializer.IndexBuffer = UseIndexBuffer.IndexBufferRHI;
Initializer.TotalPrimitiveCount = UseIndexBuffer.Indices.Num() / 3;
Initializer.GeometryType = RTGT_Triangles;
Initializer.bFastBuild = true;
Initializer.bAllowUpdate = false;
FRayTracingGeometrySegment Segment;
Segment.VertexBuffer = PositionVertexBuffer.VertexBufferRHI;
Segment.NumPrimitives = Initializer.TotalPrimitiveCount;
Segment.MaxVertices = PositionVertexBuffer.GetNumVertices();
Initializer.Segments.Add(Segment);
RayTracingGeometry.SetInitializer(MoveTemp(Initializer));
RayTracingGeometry.InitResource(RHICmdList);
}
#endif
}
/**
* Initializes a render resource, or update it if already initialized.
* @warning This function can only be called on the Render Thread
*/
void InitOrUpdateResource(FRHICommandListBase& RHICmdList, FRenderResource* Resource)
{
if (!Resource->IsInitialized())
{
Resource->InitResource(RHICmdList);
}
else
{
Resource->UpdateRHI(RHICmdList);
}
}
public:
/**
* Enqueue a command on the Render Thread to destroy the passed in buffer set.
* At this point the buffer set should be considered invalid.
*/
static void DestroyRenderBufferSet(FMeshRenderBufferSet* BufferSet)
{
//@todo, remove this check? based on our usage it could produce a memory leak - see BaseDynamicMeshSceneProxy.cpp
if (BufferSet->TriangleCount == 0)
{
return;
}
delete BufferSet;
}
};
/**
* Parameters and accompanying functions used to build and update FMeshRenderBuffers from a dynamic mesh.
* For usage, see FBaseDynamicMeshSceneProxy or the simple converter below FDynamicMeshComponentToMeshRenderBufferSet
*
* Note: This is low-level and methods assume, but do not check, all input data is consistent (e.g. that all overlays,
the triangle count and enumerator are all consistent with the provided mesh. )
*/
struct FMeshRenderBufferSetConversionUtil
{
/**
* Constant color assigned to vertices if no other vertex color is specified
*/
FColor ConstantVertexColor = FColor::White;
/**
* If true, vertex colors on the FDynamicMesh will be ignored
*/
bool bIgnoreVertexColors = false;
/**
* If true, a per-triangle color is used to set vertex colors
*/
bool bUsePerTriangleColor = false;
/**
* Per-triangle color function. Only called if bUsePerTriangleColor=true
*/
TFunction<FColor(const FDynamicMesh3*, int)> PerTriangleColorFunc = nullptr;
/**
* If true, VertexColorRemappingFunc is called on Vertex Colors provided from Mesh to remap them to a different color
*/
bool bApplyVertexColorRemapping = false;
/**
* Vertex color remapping function. Only called if bApplyVertexColorRemapping == true, for mesh vertex colors
*/
TUniqueFunction<void(FVector4f&)> VertexColorRemappingFunc = nullptr;
/**
* Color Space Transform/Conversion applied to Vertex Colors provided from Mesh Color Overlay Attribute
* Color Space Conversion is applied after any Vertex Color Remapping.
*/
EDynamicMeshVertexColorTransformMode ColorSpaceTransformMode = EDynamicMeshVertexColorTransformMode::NoTransform;
/**
* If true, a facet normals are used instead of mesh normals
*/
bool bUsePerTriangleNormals = false;
/**
* If true, populate secondary buffers using SecondaryTriFilterFunc
*/
bool bUseSecondaryTriBuffers = false;
/**
* Filter predicate for secondary triangle index buffer. Only called if bUseSecondaryTriBuffers=true
*/
TUniqueFunction<bool(const FDynamicMesh3*, int32)> SecondaryTriFilterFunc = nullptr;
/**
* Initialize rendering buffers from given attribute overlays.
* Creates three vertices per triangle, IE no shared vertices in buffers.
*/
template<typename TriangleEnumerable>
void InitializeBuffersFromOverlays(
FMeshRenderBufferSet* RenderBuffers,
const FDynamicMesh3* Mesh,
int NumTriangles, const TriangleEnumerable& Enumerable,
const FDynamicMeshUVOverlay* UVOverlay,
const FDynamicMeshNormalOverlay* NormalOverlay,
const FDynamicMeshColorOverlay* ColorOverlay,
TFunctionRef<void(int, int, int, const FVector3f&, FVector3f&, FVector3f&)> TangentsFunc,
bool bTrackTriangles = false,
bool bParallel = false)
{
TArray<const FDynamicMeshUVOverlay*> UVOverlays;
UVOverlays.Add(UVOverlay);
InitializeBuffersFromOverlays(RenderBuffers, Mesh, NumTriangles, Enumerable,
UVOverlays, NormalOverlay, ColorOverlay, TangentsFunc, bTrackTriangles, bParallel);
}
/**
* Initialize rendering buffers from given attribute overlays.
* Creates three vertices per triangle, IE no shared vertices in buffers.
*/
template<typename TriangleEnumerable, typename UVOverlayListAllocator>
void InitializeBuffersFromOverlays( FMeshRenderBufferSet* RenderBuffers,
const FDynamicMesh3* Mesh,
int NumTriangles,
const TriangleEnumerable& Enumerable,
const TArray<const FDynamicMeshUVOverlay*, UVOverlayListAllocator>& UVOverlays,
const FDynamicMeshNormalOverlay* NormalOverlay,
const FDynamicMeshColorOverlay* ColorOverlay,
TFunctionRef<void(int, int, int, const FVector3f&, FVector3f&, FVector3f&)> TangentsFunc,
bool bTrackTriangles = false,
bool bParallel = false);
/**
* Filter the triangles in a FMeshRenderBufferSet into the SecondaryIndexBuffer.
* Requires that RenderBuffers->Triangles has been initialized.
* @param bDuplicate if set, then primary IndexBuffer is unmodified and SecondaryIndexBuffer contains duplicates. Otherwise triangles are sorted via predicate into either primary or secondary.
*/
void GEOMETRYFRAMEWORK_API UpdateSecondaryTriangleBuffer( FMeshRenderBufferSet* RenderBuffers,
const FDynamicMesh3* Mesh,
bool bDuplicate);
/**
* RecomputeRenderBufferTriangleIndexSets re-sorts the existing set of triangles in a FMeshRenderBufferSet
* into primary and secondary index buffers. Note that UploadIndexBufferUpdate() must be called
* after this function!
*/
void GEOMETRYFRAMEWORK_API RecomputeRenderBufferTriangleIndexSets( FMeshRenderBufferSet* RenderBuffers,
const FDynamicMesh3* Mesh);
/**
* Update vertex positions/normals/colors of an existing set of render buffers.
* Assumes that buffers were created with unshared vertices, ie three vertices per triangle, eg by InitializeBuffersFromOverlays()
*/
template<typename TriangleEnumerable>
void UpdateVertexBuffersFromOverlays( FMeshRenderBufferSet* RenderBuffers,
const FDynamicMesh3* Mesh,
int NumTriangles, const TriangleEnumerable& Enumerable,
const FDynamicMeshNormalOverlay* NormalOverlay,
const FDynamicMeshColorOverlay* ColorOverlay,
TFunctionRef<void(int, int, int, const FVector3f&, FVector3f&, FVector3f&)> TangentsFunc,
bool bUpdatePositions = true,
bool bUpdateNormals = false,
bool bUpdateColors = false);
/**
* Update vertex uvs of an existing set of render buffers.
* Assumes that buffers were created with unshared vertices, ie three vertices per triangle, eg by InitializeBuffersFromOverlays()
*/
template<typename TriangleEnumerable, typename UVOverlayListAllocator>
void UpdateVertexUVBufferFromOverlays( FMeshRenderBufferSet* RenderBuffers,
const FDynamicMesh3* Mesh,
int32 NumTriangles, const TriangleEnumerable& Enumerable,
const TArray<const FDynamicMeshUVOverlay*, UVOverlayListAllocator>& UVOverlays);
/**
* Get the overlay color the FColor, respecting the ColorSpaceTransformMode utilizing the VertexColorRemappingFunc if requested.
*/
FColor GetOverlayColorAsFColor( const FDynamicMeshColorOverlay* ColorOverlay,
int32 ElementID)
{
checkSlow(ColorOverlay);
FVector4f UseColor = ColorOverlay->GetElement(ElementID);
if (bApplyVertexColorRemapping)
{
VertexColorRemappingFunc(UseColor);
}
if (ColorSpaceTransformMode == EDynamicMeshVertexColorTransformMode::SRGBToLinear)
{
// is there a better way to do this?
FColor QuantizedSRGBColor = ((FLinearColor)UseColor).ToFColor(false);
return FLinearColor(QuantizedSRGBColor).ToFColor(false);
}
else
{
bool bConvertToSRGB = (ColorSpaceTransformMode == EDynamicMeshVertexColorTransformMode::LinearToSRGB);
return ((FLinearColor)UseColor).ToFColor(bConvertToSRGB);
}
}
private:
using FIndex2i = UE::Geometry::FIndex2i;
using FIndex3i = UE::Geometry::FIndex3i;
};
// Simple tool to initialize a single set of mesh buffers for the entire mesh.
// for examples of more complicated conversions, see DynamicMeshSceneProxy
class FDynamicMeshComponentToMeshRenderBufferSet
{
public:
FMeshRenderBufferSetConversionUtil MeshRenderBufferSetConverter;
// note, this conversion may recompute the tangents on the DynamicMeshComponent
// since a dynamic mesh component with "autocalculated" tangents will compute them on first request
// @param DynamicMeshComponent - the mesh to be converted
// @param MeshRenderBufferSet - the buffer set to be initialized
// @param bUseComponentSettings - if true, the Component settings will override (and update) MeshRederBufferSetBuilder.{ColorspaceTransformMode, bUsePerTriangleNormals}.
GEOMETRYFRAMEWORK_API void Convert(class UDynamicMeshComponent& DynamicMeshComponent, FMeshRenderBufferSet& MeshRenderBufferSet, bool bUseComponentSettings = true);
protected:
TUniqueFunction<void(int, int, int, const FVector3f&, FVector3f&, FVector3f&)> MakeTangentsFunc(class UDynamicMeshComponent& DynamicMeshCompnent, bool bSkipAutoCompute = false);
};
// -- template implementations -- //
template<typename TriangleEnumerable, typename UVOverlayListAllocator>
void FMeshRenderBufferSetConversionUtil::InitializeBuffersFromOverlays( FMeshRenderBufferSet* RenderBuffers,
const FDynamicMesh3* Mesh,
int NumTriangles,
const TriangleEnumerable& Enumerable,
const TArray<const FDynamicMeshUVOverlay*, UVOverlayListAllocator>& UVOverlays,
const FDynamicMeshNormalOverlay* NormalOverlay,
const FDynamicMeshColorOverlay* ColorOverlay,
TFunctionRef<void(int, int, int, const FVector3f&, FVector3f&, FVector3f&)> TangentsFunc,
bool bTrackTriangles,
bool bParallel)
{
RenderBuffers->TriangleCount = NumTriangles;
if (NumTriangles == 0)
{
return;
}
bool bHaveColors = (ColorOverlay != nullptr) && (bIgnoreVertexColors == false);
const int NumVertices = NumTriangles * 3;
const int NumUVOverlays = UVOverlays.Num();
const int NumTexCoords = FMath::Max(1, NumUVOverlays); // must have at least one tex coord
{
RenderBuffers->PositionVertexBuffer.Init(NumVertices);
RenderBuffers->StaticMeshVertexBuffer.Init(NumVertices, NumTexCoords);
RenderBuffers->ColorVertexBuffer.Init(NumVertices);
RenderBuffers->IndexBuffer.Indices.AddUninitialized(NumTriangles * 3);
}
// build triangle list if requested, or if we are using secondary buffers in which case we need it to filter later
const bool bBuildTriangleList = bTrackTriangles || bUseSecondaryTriBuffers;
// populate the triangle array. we use this for parallelism.
TArray<int32> TriangleArray;
{
TriangleArray.Reserve(NumTriangles);
for (int TriangleID : Enumerable)
{
TriangleArray.Add(TriangleID);
}
}
TArray<uint32>& IndexBuffer = RenderBuffers->IndexBuffer.Indices;
{
// populate the index buffer.
for (int idx = 0, IdxMax = 3 * NumTriangles; idx < IdxMax; ++idx)
{
IndexBuffer[idx] = idx;
}
}
ParallelFor(TriangleArray.Num(), [&](int idx)
{
int32 TriangleID = TriangleArray[idx];
FIndex3i Tri = Mesh->GetTriangle(TriangleID);
FIndex3i TriNormal = (NormalOverlay != nullptr) ? NormalOverlay->GetTriangle(TriangleID) : FIndex3i::Zero();
FIndex3i TriColor = (ColorOverlay != nullptr) ? ColorOverlay->GetTriangle(TriangleID) : FIndex3i::Zero();
FColor UniformTriColor = ConstantVertexColor;
if (bUsePerTriangleColor && PerTriangleColorFunc != nullptr)
{
UniformTriColor = PerTriangleColorFunc(Mesh, TriangleID);
bHaveColors = false;
}
int32 VertIdx = idx * 3;
for (int j = 0; j < 3; ++j)
{
RenderBuffers->PositionVertexBuffer.VertexPosition(VertIdx) = (FVector3f)Mesh->GetVertex(Tri[j]);
FVector3f Normal;
if (bUsePerTriangleNormals)
{
Normal = (FVector3f)Mesh->GetTriNormal(TriangleID);
}
else
{
Normal = (NormalOverlay != nullptr && TriNormal[j] != FDynamicMesh3::InvalidID) ?
NormalOverlay->GetElement(TriNormal[j]) : Mesh->GetVertexNormal(Tri[j]);
}
// get tangents
FVector3f TangentX, TangentY;
TangentsFunc(Tri[j], TriangleID, j, Normal, TangentX, TangentY);
RenderBuffers->StaticMeshVertexBuffer.SetVertexTangents(VertIdx, TangentX, TangentY, Normal);
FColor VertexFColor = (bHaveColors && TriColor[j] != FDynamicMesh3::InvalidID) ?
GetOverlayColorAsFColor(ColorOverlay, TriColor[j]) : UniformTriColor;
RenderBuffers->ColorVertexBuffer.VertexColor(VertIdx) = VertexFColor;
VertIdx++;
}
for (int32 k = 0; k < NumTexCoords; ++k)
{
VertIdx = idx * 3;
FIndex3i UVTriangle = (k < NumUVOverlays && UVOverlays[k] != nullptr) ? UVOverlays[k]->GetTriangle(TriangleID) : FIndex3i::Invalid();
for (int j = 0; j < 3; ++j)
{
FVector2f UV = (UVTriangle[j] != FDynamicMesh3::InvalidID) ?
UVOverlays[k]->GetElement(UVTriangle[j]) : FVector2f::Zero();
RenderBuffers->StaticMeshVertexBuffer.SetVertexUV(VertIdx, k, UV);
VertIdx++;
}
}
}, !bParallel);
if (bBuildTriangleList)
{
RenderBuffers->Triangles = MoveTemp(TriangleArray);
}
// split triangles into secondary buffer (at bit redundant since we just built IndexBuffer, but we may optionally duplicate triangles in the future)
if (bUseSecondaryTriBuffers)
{
RenderBuffers->bEnableSecondaryIndexBuffer = true;
UpdateSecondaryTriangleBuffer(RenderBuffers, Mesh, false);
}
}
/**
* Update vertex positions/normals/colors of an existing set of render buffers.
* Assumes that buffers were created with unshared vertices, ie three vertices per triangle, eg by InitializeBuffersFromOverlays()
*/
template<typename TriangleEnumerable>
void FMeshRenderBufferSetConversionUtil::UpdateVertexBuffersFromOverlays( FMeshRenderBufferSet* RenderBuffers,
const FDynamicMesh3* Mesh,
int NumTriangles, const TriangleEnumerable& Enumerable,
const FDynamicMeshNormalOverlay* NormalOverlay,
const FDynamicMeshColorOverlay* ColorOverlay,
TFunctionRef<void(int, int, int, const FVector3f&, FVector3f&, FVector3f&)> TangentsFunc,
bool bUpdatePositions,
bool bUpdateNormals,
bool bUpdateColors)
{
if (RenderBuffers->TriangleCount == 0)
{
return;
}
bool bHaveColors = (ColorOverlay != nullptr) && (bIgnoreVertexColors == false);
int NumVertices = NumTriangles * 3;
if ((bUpdatePositions && ensure(RenderBuffers->PositionVertexBuffer.GetNumVertices() == NumVertices) == false)
|| (bUpdateNormals && ensure(RenderBuffers->StaticMeshVertexBuffer.GetNumVertices() == NumVertices) == false)
|| (bUpdateColors && ensure(RenderBuffers->ColorVertexBuffer.GetNumVertices() == NumVertices) == false))
{
return;
}
int VertIdx = 0;
FVector3f TangentX, TangentY;
for (int TriangleID : Enumerable)
{
FIndex3i Tri = Mesh->GetTriangle(TriangleID);
FIndex3i TriNormal = (bUpdateNormals && NormalOverlay != nullptr) ? NormalOverlay->GetTriangle(TriangleID) : FIndex3i::Zero();
FIndex3i TriColor = (bUpdateColors && ColorOverlay != nullptr) ? ColorOverlay->GetTriangle(TriangleID) : FIndex3i::Zero();
FColor UniformTriColor = ConstantVertexColor;
if (bUpdateColors && bUsePerTriangleColor && PerTriangleColorFunc != nullptr)
{
UniformTriColor = PerTriangleColorFunc(Mesh, TriangleID);
bHaveColors = false;
}
for (int j = 0; j < 3; ++j)
{
if (bUpdatePositions)
{
RenderBuffers->PositionVertexBuffer.VertexPosition(VertIdx) = (FVector3f)Mesh->GetVertex(Tri[j]);
}
if (bUpdateNormals)
{
// get normal and tangent
FVector3f Normal;
if (bUsePerTriangleNormals)
{
Normal = (FVector3f)Mesh->GetTriNormal(TriangleID);
}
else
{
Normal = (NormalOverlay != nullptr && TriNormal[j] != FDynamicMesh3::InvalidID) ?
NormalOverlay->GetElement(TriNormal[j]) : Mesh->GetVertexNormal(Tri[j]);
}
TangentsFunc(Tri[j], TriangleID, j, Normal, TangentX, TangentY);
RenderBuffers->StaticMeshVertexBuffer.SetVertexTangents(VertIdx, (FVector3f)TangentX, (FVector3f)TangentY, (FVector3f)Normal);
}
if (bUpdateColors)
{
FColor VertexFColor = (bHaveColors && TriColor[j] != FDynamicMesh3::InvalidID) ?
GetOverlayColorAsFColor(ColorOverlay, TriColor[j]) : UniformTriColor;
RenderBuffers->ColorVertexBuffer.VertexColor(VertIdx) = VertexFColor;
}
VertIdx++;
}
}
}
template<typename TriangleEnumerable, typename UVOverlayListAllocator>
void FMeshRenderBufferSetConversionUtil::UpdateVertexUVBufferFromOverlays( FMeshRenderBufferSet* RenderBuffers,
const FDynamicMesh3* Mesh,
int32 NumTriangles, const TriangleEnumerable& Enumerable,
const TArray<const FDynamicMeshUVOverlay*, UVOverlayListAllocator>& UVOverlays)
{
// We align the update to the way we set UV's in InitializeBuffersFromOverlays.
if (RenderBuffers->TriangleCount == 0)
{
return;
}
int NumVertices = NumTriangles * 3;
if (ensure(RenderBuffers->StaticMeshVertexBuffer.GetNumVertices() == NumVertices) == false)
{
return;
}
int NumUVOverlays = UVOverlays.Num();
int NumTexCoords = RenderBuffers->StaticMeshVertexBuffer.GetNumTexCoords();
if (!ensure(NumUVOverlays <= NumTexCoords))
{
return;
}
// Temporarily stores the UV element indices for all UV channels of a single triangle
TArray<FIndex3i, TFixedAllocator<MAX_STATIC_TEXCOORDS>> UVTriangles;
UVTriangles.SetNum(NumTexCoords);
int VertIdx = 0;
for (int TriangleID : Enumerable)
{
for (int32 k = 0; k < NumTexCoords; ++k)
{
UVTriangles[k] = (k < NumUVOverlays && UVOverlays[k] != nullptr) ? UVOverlays[k]->GetTriangle(TriangleID) : FIndex3i::Invalid();
}
for (int j = 0; j < 3; ++j)
{
for (int32 k = 0; k < NumTexCoords; ++k)
{
FVector2f UV = (UVTriangles[k][j] != FDynamicMesh3::InvalidID) ?
UVOverlays[k]->GetElement(UVTriangles[k][j]) : FVector2f::Zero();
RenderBuffers->StaticMeshVertexBuffer.SetVertexUV(VertIdx, k, UV);
}
++VertIdx;
}
}
}