当前位置:网站首页>(UE4 4.27) customize primitivecomponent

(UE4 4.27) customize primitivecomponent

2022-06-12 06:04:00 Senior brother Dai Dai

Preface

stay UE4 In development , Usually used Mesh Yes StaticMesh,SkeletalMesh,ProceduralMesh wait , They all have corresponding rendering components, such as UStaticMeshComponent, UProceduralMeshComponent,  In essence, these Mesh Components are inherited UPrimitiveComponent, UPrimitiveComponent adopt FPrimitiveSceneProxy The rendering agent is responsible for translating a particular Mesh Rendering data for (VertexBuffer, IndexBuffer, Material) From the game thread to the rendering thread . Sometimes to customize something special Mesh Rendering , We have to customize the new PrimitiveComponent.( Of course UProceduralMeshComponent Often meets the need to customize new Mesh demand , But sometimes for further performance or special MeshPass Have to customize PrimitiveComponent).

Custom pyramids PrimitiveComponent

Now I will take an octahedron as an example to introduce customization PrimitiveComponent

Create a render proxy

The created render proxy contains VertexBuffer, IndexBuffer, Material related data

class FPyramidSceneProxy final : public FPrimitiveSceneProxy
{
public:
	SIZE_T GetTypeHash() const override
	{
		static size_t UniquePointer;
		return reinterpret_cast<size_t>(&UniquePointer);
	}

	FPyramidSceneProxy(const UPyramidComponent* InComponent);

	virtual ~FPyramidSceneProxy()
	{
		VertexBuffers.PositionVertexBuffer.ReleaseResource();
		VertexBuffers.StaticMeshVertexBuffer.ReleaseResource();
		VertexBuffers.ColorVertexBuffer.ReleaseResource();
		VertexFactory.ReleaseResource();
		IndexBuffer.ReleaseResource();
	}

	virtual void GetDynamicMeshElements(const TArray<const FSceneView *>& Views, const FSceneViewFamily& ViewFamily, uint32 VisibilityMap, class FMeshElementCollector& Collector) const override;

	virtual FPrimitiveViewRelevance GetViewRelevance(const FSceneView* View) const;

	void BuildPyramidMesh(const FPyramidMesh* PyramidMeshData, TArray<FDynamicMeshVertex>& OutVertices, TArray<uint32>& OutIndices);
	void SetMeshData_RenderThread(FPyramidMesh* PyramidMeshData);

	uint32 GetAllocatedSize(void) const { return(FPrimitiveSceneProxy::GetAllocatedSize()); }
	virtual uint32 GetMemoryFootprint(void) const override { return(sizeof(*this) + GetAllocatedSize()); }

private:
	/** Vertex buffer for this section */
	FStaticMeshVertexBuffers VertexBuffers;

	/** Index buffer for this section */
	FDynamicMeshIndexBuffer32 IndexBuffer;

	/** Vertex factory for this section */
	FLocalVertexFactory VertexFactory;

	FColor PyramidColor;

	UMaterialInterface* Material;

	FMaterialRelevance MaterialRelevance;
};

initialization VertexBuffer, IndexBuffer, Material

FPyramidSceneProxy::FPyramidSceneProxy(const UPyramidComponent* InComponent)
	: FPrimitiveSceneProxy(InComponent)
	, VertexFactory(GetScene().GetFeatureLevel(), "FPyramidSceneProxy")
	, PyramidColor(InComponent->ShapeColor)
	, MaterialRelevance(InComponent->GetMaterialRelevance(GetScene().GetFeatureLevel()))
{
	// Setup VertexData And IndexData
	TArray<FDynamicMeshVertex> Vertices;
	TArray<uint32> Indices;

	BuildPyramidMesh(&InComponent->PyramidMesh, Vertices, Indices);
	VertexBuffers.InitFromDynamicVertex(&VertexFactory, Vertices, 1);
	IndexBuffer.Indices = Indices;

	// Enqueue initialization of render resource
	BeginInitResource(&VertexBuffers.PositionVertexBuffer);
	BeginInitResource(&VertexBuffers.StaticMeshVertexBuffer);
	BeginInitResource(&VertexBuffers.ColorVertexBuffer);
	BeginInitResource(&IndexBuffer);
	BeginInitResource(&VertexFactory);

	Material = nullptr == InComponent->Material ? UMaterial::GetDefaultMaterial(MD_Surface) : InComponent->Material;
}

Cover GetDynamicMeshElements, Conduct MeshBatch Submission of

Mesh The vertex data of , Index data , Apex factory , texture of material Proxy etc.

void FPyramidSceneProxy::GetDynamicMeshElements(const TArray<const FSceneView *>& Views, const FSceneViewFamily& ViewFamily, uint32 VisibilityMap, class FMeshElementCollector& Collector) const
{
	QUICK_SCOPE_CYCLE_COUNTER(STAT_PyramidSceneProxy_GetDynamicMeshElements);

	// Set up wireframe material (if needed)
	const bool bWireframe = AllowDebugViewmodes() && ViewFamily.EngineShowFlags.Wireframe;
	
	FColoredMaterialRenderProxy* WireframeMaterialInstance = NULL;
	if (bWireframe)
	{
		WireframeMaterialInstance = new FColoredMaterialRenderProxy(
			GEngine->WireframeMaterial ? GEngine->WireframeMaterial->GetRenderProxy() : NULL,
			FLinearColor(0, 0.5f, 1.f)
		);

		Collector.RegisterOneFrameMaterialProxy(WireframeMaterialInstance);
	}


	FMaterialRenderProxy* MaterialProxy = bWireframe ? WireframeMaterialInstance : Material->GetRenderProxy();

	for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ++ViewIndex)
	{
		if (VisibilityMap & (1 << ViewIndex))
		{
			const FSceneView* View = Views[ViewIndex];
			
			//Draw Mesh
			FMeshBatch& Mesh = Collector.AllocateMesh();
			FMeshBatchElement& BatchElement = Mesh.Elements[0];
			BatchElement.IndexBuffer = &IndexBuffer;
			Mesh.bWireframe = bWireframe;
			Mesh.VertexFactory = &VertexFactory;
			Mesh.MaterialRenderProxy = MaterialProxy;

			bool bHasPrecomputedVolumetricLightmap;
			FMatrix PreviousLocalToWorld;
			int32 SingleCaptureIndex;
			bool bOutputVelocity;
			GetScene().GetPrimitiveUniformShaderParameters_RenderThread(GetPrimitiveSceneInfo(), bHasPrecomputedVolumetricLightmap, PreviousLocalToWorld, SingleCaptureIndex, bOutputVelocity);

			FDynamicPrimitiveUniformBuffer& DynamicPrimitiveUniformBuffer = Collector.AllocateOneFrameResource<FDynamicPrimitiveUniformBuffer>();
			DynamicPrimitiveUniformBuffer.Set(GetLocalToWorld(), PreviousLocalToWorld, GetBounds(), GetLocalBounds(), true, bHasPrecomputedVolumetricLightmap, DrawsVelocity(), bOutputVelocity);
			BatchElement.PrimitiveUniformBufferResource = &DynamicPrimitiveUniformBuffer.UniformBuffer;
			
			BatchElement.FirstIndex = 0;
			BatchElement.NumPrimitives = IndexBuffer.Indices.Num() / 3;
			BatchElement.MinVertexIndex = 0;
			BatchElement.MaxVertexIndex = VertexBuffers.PositionVertexBuffer.GetNumVertices() - 1;
			Mesh.ReverseCulling = IsLocalToWorldDeterminantNegative();
			Mesh.Type = PT_TriangleList;
			Mesh.DepthPriorityGroup = SDPG_World;
			Mesh.bCanApplyViewModeOverrides = false;
			Collector.AddMesh(ViewIndex, Mesh);
		}
	}
}

Cover GetViewRelevance, Set up Mesh Render state information

FPrimitiveViewRelevance FPyramidSceneProxy::GetViewRelevance(const FSceneView* View) const
{
	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 = IsMovable() && Result.bOpaque && Result.bRenderInMainPass;
	return Result;
}

Create an octahedral PrimitiveComponent

UCLASS(meta = (BlueprintSpawnableComponent), ClassGroup = Rendering)
class CUSTOMRENDERCOMPONENT_API UPyramidComponent : public UPrimitiveComponent
{
	GENERATED_BODY()
	
public:
	virtual FPrimitiveSceneProxy* CreateSceneProxy() override;

	//~ Begin UActorComponent Interface.
	virtual void OnRegister() override;

private:
	//~ Begin USceneComponent Interface.
	virtual FBoxSphereBounds CalcBounds(const FTransform& LocalToWorld) const override;
	//~ Begin USceneComponent Interface.

#if WITH_EDITOR
	void PostEditChangeProperty(struct FPropertyChangedEvent& PropertyChangedEvent) override;
#endif // WITH_EDITOR

	void RecreateMeshData();
	void UpdateLocalBounds();

public:
	void RecreateMesh();
	void SendMeshDataToRenderThread();

	UFUNCTION(BlueprintCallable)
	void SetCustomMaterial(UMaterialInterface* InMaterial);

	/** Accesses the scene relevance information for the materials applied to the mesh. Valid from game thread only. */
	FMaterialRelevance GetMaterialRelevance(ERHIFeatureLevel::Type InFeatureLevel) const;
	virtual void GetUsedMaterials(TArray<UMaterialInterface*>& OutMaterials, bool bGetDebugMaterials = false) const override;

	UFUNCTION(BlueprintCallable)
	void SetPyramidHeight(float NewPyramidHeight);

	UFUNCTION(BlueprintCallable)
	void SetPyramidBottomSize(FVector2D NewSize);

private:
	/** Local space bounds of mesh */
	UPROPERTY()
		FBoxSphereBounds LocalBounds;

	UPROPERTY(EditAnywhere)
		float PyramidHeight = 4.0f;

	UPROPERTY(EditAnywhere)
		FVector2D PyramidBottomSize = FVector2D(4.0f, 4.0f);

public:

	/** Color used to draw the shape. */
	UPROPERTY(EditAnywhere)
		FColor ShapeColor;

	UPROPERTY()
		FPyramidMesh PyramidMesh;

	UPROPERTY(EditAnywhere)
		UMaterialInterface* Material;
};

Octahedral construction Mesh Data functions

void UPyramidComponent::RecreateMeshData()
{
	PyramidMesh.Reset();

	//Vertex Pos
	FVector HeightPos = FVector(0.0f, 0.0f, PyramidHeight);
	FVector LowPos = FVector(0.0f, 0.0f, -PyramidHeight);
	float BoxSizeX = PyramidBottomSize.X / 2.0f;
	float BoxSizeY = PyramidBottomSize.Y / 2.0f;
	FVector RightTopPos = FVector(BoxSizeX, BoxSizeY, 0.0f);
	FVector RightDownPos = FVector(-BoxSizeX, BoxSizeY, 0.0f);
	FVector LeftTopPos = FVector(BoxSizeX, -BoxSizeY, 0.0f);
	FVector LeftDownPos = FVector(-BoxSizeX, -BoxSizeY, 0.0f);

	FVector FaceNormal1 = FVector::CrossProduct(RightTopPos - HeightPos, HeightPos - LeftTopPos);
	FaceNormal1.Normalize();
	FVector FaceNormal2 = FVector::CrossProduct(LeftTopPos - HeightPos, HeightPos - LeftDownPos);
	FaceNormal2.Normalize();
	FVector FaceNormal3 = FVector::CrossProduct(LeftDownPos - HeightPos, HeightPos - RightDownPos);
	FaceNormal3.Normalize();
	FVector FaceNormal4 = FVector::CrossProduct(RightDownPos - HeightPos, HeightPos - RightTopPos);
	FaceNormal4.Normalize();
	FVector FaceNormal5 = FVector::CrossProduct(LeftTopPos - LowPos, LowPos - RightTopPos);
	FaceNormal5.Normalize();
	FVector FaceNormal6 = FVector::CrossProduct(LeftDownPos - LowPos, LowPos - LeftTopPos);
	FaceNormal6.Normalize();
	FVector FaceNormal7 = FVector::CrossProduct(RightDownPos - LowPos, LowPos - LeftDownPos);
	FaceNormal7.Normalize();
	FVector FaceNormal8 = FVector::CrossProduct(RightTopPos - LowPos, LowPos - RightDownPos);
	FaceNormal8.Normalize();

	//Add Vertex
	//Up
	PyramidMesh.VertexArray.Add(FCustomMeshVertex(LeftTopPos, FaceNormal1));
	PyramidMesh.VertexArray.Add(FCustomMeshVertex(HeightPos, FaceNormal1));
	PyramidMesh.VertexArray.Add(FCustomMeshVertex(RightTopPos, FaceNormal1));

	PyramidMesh.VertexArray.Add(FCustomMeshVertex(LeftDownPos, FaceNormal2));
	PyramidMesh.VertexArray.Add(FCustomMeshVertex(HeightPos, FaceNormal2));
	PyramidMesh.VertexArray.Add(FCustomMeshVertex(LeftTopPos, FaceNormal2));

	PyramidMesh.VertexArray.Add(FCustomMeshVertex(RightDownPos, FaceNormal3));
	PyramidMesh.VertexArray.Add(FCustomMeshVertex(HeightPos, FaceNormal3));
	PyramidMesh.VertexArray.Add(FCustomMeshVertex(LeftDownPos, FaceNormal3));

	PyramidMesh.VertexArray.Add(FCustomMeshVertex(RightTopPos, FaceNormal4));
	PyramidMesh.VertexArray.Add(FCustomMeshVertex(HeightPos, FaceNormal4));
	PyramidMesh.VertexArray.Add(FCustomMeshVertex(RightDownPos, FaceNormal4));

	//Down
	PyramidMesh.VertexArray.Add(FCustomMeshVertex(RightTopPos, FaceNormal5));
	PyramidMesh.VertexArray.Add(FCustomMeshVertex(LowPos, FaceNormal5));
	PyramidMesh.VertexArray.Add(FCustomMeshVertex(LeftTopPos, FaceNormal5));

	PyramidMesh.VertexArray.Add(FCustomMeshVertex(LeftTopPos, FaceNormal6));
	PyramidMesh.VertexArray.Add(FCustomMeshVertex(LowPos, FaceNormal6));
	PyramidMesh.VertexArray.Add(FCustomMeshVertex(LeftDownPos, FaceNormal6));

	PyramidMesh.VertexArray.Add(FCustomMeshVertex(LeftDownPos, FaceNormal7));
	PyramidMesh.VertexArray.Add(FCustomMeshVertex(LowPos, FaceNormal7));
	PyramidMesh.VertexArray.Add(FCustomMeshVertex(RightDownPos, FaceNormal7));

	PyramidMesh.VertexArray.Add(FCustomMeshVertex(RightDownPos, FaceNormal8));
	PyramidMesh.VertexArray.Add(FCustomMeshVertex(LowPos, FaceNormal8));
	PyramidMesh.VertexArray.Add(FCustomMeshVertex(RightTopPos, FaceNormal8));

	for (int32 Index = 0; Index < 24; ++Index)
	{
		PyramidMesh.IndexArray.Add(Index);
	}

	UpdateLocalBounds();
}

Override create render proxy function

FPrimitiveSceneProxy* UPyramidComponent::CreateSceneProxy()
{
	return new FPyramidSceneProxy(this);
}

Override build bounding volume function

Mesh The bounding box of visual vertebra elimination is through CalcBounds Function transitive , Every time you build a new Mesh Remember to call when the data UpdateLocalBounds and  MarkRenderTransformDirty Update the size of the weeding bounding box

FBoxSphereBounds UPyramidComponent::CalcBounds(const FTransform& LocalToWorld) const
{
	FBoxSphereBounds Ret(LocalBounds.TransformBy(LocalToWorld));

	Ret.BoxExtent *= BoundsScale;
	Ret.SphereRadius *= BoundsScale;

	return Ret;
}


void UPyramidComponent::UpdateLocalBounds()
{
	FBox LocalBox(ForceInit);

	for(auto& MeshVertex : PyramidMesh.VertexArray)
	{
		LocalBox += MeshVertex.Position;
	}

	LocalBounds = LocalBox.IsValid ? FBoxSphereBounds(LocalBox) : FBoxSphereBounds(FVector(0, 0, 0), FVector(0, 0, 0), 0); // fallback to reset box sphere bounds

	// Update global bounds
	UpdateBounds();
	// Need to send to render thread
	MarkRenderTransformDirty();
}

UPyramidComponent Update data to FPrimitiveSceneProxy

When you set the new octahedral height , The corresponding vertex data will change , There are two ways to update data :

(1) Render dirty marks , cause Rendering Component Re Commission CreateSceneProxy, Create a new FPrimitiveSceneProxy, According to again Component Data to build new VertexBuffer, IndexBuffer wait . However, it is not good to destroy and create resources repeatedly . When you set a new material, it is marked as dirty .

(2) Another way is Component Update the vertex cache to be performed by rendering instructions . Specifically in GameThread Directly through ENQUEUE_RENDER_COMMAND Pass the built data changes to the rendering thread FPrimitiveSceneProxy, This is suitable for frequent changes VertexBuffer, IndexBuffer Parametric Mesh.

void UPyramidComponent::SendMeshDataToRenderThread()
{
	FPyramidMesh* NewPyramidMesh = new FPyramidMesh;
	*NewPyramidMesh = PyramidMesh;

	// Enqueue command to set vertex data to renderthread
	FPyramidSceneProxy* PyramidSceneProxy = (FPyramidSceneProxy*)SceneProxy;

	if (PyramidSceneProxy)
	{
		ENQUEUE_RENDER_COMMAND(FPyramidMeshData)(
			[PyramidSceneProxy, NewPyramidMesh](FRHICommandListImmediate& RHICmdList)
			{
				PyramidSceneProxy->SetMeshData_RenderThread(NewPyramidMesh);
			}
		);

	}
}

void FPyramidSceneProxy::SetMeshData_RenderThread(FPyramidMesh* NewPyramidMeshData)
{
	check(IsInRenderingThread());

	//FCustomVertex To FDynMeshVertexildPyramidMesh(NewPyramidMeshData, Vertices, Indices);
	TArray<FDynamicMeshVertex> Vertices;
	TArray<uint32> Indices;
	BuildPyramidMesh(NewPyramidMeshData, Vertices, Indices);

	for (int32 Index = 0; Index < Vertices.Num(); Index++)
	{
		const FDynamicMeshVertex& Vertex = Vertices[Index];

		VertexBuffers.PositionVertexBuffer.VertexPosition(Index) = Vertex.Position;
		VertexBuffers.StaticMeshVertexBuffer.SetVertexTangents(Index, Vertex.TangentX.ToFVector(), Vertex.GetTangentY(), Vertex.TangentZ.ToFVector());
		VertexBuffers.StaticMeshVertexBuffer.SetVertexUV(Index, 0, Vertex.TextureCoordinate[0]);
		VertexBuffers.ColorVertexBuffer.VertexColor(Index) = Vertex.Color;
	}

	{
		auto& VertexBuffer = VertexBuffers.PositionVertexBuffer;
		void* VertexBufferData = RHILockVertexBuffer(VertexBuffer.VertexBufferRHI, 0, VertexBuffer.GetNumVertices() * VertexBuffer.GetStride(), RLM_WriteOnly);
		FMemory::Memcpy(VertexBufferData, VertexBuffer.GetVertexData(), VertexBuffer.GetNumVertices() * VertexBuffer.GetStride());
		RHIUnlockVertexBuffer(VertexBuffer.VertexBufferRHI);
	}

	{
		auto& VertexBuffer = VertexBuffers.ColorVertexBuffer;
		void* VertexBufferData = RHILockVertexBuffer(VertexBuffer.VertexBufferRHI, 0, VertexBuffer.GetNumVertices() * VertexBuffer.GetStride(), RLM_WriteOnly);
		FMemory::Memcpy(VertexBufferData, VertexBuffer.GetVertexData(), VertexBuffer.GetNumVertices() * VertexBuffer.GetStride());
		RHIUnlockVertexBuffer(VertexBuffer.VertexBufferRHI);
	}

	{
		auto& VertexBuffer = VertexBuffers.StaticMeshVertexBuffer;
		void* VertexBufferData = RHILockVertexBuffer(VertexBuffer.TangentsVertexBuffer.VertexBufferRHI, 0, VertexBuffer.GetTangentSize(), RLM_WriteOnly);
		FMemory::Memcpy(VertexBufferData, VertexBuffer.GetTangentData(), VertexBuffer.GetTangentSize());
		RHIUnlockVertexBuffer(VertexBuffer.TangentsVertexBuffer.VertexBufferRHI);
	}

	{
		auto& VertexBuffer = VertexBuffers.StaticMeshVertexBuffer;
		void* VertexBufferData = RHILockVertexBuffer(VertexBuffer.TexCoordVertexBuffer.VertexBufferRHI, 0, VertexBuffer.GetTexCoordSize(), RLM_WriteOnly);
		FMemory::Memcpy(VertexBufferData, VertexBuffer.GetTexCoordData(), VertexBuffer.GetTexCoordSize());
		RHIUnlockVertexBuffer(VertexBuffer.TexCoordVertexBuffer.VertexBufferRHI);
	}

	if (NewPyramidMeshData)
	{
		delete NewPyramidMeshData;
		NewPyramidMeshData = nullptr;
	}
}

Results show

Here, you should pay attention to setting the material  MarkRenderStateDirtyMarkRenderStateDirty Will make UPyramidComponent Create a new FPrimitiveSceneProxy.

Octahedral rendering component source code link

https://download.csdn.net/download/qq_29523119/35627759

Reference material

【1】CableComponent.h and  ProceduralMeshComponent.h

【2】https://medium.com/@lordned/unreal-engine-4-rendering-part-2-shaders-and-vertex-data-80317e1ae5f3

原网站

版权声明
本文为[Senior brother Dai Dai]所创,转载请带上原文链接,感谢
https://yzsam.com/2022/03/202203010613123384.html