欢迎大家加入虚幻4技术美术 ,图形渲染和各种游戏效果实现学习群。大家一起学习一起讨论。192946459
最近在研究虚幻4的渲染组件,我会一一介绍以下几个组件:
SceneComponent
PrimitiveComponent
MeshComponent
CustomMeshComponent
ProcedureMeshComponent
CableComponent
我们一层一层研究,就会对虚幻的渲染管线的application阶段有一个很好的认识,比如如何自己画一个模型,如何动态更新顶点缓冲区如何动态更新索引缓冲区。如何上传顶点缓冲区等。希望有兴趣研究虚幻4图形效果的朋友能一起来探讨。
欢迎大家加入虚幻4技术美术 ,图形渲染和各种游戏效果实现学习群。大家一起学习一起讨论。192946459
下面先开始讨论SceneComponent和PrimitiveComponent
SceneComponent是虚幻引擎里面一个十分基础的组件。主要负责Transform的相关逻辑,并且提供了大量接口,如移动缩放位移,可见性,附加等基础功能。当然在它的下层还有ActorComponent,这里就不讨论了,有兴趣的可以去翻翻源码。
PrimitiveComponent是虚幻4渲染组件,包含了大量渲染相关的逻辑,也是游戏线程和逻辑线程沟通的媒介。虚幻中的Staticmeshcomponent SkeletalMeshcomponent CustomMeshComponent ProcedurMeshComponent CableMeshComponent都从它派生而来。它不仅和渲染引擎有千丝万缕的关系,它还和物理引擎打交道,其内部有大量和物理交互接口。我们这里主要研究渲染部分,那么我们应该重点关心primitiveComponent的场景代理(SceneProxy)。
/**
* Creates a proxy to represent the primitive to the scene manager in the rendering thread.
* @return The proxy object.
*/
virtual FPrimitiveSceneProxy* CreateSceneProxy()
{
return NULL;
}
这个函数在primitiveComponent中没有任何逻辑,以后我们如果想要画个模型什么的需要我们自己重写这个函数,重写这个类。
那么到底如何画一个模型呢,如何上传顶点缓冲和索引缓冲呢,下面我们打开CustomMeshComponent的代码,这个组件相当简单,但是很清晰地告诉了我们如何完成模型绘制。
下面是CustomMeshComponent.h的代码,非常简单,一共就60多行。但是比较完整展示了虚幻引擎渲染管线的图元汇编阶段的实现。
#pragma once
#include "CoreMinimal.h"
#include "UObject/ObjectMacros.h"
#include "Components/MeshComponent.h"
#include "CustomMeshComponent.generated.h"
class FPrimitiveSceneProxy;
USTRUCT(BlueprintType)
struct FCustomMeshTriangle
{
GENERATED_USTRUCT_BODY()
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category=Triangle)
FVector Vertex0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category=Triangle)
FVector Vertex1;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category=Triangle)
FVector Vertex2;
};
/** Component that allows you to specify custom triangle mesh geometry */
UCLASS(hidecategories=(Object,LOD, Physics, Collision), editinlinenew, meta=(BlueprintSpawnableComponent), ClassGroup=Rendering)
class CUSTOMMESHCOMPONENT_API UCustomMeshComponent : public UMeshComponent
{
GENERATED_UCLASS_BODY()
/** Set the geometry to use on this triangle mesh */
UFUNCTION(BlueprintCallable, Category="Components|CustomMesh")
bool SetCustomMeshTriangles(const TArray<FCustomMeshTriangle>& Triangles);
/** Add to the geometry to use on this triangle mesh. This may cause an allocation. Use SetCustomMeshTriangles() instead when possible to reduce allocations. */
UFUNCTION(BlueprintCallable, Category = "Components|CustomMesh")
void AddCustomMeshTriangles(const TArray<FCustomMeshTriangle>& Triangles);
/** Removes all geometry from this triangle mesh. Does not deallocate memory, allowing new geometry to reuse the existing allocation. */
UFUNCTION(BlueprintCallable, Category = "Components|CustomMesh")
void ClearCustomMeshTriangles();
private:
//~ Begin UPrimitiveComponent Interface.
virtual FPrimitiveSceneProxy* CreateSceneProxy() override;
//~ End UPrimitiveComponent Interface.
//~ Begin UMeshComponent Interface.
virtual int32 GetNumMaterials() const override;
//~ End UMeshComponent Interface.
//~ Begin USceneComponent Interface.
virtual FBoxSphereBounds CalcBounds(const FTransform& LocalToWorld) const override;
//~ Begin USceneComponent Interface.
/** */
TArray<FCustomMeshTriangle> CustomMeshTris;
friend class FCustomMeshSceneProxy;
};
首先它前置声明了对渲染至关重要的class FPrimitiveSceneProxy这个类。然后重写了PrimitiveComponent中的 virtual FPrimitiveSceneProxy* CreateSceneProxy() override;这个接口。这个接口用于创建场景代理,然后重写了 virtual FBoxSphereBounds CalcBounds(const FTransform& LocalToWorld) const override;这个接口用于定义渲染包围盒。然后还定义了一些用于更新上传顶点缓冲区的函数接口。最后的一句话是将FCustomMeshSceneProxy;这个类声明为friend。这个类是哪里来的呢,是我们自己定义的。他的定义位置在CustommeshComponent.cpp中,由SceneProxy派生而来。下面我们就看看customMeshComponent.cpp中的逻辑。
要渲染模型,肯定我们需要提供渲染资源,那么下一步我们将重写各种资源类,为渲染自己的模型提供渲染资源,由于这是渲染的开始,所以我们需要创建顶点缓冲 索引缓冲 输入布局等资源。
首先创建顶点缓冲区。这个只是作为图元汇编阶段的渲染资源
/** Vertex Buffer */
class FCustomMeshVertexBuffer : public FVertexBuffer
{
public:
TArray<FDynamicMeshVertex> Vertices;
virtual void InitRHI() override
{
FRHIResourceCreateInfo CreateInfo;
void* VertexBufferData = nullptr;
VertexBufferRHI = RHICreateAndLockVertexBuffer(Vertices.Num() * sizeof(FDynamicMeshVertex), BUF_Static, CreateInfo, VertexBufferData);
// Copy the vertex data into the vertex buffer.
FMemory::Memcpy(VertexBufferData,Vertices.GetData(),Vertices.Num() * sizeof(FDynamicMeshVertex));
RHIUnlockVertexBuffer(VertexBufferRHI);
}
};
然后是索引缓冲区资源
/** Index Buffer */
class FCustomMeshIndexBuffer : public FIndexBuffer
{
public:
TArray<int32> Indices;
virtual void InitRHI() override
{
FRHIResourceCreateInfo CreateInfo;
void* Buffer = nullptr;
IndexBufferRHI = RHICreateAndLockIndexBuffer(sizeof(int32),Indices.Num() * sizeof(int32),BUF_Static, CreateInfo, Buffer);
// Write the indices to the index buffer.
FMemory::Memcpy(Buffer,Indices.GetData(),Indices.Num() * sizeof(int32));
RHIUnlockIndexBuffer(IndexBufferRHI);
}
};
虚幻里面还有一个控制InputLayer的类,它就是FLocalVertexFactory
/** Vertex Factory */
class FCustomMeshVertexFactory : public FLocalVertexFactory
{
public:
FCustomMeshVertexFactory()
{}
/** Init function that should only be called on render thread. */
void Init_RenderThread(const FCustomMeshVertexBuffer* VertexBuffer)
{
check(IsInRenderingThread());
FDataType NewData;
NewData.PositionComponent = STRUCTMEMBER_VERTEXSTREAMCOMPONENT(VertexBuffer, FDynamicMeshVertex, Position, VET_Float3);
NewData.TextureCoordinates.Add(
FVertexStreamComponent(VertexBuffer, STRUCT_OFFSET(FDynamicMeshVertex, TextureCoordinate), sizeof(FDynamicMeshVertex), VET_Float2)
);
NewData.TangentBasisComponents[0] = STRUCTMEMBER_VERTEXSTREAMCOMPONENT(VertexBuffer, FDynamicMeshVertex, TangentX, VET_PackedNormal);
NewData.TangentBasisComponents[1] = STRUCTMEMBER_VERTEXSTREAMCOMPONENT(VertexBuffer, FDynamicMeshVertex, TangentZ, VET_PackedNormal);
NewData.ColorComponent = STRUCTMEMBER_VERTEXSTREAMCOMPONENT(VertexBuffer, FDynamicMeshVertex, Color, VET_Color);
SetData(NewData);
}
/** Initialization */
void Init(const FCustomMeshVertexBuffer* VertexBuffer)
{
if (IsInRenderingThread())
{
Init_RenderThread(VertexBuffer);
}
else
{
ENQUEUE_UNIQUE_RENDER_COMMAND_TWOPARAMETER(
InitCustomMeshVertexFactory,
FCustomMeshVertexFactory*, VertexFactory, this,
const FCustomMeshVertexBuffer*, VertexBuffer, VertexBuffer,
{
VertexFactory->Init_RenderThread(VertexBuffer);
});
}
}
};
我这里不详细讲每行代码的意思,你可以去翻阅最近一位大神写的书(大象无形)里面有一个章节专门讲了上面那些代码的意思,我这里旨在理清楚组件的实现方式。
有了以上的渲染资源之后,下一步我们需要把这些资源提交给渲染引擎。那么就用到了我们前面一直在关注的SceneProxy
下面虚幻的CustomMeshComponent重写了这个类
class FCustomMeshSceneProxy : public FPrimitiveSceneProxy
这个类各种函数和成员如下
@构造函数用于初始化各种资源和成员 FCustomMeshSceneProxy(UCustomMeshComponent* Component)
: FPrimitiveSceneProxy(Component)
@析构函数,用于回收各种资源virtual ~FCustomMeshSceneProxy()
@重写了绘制函数virtual void GetDynamicMeshElements。可以理解为虚幻的drawcall
@virtual FPrimitiveViewRelevance GetViewRelevance(const FSceneView* View) const override
@virtual bool CanBeOccluded() const override
@virtual uint32 GetMemoryFootprint( void ) const override { return( sizeof( *this ) + GetAllocatedSize() ); }
下面是FCustomMeshSceneProxy的私有变量
private:
UMaterialInterface* Material; 材质
FCustomMeshVertexBuffer VertexBuffer; 顶点缓冲区
FCustomMeshIndexBuffer IndexBuffer; 索引缓冲
FCustomMeshVertexFactory VertexFactory; 顶点输入布局工厂
FMaterialRelevance MaterialRelevance;
然后让CustomMeshComponent创建FCustomMeshSceneProxy场景代理实例
FPrimitiveSceneProxy* UCustomMeshComponent::CreateSceneProxy()
{
FPrimitiveSceneProxy* Proxy = NULL;
if(CustomMeshTris.Num() > 0)
{
Proxy = new FCustomMeshSceneProxy(this);
}
return Proxy;
}
渲染包围盒的创建,如果不创建包围盒,组件将无法被渲染。
FBoxSphereBounds UCustomMeshComponent::CalcBounds(const FTransform& LocalToWorld) const
{
FBoxSphereBounds NewBounds;
NewBounds.Origin = FVector::ZeroVector;
NewBounds.BoxExtent = FVector(HALF_WORLD_MAX,HALF_WORLD_MAX,HALF_WORLD_MAX);
NewBounds.SphereRadius = FMath::Sqrt(3.0f * FMath::Square(HALF_WORLD_MAX));
return NewBounds;
}
不知道有没有人发现,虚幻4官方的CustomMeshComponent没有uv,无法投射贴图,那是因为CustommeshComponent根本就没定义顶点的uv和tangent。下一章介绍ProcedurMeshComponent的时候我会和大家一起改造CustomMeshComponent组件。本人才疏学浅,来这里发帖也是希望大家能加入我们一起讨论。欢迎大家加入虚幻4技术美术 ,图形渲染和各种游戏效果实现学习群。大家一起学习一起讨论。192946459
|