UE4中的UProceduralMeshComponent组件可以用于绘制自定义形状的模型,该组件位于ProceduralMeshComponent插件中,如果要引用该插件中的功能就要在XXXX.build.cs文件中加入对ProceduralMeshComponent模块的引用,如下所示
PublicDependencyModuleNames.AddRange(new string[] {......, "ProceduralMeshComponent"});
UProceduralMeshComponent类中CreateMeshSection_LinearColor函数用于根据顶点数组等数据创建模型,UpdateMeshSection_LinearColor用于更新模型,ClearMeshSection和ClearAllMeshSections用于删除模型。
本文用CreateMeshSection_LinearColor演示如何创建三角形面片,四边形面片以及如何根据一组折线上的点画出指定宽度的折线面片。
首先创建一个CustomMeshActor.h和CustomMeshActor.cpp文件,定义一个ACustomMeshActor类,类中有一个UProceduralMeshComponent组件成员变量,代码如下
//CustomMeshActor.h
#pragma once
#include"CoreMinimal.h"
#include"GameFramework/Actor.h"
#include"ProceduralMeshComponent.h"
#include"CustomMeshActor.generated.h"
UCLASS()
class THIRDPERSON_API ACustomMeshActor :public AActor
{
GENERATED_UCLASS_BODY()
public:
ACustomMeshActor();
UPROPERTY()
UProceduralMeshComponent * DrawMeshComponent;
};
//CustomMeshActor.cpp
#include "CustomMeshActor.h"
ACustomMeshActor::ACustomMeshActor(const FObjectInitializer& ObjectInitializer)
{
// Set this actor to call Tick() every frame. You can turn this off to improve performance if you don't need it.
PrimaryActorTick.bCanEverTick = true;
RootComponent = CreateDefaultSubobject<USceneComponent>("Root");
DrawMeshComponent = CreateDefaultSubobject<UProceduralMeshComponent>(TEXT("DrawMeshComponent"));
DrawMeshComponent->SetupAttachment(RootComponent);
}
1、三角形面片
定义DrawTriangle函数用于绘制三角形面片
void ACustomMeshActor::DrawTriangle(UMaterialInterface * InMaterial)
{
TArray<FVector> vertices;
TArray<int32> triangles;
vertices.Add(FVector(0,0,0));
vertices.Add(FVector(0,100,0));
vertices.Add(FVector(100,0,0));
triangles.Add(0);
triangles.Add(1);
triangles.Add(2);
TArray<FVector> normals;
TArray<FProcMeshTangent> tangents;
TArray<FLinearColor> vertexColors;
TArray<FVector2D> uvs;
DrawMeshComponent->CreateMeshSection_LinearColor(0, vertices, triangles, normals, uvs, vertexColors, tangents, true);
DrawMeshComponent->SetMaterial(0, InMaterial);
}
上面的代码画一个由(0,0,0),(0,100,0)和(100,0,0)三个顶点围成的三角形,该三角形的材质以函数的参数传入。
画一个三角形需要三个顶点和三个顶点索引,也就是上面的vertices和triangles,其中triangles是顶点的索引,必须是3的倍数,3个索引表示一个三角形,而且三个索引对应的顶点必须逆时针排列才能看到该三角面(逆时针是根据视角决定的,如从上往下看和从下往上看的逆时针方向不一样。此外如果需要从两个相反的方向都能看到三角面,可以将材质设置为双面材质)。
在编辑中创建一个基类为ACustomMeshActor的蓝图类BPCustomMeshActor,在ConstructionScript函数中使用DrawTriangle函数就可以看到绘制出来的三角形面片,材质使用了纯红色的自定义材质RedMaterial,如下图所示
2、四边形片面
定义DrawPlane函数用于绘制四边形面片,四边形可以由两个三角形组成,因此有6个索引,而顶点只有4个
void ACustomMeshActor::DrawPlane(UMaterialInterface * InMaterial)
{
TArray<FVector> vertices;
TArray<int32> triangles;
vertices.Add(FVector(0, 0, 0));
vertices.Add(FVector(0, 100, 0));
vertices.Add(FVector(100, 100, 0));
vertices.Add(FVector(100, 0, 0));
triangles.Add(0);
triangles.Add(1);
triangles.Add(2);
triangles.Add(2);
triangles.Add(3);
triangles.Add(0);
TArray<FVector> normals;
TArray<FProcMeshTangent> tangents;
TArray<FLinearColor> vertexColors;
TArray<FVector2D> uvs;
DrawMeshComponent->CreateMeshSection_LinearColor(0, vertices, triangles, normals, uvs, vertexColors, tangents, true);
DrawMeshComponent->SetMaterial(0, InMaterial);
}
效果如下图
3、折线面片
定义DrawLine用于绘制折线面片,InPoints是折线上的点,InWidth是线的宽度,为了简单起见这里只画XY平面的折线,即要确保InPoints中点的Z轴值为0或全都相等,
void ACustomMeshActor::DrawLine(UMaterialInterface * InMaterial,const TArray<FVector>& InPoints, float InWidth)
{
TArray<FVector> vertices;
TArray<int32> triangles;
int32 num = InPoints.Num();
if (num < 2)
{
return;
}
for (int32 i = 0; i < num; i++)
{
FVector right(0, -1, 0);
if (i == num - 1)
{
right = FVector::CrossProduct(InPoints[i] - InPoints[i-1], FVector(0, 0, 1));
}
else
{
right = FVector::CrossProduct(InPoints[i+1] - InPoints[i], FVector(0, 0, 1));
}
right.Normalize();
right = -right;//右手座标系转左手座标系
FVector pt1 = InPoints[i] - right * InWidth / 2;
FVector pt2 = InPoints[i] + right * InWidth / 2;
vertices.Add(pt1);
vertices.Add(pt2);
int vNum = vertices.Num();
if (i > 0)
{
triangles.Add(vNum - 4);
triangles.Add(vNum - 3);
triangles.Add(vNum - 2);
triangles.Add(vNum - 2);
triangles.Add(vNum - 3);
triangles.Add(vNum - 1);
}
}
TArray<FVector> normals;
TArray<FProcMeshTangent> tangents;
TArray<FLinearColor> vertexColors;
TArray<FVector2D> uvs;
DrawMeshComponent->CreateMeshSection_LinearColor(0, vertices, triangles, normals, uvs, vertexColors, tangents, true);
DrawMeshComponent->SetMaterial(0, InMaterial);
}
right是该线段的右向单位向量,也就是在XY二维平面上与该线段垂直的向量,加减right向量就是将点左右平移,triangles添加索引与vertices加入顶点的顺序有关。绘制出来后如下图所示
上面几个例子中normals;tangents;vertexColors;uvs;这些变量都没有添加值,具体使用时可根据需要添加,尤其是uvs一般都会有值的。
附上完整的代码:
CustomMeshActor.h
#pragma once
#include"CoreMinimal.h"
#include"GameFramework/Actor.h"
#include"ProceduralMeshComponent.h"
#include"CustomMeshActor.generated.h"
UCLASS()
class THIRDPERSON_API ACustomMeshActor :public AActor
{
GENERATED_UCLASS_BODY()
public:
ACustomMeshActor();
UFUNCTION(BlueprintCallable, Category = "CustomMeshActor")
void DrawTriangle(UMaterialInterface* InMaterial);
UFUNCTION(BlueprintCallable, Category = "CustomMeshActor")
void DrawPlane(UMaterialInterface* InMaterial);
UFUNCTION(BlueprintCallable, Category = "CustomMeshActor")
void DrawLine(UMaterialInterface * InMaterial, const TArray<FVector>& InPoints, float InWidth);
UPROPERTY()
UProceduralMeshComponent * DrawMeshComponent;
};
CustomMeshActor.cpp
#include "CustomMeshActor.h"
ACustomMeshActor::ACustomMeshActor(const FObjectInitializer& ObjectInitializer)
{
// Set this actor to call Tick() every frame. You can turn this off to improve performance if you don't need it.
PrimaryActorTick.bCanEverTick = true;
RootComponent = CreateDefaultSubobject<USceneComponent>("Root");
DrawMeshComponent = CreateDefaultSubobject<UProceduralMeshComponent>(TEXT("DrawMeshComponent"));
DrawMeshComponent->SetupAttachment(RootComponent);
}
void ACustomMeshActor::DrawTriangle(UMaterialInterface * InMaterial)
{
TArray<FVector> vertices;
TArray<int32> triangles;
vertices.Add(FVector(0,0,0));
vertices.Add(FVector(0,100,0));
vertices.Add(FVector(100,0,0));
triangles.Add(0);
triangles.Add(1);
triangles.Add(2);
TArray<FVector> normals;
TArray<FProcMeshTangent> tangents;
TArray<FLinearColor> vertexColors;
TArray<FVector2D> uvs;
DrawMeshComponent->CreateMeshSection_LinearColor(0, vertices, triangles, normals, uvs, vertexColors, tangents, true);
DrawMeshComponent->SetMaterial(0, InMaterial);
}
void ACustomMeshActor::DrawPlane(UMaterialInterface * InMaterial)
{
TArray<FVector> vertices;
TArray<int32> triangles;
vertices.Add(FVector(0, 0, 0));
vertices.Add(FVector(0, 100, 0));
vertices.Add(FVector(100, 100, 0));
vertices.Add(FVector(100, 0, 0));
triangles.Add(0);
triangles.Add(1);
triangles.Add(2);
triangles.Add(2);
triangles.Add(3);
triangles.Add(0);
TArray<FVector> normals;
TArray<FProcMeshTangent> tangents;
TArray<FLinearColor> vertexColors;
TArray<FVector2D> uvs;
DrawMeshComponent->CreateMeshSection_LinearColor(0, vertices, triangles, normals, uvs, vertexColors, tangents, true);
DrawMeshComponent->SetMaterial(0, InMaterial);
}
void ACustomMeshActor::DrawLine(UMaterialInterface * InMaterial,const TArray<FVector>& InPoints, float InWidth)
{
TArray<FVector> vertices;
TArray<int32> triangles;
int32 num = InPoints.Num();
if (num < 2)
{
return;
}
for (int32 i = 0; i < num; i++)
{
FVector right(0, -1, 0);
if (i == num - 1)
{
right = FVector::CrossProduct(InPoints[i] - InPoints[i-1], FVector(0, 0, 1));
}
else
{
right = FVector::CrossProduct(InPoints[i+1] - InPoints[i], FVector(0, 0, 1));
}
right.Normalize();
right = -right;//右手座标系转左手座标系
FVector pt1 = InPoints[i] - right * InWidth / 2;
FVector pt2 = InPoints[i] + right * InWidth / 2;
vertices.Add(pt1);
vertices.Add(pt2);
int vNum = vertices.Num();
if (i > 0)
{
triangles.Add(vNum - 4);
triangles.Add(vNum - 3);
triangles.Add(vNum - 2);
triangles.Add(vNum - 2);
triangles.Add(vNum - 3);
triangles.Add(vNum - 1);
}
}
TArray<FVector> normals;
TArray<FProcMeshTangent> tangents;
TArray<FLinearColor> vertexColors;
TArray<FVector2D> uvs;
DrawMeshComponent->CreateMeshSection_LinearColor(0, vertices, triangles, normals, uvs, vertexColors, tangents, true);
DrawMeshComponent->SetMaterial(0, InMaterial);
}