You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
1005 lines
32 KiB
C++
1005 lines
32 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#pragma once
|
|
|
|
#include "CoreMinimal.h"
|
|
#include "PrimitiveSceneProxy.h"
|
|
#include "VertexFactory.h"
|
|
#include "LocalVertexFactory.h"
|
|
#include "PrimitiveViewRelevance.h"
|
|
#include "Rendering/StaticMeshVertexBuffer.h"
|
|
#include "Rendering/PositionVertexBuffer.h"
|
|
#include "Rendering/ColorVertexBuffer.h"
|
|
#include "DynamicMeshBuilder.h"
|
|
#include "Components/BaseDynamicMeshComponent.h"
|
|
#include "RayTracingGeometry.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 FDynamicPrimitiveUniformBuffer;
|
|
class FMaterialRenderProxy;
|
|
class UMaterialInterface;
|
|
struct FRayTracingMaterialGatheringContext;
|
|
|
|
/**
|
|
* 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);
|
|
}
|
|
|
|
|
|
virtual ~FMeshRenderBufferSet()
|
|
{
|
|
check(IsInRenderingThread());
|
|
|
|
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.
|
|
*/
|
|
void Upload()
|
|
{
|
|
FRHICommandListBase& RHICmdList = FRHICommandListImmediate::Get();
|
|
|
|
if (TriangleCount == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
InitOrUpdateResource(&this->PositionVertexBuffer);
|
|
InitOrUpdateResource(&this->StaticMeshVertexBuffer);
|
|
InitOrUpdateResource(&this->ColorVertexBuffer);
|
|
|
|
FLocalVertexFactory::FDataType Data;
|
|
this->PositionVertexBuffer.BindPositionVertexBuffer(&this->VertexFactory, Data);
|
|
this->StaticMeshVertexBuffer.BindTangentVertexBuffer(&this->VertexFactory, Data);
|
|
this->StaticMeshVertexBuffer.BindPackedTexCoordVertexBuffer(&this->VertexFactory, Data);
|
|
// currently no lightmaps support
|
|
//this->StaticMeshVertexBuffer.BindLightMapVertexBuffer(&this->VertexFactory, Data, LightMapIndex);
|
|
this->ColorVertexBuffer.BindColorVertexBuffer(&this->VertexFactory, Data);
|
|
this->VertexFactory.SetData(Data);
|
|
|
|
InitOrUpdateResource(&this->VertexFactory);
|
|
PositionVertexBuffer.InitResource(RHICmdList);
|
|
StaticMeshVertexBuffer.InitResource(RHICmdList);
|
|
ColorVertexBuffer.InitResource(RHICmdList);
|
|
VertexFactory.InitResource(RHICmdList);
|
|
|
|
if (IndexBuffer.Indices.Num() > 0)
|
|
{
|
|
IndexBuffer.InitResource(RHICmdList);
|
|
}
|
|
if (bEnableSecondaryIndexBuffer && SecondaryIndexBuffer.Indices.Num() > 0)
|
|
{
|
|
SecondaryIndexBuffer.InitResource(RHICmdList);
|
|
}
|
|
|
|
InvalidateRayTracingData();
|
|
ValidateRayTracingData(); // currently we are immediately validating. This may be revisited in future.
|
|
}
|
|
|
|
|
|
/**
|
|
* 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?
|
|
|
|
check(IsInRenderingThread());
|
|
if (IndexBuffer.Indices.Num() > 0)
|
|
{
|
|
InitOrUpdateResource(&IndexBuffer);
|
|
}
|
|
if (bEnableSecondaryIndexBuffer && SecondaryIndexBuffer.Indices.Num() > 0)
|
|
{
|
|
InitOrUpdateResource(&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.
|
|
*/
|
|
void UploadVertexUpdate(bool bPositions, bool bMeshAttribs, bool bColors)
|
|
{
|
|
// todo: look at calls to this function, it seems possible that TransferVertexUpdateToGPU
|
|
// could be used instead (which should be somewhat more efficient?). It's not clear if there
|
|
// are any situations where we would change vertex buffer size w/o also updating the index
|
|
// buffers (in which case we are fully rebuilding the buffers...)
|
|
|
|
check(IsInRenderingThread());
|
|
|
|
if (TriangleCount == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (bPositions)
|
|
{
|
|
InitOrUpdateResource(&this->PositionVertexBuffer);
|
|
}
|
|
if (bMeshAttribs)
|
|
{
|
|
InitOrUpdateResource(&this->StaticMeshVertexBuffer);
|
|
}
|
|
if (bColors)
|
|
{
|
|
InitOrUpdateResource(&this->ColorVertexBuffer);
|
|
}
|
|
|
|
FLocalVertexFactory::FDataType Data;
|
|
this->PositionVertexBuffer.BindPositionVertexBuffer(&this->VertexFactory, Data);
|
|
this->StaticMeshVertexBuffer.BindTangentVertexBuffer(&this->VertexFactory, Data);
|
|
this->StaticMeshVertexBuffer.BindPackedTexCoordVertexBuffer(&this->VertexFactory, Data);
|
|
this->ColorVertexBuffer.BindColorVertexBuffer(&this->VertexFactory, Data);
|
|
this->VertexFactory.SetData(Data);
|
|
|
|
InitOrUpdateResource(&this->VertexFactory);
|
|
|
|
InvalidateRayTracingData();
|
|
ValidateRayTracingData(); // currently we are immediately validating. This may be revisited in future.
|
|
}
|
|
|
|
|
|
/**
|
|
* 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.
|
|
*/
|
|
void TransferVertexUpdateToGPU(FRHICommandListBase& RHICmdList, bool bPositions, bool bNormals, bool bTexCoords, bool bColors)
|
|
{
|
|
if (TriangleCount == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (bPositions)
|
|
{
|
|
FPositionVertexBuffer& VertexBuffer = this->PositionVertexBuffer;
|
|
void* VertexBufferData = RHICmdList.LockBuffer(VertexBuffer.VertexBufferRHI, 0, VertexBuffer.GetNumVertices() * VertexBuffer.GetStride(), RLM_WriteOnly);
|
|
FMemory::Memcpy(VertexBufferData, VertexBuffer.GetVertexData(), VertexBuffer.GetNumVertices() * VertexBuffer.GetStride());
|
|
RHICmdList.UnlockBuffer(VertexBuffer.VertexBufferRHI);
|
|
}
|
|
if (bNormals)
|
|
{
|
|
FStaticMeshVertexBuffer& VertexBuffer = this->StaticMeshVertexBuffer;
|
|
void* VertexBufferData = RHICmdList.LockBuffer(VertexBuffer.TangentsVertexBuffer.VertexBufferRHI, 0, VertexBuffer.GetTangentSize(), RLM_WriteOnly);
|
|
FMemory::Memcpy(VertexBufferData, VertexBuffer.GetTangentData(), VertexBuffer.GetTangentSize());
|
|
RHICmdList.UnlockBuffer(VertexBuffer.TangentsVertexBuffer.VertexBufferRHI);
|
|
}
|
|
if (bColors)
|
|
{
|
|
FColorVertexBuffer& VertexBuffer = this->ColorVertexBuffer;
|
|
void* VertexBufferData = RHICmdList.LockBuffer(VertexBuffer.VertexBufferRHI, 0, VertexBuffer.GetNumVertices() * VertexBuffer.GetStride(), RLM_WriteOnly);
|
|
FMemory::Memcpy(VertexBufferData, VertexBuffer.GetVertexData(), VertexBuffer.GetNumVertices() * VertexBuffer.GetStride());
|
|
RHICmdList.UnlockBuffer(VertexBuffer.VertexBufferRHI);
|
|
}
|
|
if (bTexCoords)
|
|
{
|
|
FStaticMeshVertexBuffer& VertexBuffer = this->StaticMeshVertexBuffer;
|
|
void* VertexBufferData = RHICmdList.LockBuffer(VertexBuffer.TexCoordVertexBuffer.VertexBufferRHI, 0, VertexBuffer.GetTexCoordSize(), RLM_WriteOnly);
|
|
FMemory::Memcpy(VertexBufferData, VertexBuffer.GetTexCoordData(), VertexBuffer.GetTexCoordSize());
|
|
RHICmdList.UnlockBuffer(VertexBuffer.TexCoordVertexBuffer.VertexBufferRHI);
|
|
}
|
|
|
|
InvalidateRayTracingData();
|
|
ValidateRayTracingData(); // currently we are immediately validating. This may be revisited in future.
|
|
}
|
|
|
|
UE_DEPRECATED(5.3, "TransferVertexUpdateToGPU now requires a command list")
|
|
void TransferVertexUpdateToGPU(bool bPositions, bool bNormals, bool bTexCoords, bool bColors)
|
|
{
|
|
TransferVertexUpdateToGPU(FRHICommandListImmediate::Get(), bPositions, bNormals, bTexCoords, 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;
|
|
|
|
RayTracingGeometry.SetInitializer(Initializer);
|
|
RayTracingGeometry.InitResource(RHICmdList);
|
|
|
|
FRayTracingGeometrySegment Segment;
|
|
Segment.VertexBuffer = PositionVertexBuffer.VertexBufferRHI;
|
|
Segment.NumPrimitives = RayTracingGeometry.Initializer.TotalPrimitiveCount;
|
|
Segment.MaxVertices = PositionVertexBuffer.GetNumVertices();
|
|
RayTracingGeometry.Initializer.Segments.Add(Segment);
|
|
|
|
RayTracingGeometry.UpdateRHI(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(FRenderResource* Resource)
|
|
{
|
|
FRHICommandListBase& RHICmdList = FRHICommandListImmediate::Get();
|
|
|
|
if (!Resource->IsInitialized())
|
|
{
|
|
Resource->InitResource(RHICmdList);
|
|
}
|
|
else
|
|
{
|
|
Resource->UpdateRHI(RHICmdList);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
protected:
|
|
friend class FBaseDynamicMeshSceneProxy;
|
|
|
|
/**
|
|
* 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)
|
|
{
|
|
if (BufferSet->TriangleCount == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
ENQUEUE_RENDER_COMMAND(FMeshRenderBufferSetDestroy)(
|
|
[BufferSet](FRHICommandListImmediate& RHICmdList)
|
|
{
|
|
delete BufferSet;
|
|
});
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
* FBaseDynamicMeshSceneProxy is an abstract base class for a Render Proxy
|
|
* for a UBaseDynamicMeshComponent, where the assumption is that mesh data
|
|
* will be stored in FMeshRenderBufferSet instances
|
|
*/
|
|
class FBaseDynamicMeshSceneProxy : public FPrimitiveSceneProxy
|
|
{
|
|
using FIndex2i = UE::Geometry::FIndex2i;
|
|
using FIndex3i = UE::Geometry::FIndex3i;
|
|
public:
|
|
UBaseDynamicMeshComponent* ParentBaseComponent;
|
|
|
|
/**
|
|
* 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;
|
|
|
|
protected:
|
|
// Set of currently-allocated RenderBuffers. We own these pointers and must clean them up.
|
|
// Must guard access with AllocatedSetsLock!!
|
|
TSet<FMeshRenderBufferSet*> AllocatedBufferSets;
|
|
|
|
// use to control access to AllocatedBufferSets
|
|
FCriticalSection AllocatedSetsLock;
|
|
|
|
// control raytracing support
|
|
bool bEnableRaytracing = false;
|
|
|
|
// Allow view-mode overrides.
|
|
bool bEnableViewModeOverrides = true;
|
|
|
|
public:
|
|
GEOMETRYFRAMEWORK_API FBaseDynamicMeshSceneProxy(UBaseDynamicMeshComponent* Component);
|
|
|
|
GEOMETRYFRAMEWORK_API virtual ~FBaseDynamicMeshSceneProxy();
|
|
|
|
|
|
//
|
|
// FBaseDynamicMeshSceneProxy API - subclasses must implement these functions
|
|
//
|
|
|
|
|
|
/**
|
|
* Return set of active renderbuffers. Must be implemented by subclass.
|
|
* This is the set of render buffers that will be drawn by GetDynamicMeshElements
|
|
*/
|
|
virtual void GetActiveRenderBufferSets(TArray<FMeshRenderBufferSet*>& Buffers) const = 0;
|
|
|
|
|
|
|
|
//
|
|
// RenderBuffer management
|
|
//
|
|
|
|
|
|
/**
|
|
* Allocates a set of render buffers. FPrimitiveSceneProxy will keep track of these
|
|
* buffers and destroy them on destruction.
|
|
*/
|
|
GEOMETRYFRAMEWORK_API virtual FMeshRenderBufferSet* AllocateNewRenderBufferSet();
|
|
|
|
/**
|
|
* Explicitly release a set of RenderBuffers
|
|
*/
|
|
GEOMETRYFRAMEWORK_API virtual void ReleaseRenderBufferSet(FMeshRenderBufferSet* BufferSet);
|
|
|
|
|
|
/**
|
|
* 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, TriangleEnumerable Enumerable,
|
|
const FDynamicMeshUVOverlay* UVOverlay,
|
|
const FDynamicMeshNormalOverlay* NormalOverlay,
|
|
const FDynamicMeshColorOverlay* ColorOverlay,
|
|
TFunctionRef<void(int, int, int, const FVector3f&, FVector3f&, FVector3f&)> TangentsFunc,
|
|
bool bTrackTriangles = false)
|
|
{
|
|
TArray<const FDynamicMeshUVOverlay*> UVOverlays;
|
|
UVOverlays.Add(UVOverlay);
|
|
InitializeBuffersFromOverlays(RenderBuffers, Mesh, NumTriangles, Enumerable,
|
|
UVOverlays, NormalOverlay, ColorOverlay, TangentsFunc, bTrackTriangles);
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* 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, 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)
|
|
{
|
|
RenderBuffers->TriangleCount = NumTriangles;
|
|
if (NumTriangles == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
bool bHaveColors = (ColorOverlay != nullptr) && (bIgnoreVertexColors == false);
|
|
|
|
int NumVertices = NumTriangles * 3;
|
|
int NumUVOverlays = UVOverlays.Num();
|
|
int NumTexCoords = FMath::Max(1, NumUVOverlays); // must have at least one tex coord
|
|
TArray<FIndex3i, TFixedAllocator<MAX_STATIC_TEXCOORDS>> UVTriangles;
|
|
UVTriangles.SetNum(NumTexCoords);
|
|
|
|
{
|
|
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
|
|
bool bBuildTriangleList = bTrackTriangles || bUseSecondaryTriBuffers;
|
|
if (bBuildTriangleList)
|
|
{
|
|
RenderBuffers->Triangles = TArray<int32>();
|
|
}
|
|
|
|
int TriIdx = 0, VertIdx = 0;
|
|
FVector3f TangentX, TangentY;
|
|
for (int TriangleID : Enumerable)
|
|
{
|
|
FIndex3i Tri = Mesh->GetTriangle(TriangleID);
|
|
for (int32 k = 0; k < NumTexCoords; ++k)
|
|
{
|
|
UVTriangles[k] = (k < NumUVOverlays && UVOverlays[k] != nullptr) ? UVOverlays[k]->GetTriangle(TriangleID) : FIndex3i::Invalid();
|
|
}
|
|
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;
|
|
}
|
|
|
|
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
|
|
TangentsFunc(Tri[j], TriangleID, j, Normal, TangentX, TangentY);
|
|
|
|
RenderBuffers->StaticMeshVertexBuffer.SetVertexTangents(VertIdx, TangentX, TangentY, Normal);
|
|
|
|
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);
|
|
}
|
|
|
|
FColor VertexFColor = (bHaveColors && TriColor[j] != FDynamicMesh3::InvalidID) ?
|
|
GetOverlayColorAsFColor(ColorOverlay, TriColor[j]) : UniformTriColor;
|
|
|
|
RenderBuffers->ColorVertexBuffer.VertexColor(VertIdx) = VertexFColor;
|
|
|
|
RenderBuffers->IndexBuffer.Indices[TriIdx++] = VertIdx; // currently TriIdx == VertIdx so we don't really need both...
|
|
VertIdx++;
|
|
}
|
|
|
|
if (bBuildTriangleList)
|
|
{
|
|
RenderBuffers->Triangles->Add(TriangleID);
|
|
}
|
|
}
|
|
|
|
// split triangles into secondary buffer (at bit redudant since we just built IndexBuffer, but we may optionally duplicate triangles in the future
|
|
if (bUseSecondaryTriBuffers)
|
|
{
|
|
RenderBuffers->bEnableSecondaryIndexBuffer = true;
|
|
UpdateSecondaryTriangleBuffer(RenderBuffers, Mesh, false);
|
|
}
|
|
}
|
|
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* 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 UpdateSecondaryTriangleBuffer(
|
|
FMeshRenderBufferSet* RenderBuffers,
|
|
const FDynamicMesh3* Mesh,
|
|
bool bDuplicate)
|
|
{
|
|
if (ensure(bUseSecondaryTriBuffers == true && RenderBuffers->Triangles.IsSet()) == false)
|
|
{
|
|
return;
|
|
}
|
|
|
|
const TArray<int32>& TriangleIDs = RenderBuffers->Triangles.GetValue();
|
|
int NumTris = TriangleIDs.Num();
|
|
TArray<uint32>& Indices = RenderBuffers->IndexBuffer.Indices;
|
|
TArray<uint32>& SecondaryIndices = RenderBuffers->SecondaryIndexBuffer.Indices;
|
|
|
|
RenderBuffers->SecondaryIndexBuffer.Indices.Reset();
|
|
if (bDuplicate == false)
|
|
{
|
|
RenderBuffers->IndexBuffer.Indices.Reset();
|
|
}
|
|
for ( int k = 0; k < NumTris; ++k)
|
|
{
|
|
int TriangleID = TriangleIDs[k];
|
|
bool bInclude = SecondaryTriFilterFunc(Mesh, TriangleID);
|
|
if (bInclude)
|
|
{
|
|
SecondaryIndices.Add(3*k);
|
|
SecondaryIndices.Add(3*k + 1);
|
|
SecondaryIndices.Add(3*k + 2);
|
|
}
|
|
else if (bDuplicate == false)
|
|
{
|
|
Indices.Add(3*k);
|
|
Indices.Add(3*k + 1);
|
|
Indices.Add(3*k + 2);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* 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 RecomputeRenderBufferTriangleIndexSets(
|
|
FMeshRenderBufferSet* RenderBuffers,
|
|
const FDynamicMesh3* Mesh)
|
|
{
|
|
if (RenderBuffers->TriangleCount == 0)
|
|
{
|
|
return;
|
|
}
|
|
if (ensure(RenderBuffers->Triangles.IsSet() && RenderBuffers->Triangles->Num() > 0) == false)
|
|
{
|
|
return;
|
|
}
|
|
|
|
//bool bDuplicate = false; // flag for future use, in case we want to draw all triangles in primary and duplicates in secondary...
|
|
RenderBuffers->IndexBuffer.Indices.Reset();
|
|
RenderBuffers->SecondaryIndexBuffer.Indices.Reset();
|
|
|
|
TArray<uint32>& Indices = RenderBuffers->IndexBuffer.Indices;
|
|
TArray<uint32>& SecondaryIndices = RenderBuffers->SecondaryIndexBuffer.Indices;
|
|
const TArray<int32>& TriangleIDs = RenderBuffers->Triangles.GetValue();
|
|
|
|
int NumTris = TriangleIDs.Num();
|
|
for (int k = 0; k < NumTris; ++k)
|
|
{
|
|
int TriangleID = TriangleIDs[k];
|
|
bool bInclude = SecondaryTriFilterFunc(Mesh, TriangleID);
|
|
if (bInclude)
|
|
{
|
|
SecondaryIndices.Add(3 * k);
|
|
SecondaryIndices.Add(3 * k + 1);
|
|
SecondaryIndices.Add(3 * k + 2);
|
|
}
|
|
else // if (bDuplicate == false)
|
|
{
|
|
Indices.Add(3 * k);
|
|
Indices.Add(3 * k + 1);
|
|
Indices.Add(3 * k + 2);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* 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, 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)
|
|
{
|
|
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++;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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, 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;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* @return number of active materials
|
|
*/
|
|
GEOMETRYFRAMEWORK_API virtual int32 GetNumMaterials() const;
|
|
|
|
/**
|
|
* Safe GetMaterial function that will never return nullptr
|
|
*/
|
|
GEOMETRYFRAMEWORK_API virtual UMaterialInterface* GetMaterial(int32 k) const;
|
|
|
|
/**
|
|
* Set whether or not to validate mesh batch materials against the component materials.
|
|
*/
|
|
void SetVerifyUsedMaterials(const bool bState)
|
|
{
|
|
bVerifyUsedMaterials = bState;
|
|
}
|
|
|
|
|
|
/**
|
|
* This needs to be called if the set of active materials changes, otherwise
|
|
* the check in FPrimitiveSceneProxy::VerifyUsedMaterial() will fail if an override
|
|
* material is set, if materials change, etc, etc
|
|
*/
|
|
GEOMETRYFRAMEWORK_API virtual void UpdatedReferencedMaterials();
|
|
|
|
|
|
//
|
|
// FBaseDynamicMeshSceneProxy implementation
|
|
//
|
|
|
|
/**
|
|
* If EngineShowFlags request vertex color rendering, returns the appropriate vertex color override material's render proxy. Otherwise returns nullptr.
|
|
*/
|
|
GEOMETRYFRAMEWORK_API static FMaterialRenderProxy* GetEngineVertexColorMaterialProxy(FMeshElementCollector& Collector, const FEngineShowFlags& EngineShowFlags, bool bProxyIsSelected, bool bIsHovered);
|
|
|
|
/**
|
|
* Render set of active RenderBuffers returned by GetActiveRenderBufferSets
|
|
*/
|
|
GEOMETRYFRAMEWORK_API virtual void GetDynamicMeshElements(
|
|
const TArray<const FSceneView*>& Views,
|
|
const FSceneViewFamily& ViewFamily,
|
|
uint32 VisibilityMap,
|
|
FMeshElementCollector& Collector) const override;
|
|
|
|
|
|
/**
|
|
* Draw a single-frame FMeshBatch for a FMeshRenderBufferSet
|
|
*/
|
|
GEOMETRYFRAMEWORK_API virtual void DrawBatch(FMeshElementCollector& Collector,
|
|
const FMeshRenderBufferSet& RenderBuffers,
|
|
const FDynamicMeshIndexBuffer32& IndexBuffer,
|
|
FMaterialRenderProxy* UseMaterial,
|
|
bool bWireframe,
|
|
ESceneDepthPriorityGroup DepthPriority,
|
|
int ViewIndex,
|
|
FDynamicPrimitiveUniformBuffer& DynamicPrimitiveUniformBuffer) const;
|
|
|
|
#if RHI_RAYTRACING
|
|
|
|
GEOMETRYFRAMEWORK_API virtual bool IsRayTracingRelevant() const override;
|
|
GEOMETRYFRAMEWORK_API virtual bool HasRayTracingRepresentation() const override;
|
|
|
|
GEOMETRYFRAMEWORK_API virtual void GetDynamicRayTracingInstances(FRayTracingMaterialGatheringContext& Context, TArray<FRayTracingInstance>& OutRayTracingInstances) override;
|
|
|
|
|
|
/**
|
|
* Draw a single-frame raytracing FMeshBatch for a FMeshRenderBufferSet
|
|
*/
|
|
GEOMETRYFRAMEWORK_API virtual void DrawRayTracingBatch(
|
|
FRayTracingMaterialGatheringContext& Context,
|
|
const FMeshRenderBufferSet& RenderBuffers,
|
|
const FDynamicMeshIndexBuffer32& IndexBuffer,
|
|
FRayTracingGeometry& RayTracingGeometry,
|
|
FMaterialRenderProxy* UseMaterialProxy,
|
|
ESceneDepthPriorityGroup DepthPriority,
|
|
FDynamicPrimitiveUniformBuffer& DynamicPrimitiveUniformBuffer,
|
|
TArray<FRayTracingInstance>& OutRayTracingInstances) const;
|
|
|
|
|
|
#endif // RHI_RAYTRACING
|
|
|
|
};
|