0%

在虚幻引擎中用代码生成网格体:基础入门

今天我们来聊聊如何在虚幻引擎(Unreal Engine, UE)中用代码生成网格体。我发现网上关于这方面的介绍相对较少,而我刚好正在深入学习这个主题。本文是这个系列的第一篇,将带你了解基础知识。

介绍 ProceduralMeshComponent 插件

虚幻引擎提供了一个非常有用的插件,叫做 ProceduralMeshComponent。它允许我们通过编程的方式动态创建和修改网格体。这有什么用呢?对我来说,学习这个是为了实现自定义模型。比如,你可以通过UE的参数,在编辑器或运行时实时调整一个立方体的边长,而不需要预先导入一个静态模型。

这个插件同时支持C++和蓝图,但我这里只介绍如何在C++中使用它。

基础配置

在开始编写代码之前,我们需要先配置项目以启用 ProceduralMeshComponent 插件。

  1. 修改构建文件: 找到你项目目录下的 <你的项目名>.Build.cs 文件。

  2. 添加插件依赖:PublicDependencyModuleNames.AddRange 后面的花括号中,添加 "ProceduralMeshComponent"。修改后的代码应该类似这样:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    PublicDependencyModuleNames.AddRange(
    new string[] {
    "Core",
    "CoreUObject",
    "Engine",
    "InputCore",
    "ProceduralMeshComponent" // 添加这一行
    }
    );
  3. 重新生成项目文件: 关闭虚幻引擎项目。删除项目根目录下的 BinariesIntermediateSaved 文件夹。然后,右键点击你的 .uproject 文件,选择“Generate Visual Studio Project Files”(生成 Visual Studio 项目文件)。这会确保插件正确编译。

使用 ProceduralMeshComponent

配置完成后,我们就可以开始编写代码了。

1. 包含头文件

在你的C++文件中,需要包含 ProceduralMeshComponent 的头文件:

1
#include "ProceduralMeshComponent.h"

2. 创建一个Actor和组件

首先,你需要创建一个继承自 AActor 的C++类。在这个Actor中,我们将添加一个 UProceduralMeshComponent 类型的指针成员变量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// MyProceduralMeshActor.h
#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "ProceduralMeshComponent.h"
#include "MyProceduralMeshActor.generated.h"

UCLASS()
class MYPROJECT_API AMyProceduralMeshActor : public AActor
{
GENERATED_BODY()

public:
AMyProceduralMeshActor();

UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Components")
UProceduralMeshComponent* ProceduralMesh;

protected:
virtual void BeginPlay() override;

public:
void GenerateMesh(); // 用于生成网格体的方法
};

然后在 .cpp 文件中初始化组件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// MyProceduralMeshActor.cpp
#include "MyProceduralMeshActor.h"

AMyProceduralMeshActor::AMyProceduralMeshActor()
{
PrimaryActorTick.bCanEverTick = false;

// 创建并设置ProceduralMeshComponent
ProceduralMesh = CreateDefaultSubobject<UProceduralMeshComponent>(TEXT("GeneratedMesh"));
RootComponent = ProceduralMesh; // 将其设置为根组件
}

void AMyProceduralMeshActor::BeginPlay()
{
Super::BeginPlay();
GenerateMesh(); // 在Actor开始时生成网格
}

3. 理解核心函数:CreateMeshSection_LinearColor

ProceduralMeshComponent 提供了几个用于创建网格体的函数,它们的工作方式都非常相似。这里我们重点介绍一个最常用的函数:CreateMeshSection_LinearColor

函数声明:

1
2
3
4
5
6
7
8
9
void CreateMeshSection_LinearColor(int32 SectionIndex,
const TArray<FVector>& Vertices,
const TArray<int32>& Triangles,
const TArray<FVector>& Normals,
const TArray<FVector2D>& UV0,
const TArray<FLinearColor>& VertexColors,
const TArray<FProcMeshTangent>& Tangents,
bool bCreateCollision,
bool bSRGBConversion = false);

这个函数的工作原理类似于 OpenGL 等图形编程中构建几何体的方法。你通过定义一系列的顶点(Vertices)三角形索引(Triangles),以及可选的法线(Normals)UV坐标(UVs)顶点颜色(VertexColors) 来构建网格体。

4. 示例:生成一个简单的平面

让我们在 GenerateMesh 方法中实现一个简单的平面:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
// MyProceduralMeshActor.cpp
void AMyProceduralMeshActor::GenerateMesh()
{
TArray<FVector> Vertices;
TArray<int32> Triangles;
TArray<FVector> Normals;
TArray<FVector2D> UV0;
TArray<FLinearColor> VertexColors;
TArray<FProcMeshTangent> Tangents;

// 1. 定义顶点
Vertices.Add(FVector(0, 0, 0)); // 索引 0
Vertices.Add(FVector(100, 0, 0)); // 索引 1
Vertices.Add(FVector(0, 100, 0)); // 索引 2
Vertices.Add(FVector(100, 100, 0)); // 索引 3

// 2. 定义三角形(逆时针顺序为正面)
Triangles.Add(0); // 第一个三角形
Triangles.Add(1);
Triangles.Add(2);

Triangles.Add(1); // 第二个三角形
Triangles.Add(3);
Triangles.Add(2);

// 3. 定义法线(所有顶点朝向 Z 轴正方向)
Normals.Add(FVector(0, 0, 1));
Normals.Add(FVector(0, 0, 1));
Normals.Add(FVector(0, 0, 1));
Normals.Add(FVector(0, 0, 1));

// 4. 定义UV坐标
UV0.Add(FVector2D(0, 0));
UV0.Add(FVector2D(1, 0));
UV0.Add(FVector2D(0, 1));
UV0.Add(FVector2D(1, 1));

// 5. 定义顶点颜色(白色)
VertexColors.Add(FLinearColor(1, 1, 1, 1));
VertexColors.Add(FLinearColor(1, 1, 1, 1));
VertexColors.Add(FLinearColor(1, 1, 1, 1));
VertexColors.Add(FLinearColor(1, 1, 1, 1));

// 6. 调用创建函数
ProceduralMesh->CreateMeshSection_LinearColor(
0, // SectionIndex
Vertices,
Triangles,
Normals,
UV0,
VertexColors,
Tangents, // 暂不使用切线
true // 创建碰撞
);
}

AMyProceduralMeshActor 拖拽到场景中,运行游戏,你就会看到一个由代码生成的白色平面!

在编辑器中调试和预览(可选)

为了方便在不运行游戏的情况下,实时预览参数对网格体的影响,我们可以利用虚幻编辑器中的 PostEditChangeProperty 函数。

首先,在你的 .h 文件中,添加以下代码:

1
2
3
4
5
// MyProceduralMeshActor.h
#if WITH_EDITOR
// 在编辑器中属性改变时调用
virtual void PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) override;
#endif

然后,在 .cpp 文件中实现该函数。这里以一个虚构的 CubeParameters 结构体为例,你可以根据自己的项目需求进行修改:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// MyProceduralMeshActor.cpp
#if WITH_EDITOR
void AMyProceduralMeshActor::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent)
{
Super::PostEditChangeProperty(PropertyChangedEvent);

// 检查是否是需要实时预览的属性被修改
if (PropertyChangedEvent.Property != nullptr)
{
FName PropertyName = PropertyChangedEvent.Property->GetFName();
if (PropertyName == GET_MEMBER_NAME_CHECKED(FCubeParameters, CubeSize) ||
PropertyName == GET_MEMBER_NAME_CHECKED(FCubeParameters, SegmentCount))
{
// 当相关属性改变时,重新生成网格体
GenerateMesh();
}
}
}
#endif

这段代码利用了虚幻引擎的反射系统,它只在编辑器中编译和运行(由 #if WITH_EDITOR 宏控制)。每当你在 Actor 的 Details 面板中修改了特定属性,它就会自动调用 GenerateMesh 函数,让你即时看到变化。

接下来

这次我们只是简单介绍了 ProceduralMeshComponent 的基础用法。在下一篇文章中,我将深入讲解如何利用这些知识,一步步实现一个带有倒角(Bevel)效果的立方体。