NSDT工具推荐Three.js AI纹理开发包 - YOLO合成数据生成器 - GLTF/GLB在线编辑 - 3D模型格式在线转换 - 可编程3D场景编辑器 - REVIT导出3D模型插件 - 3D模型语义搜索引擎 - Three.js虚拟轴心开发包 - 3D模型在线减面 - STL模型在线切割

我不会深入程序化建模(Procedural Mesh)背后的硬核理论,因为已经有很多文章介绍这些理论。相反,我将侧重于程序化建模的实际用途,例如简单景观的生成、在程序化生成中使用样条线、生成洞穴系统等。我也想做一些更多的实验性的事情, 比如生成式艺术。

现在不能保证本系列教程会涵盖上述内容, 因为我自己都不知道这个系列最终会写到什么程度,但可以保证的是教程会涵盖UE4程序化生成的基本知识。我将使用C++来实现所有示例,因为蓝图的可读性不高,不过可能会用蓝图实现一部分程序化生成。

好吧,我们开始。

1、创建三角形

首先进入Unreal编辑器,创建一个空的C++项目并添加C++类,父类选择Actor:

在"Build.cs"文件中的模块中添加ProceduralMeshComponent。

PublicDependencyModuleNames.AddRange(new string[] { 
	"Core", "CoreUObject", "Engine", "InputCore", "ProceduralMeshComponent" });

在新类的头文件中,包括ProceduralMeshComponent. h。此外,在类上添加ProceduralMeshComponent。接下来,添加创建程序化网格的所有必要数组。现在,你只需要顶点和三角形。不幸的是,创建程序化网格的函数还需要其他数组,因此也一并创建。接下来,将"OnConstruction"函数添加到头文件。这基本上是构建脚本,这样就创建了网格。同时创建ClearMeshData函数,用来清理数组。

#include "GameFramework/Actor.h"
#include "ProceduralMeshComponent.h"
#include "MyProceduralMesh.generated.h"

UCLASS()
class TUTORIALS_API AMyProceduralMesh : public AActor
{
	GENERATED_BODY()

	UPROPERTY(VisibleAnywhere, Category = "MyProceduralMesh")
	UProceduralMeshComponent* pm;

public:	
	AMyProceduralMesh();

	UPROPERTY()
	TArray<FVector> vertices;
	UPROPERTY()
	TArray<FVector> normals;
	UPROPERTY()
	TArray<int32> triangles;
	UPROPERTY()
	TArray<FVector2D> uvs;
	UPROPERTY()
	TArray<FLinearColor> vertexColors;
	UPROPERTY()
	TArray<FProcMeshTangent> tangents;
	
	virtual void OnConstruction(const FTransform& Transform) override;

	void ClearMeshData();
};

在 cpp 文件的构造器中,你需要做的第一件事是创建ProceduralMeshComponent

AMyProceduralMesh::AMyProceduralMesh()
{
 	PrimaryActorTick.bCanEverTick = true;

	pm = CreateDefaultSubobject<UProceduralMeshComponent>(TEXT("ProceduralMesh"));
	pm->AttachToComponent(GetRootComponent(), FAttachmentTransformRules::KeepRelativeTransform);
}

然后实现OnConstruction函数并添加顶点。现在在 X 轴和 Y 轴上创建它们。下一步将创建三角形。你可以通过将顶点序号添加到三角形数组中来做到这一点。在这里你需要小心,因为顶点的顺序将决定三角形的正面。你需要逆时针插入顶点。因此下面的三角形将是0-2-1。

接下来,初始化其余数组,其长度均与顶点数组相同。现在,您有了创建三角形所需的所有数据,可以创建它了:

void AMyProceduralMesh::OnConstruction(const FTransform& Transform)
{
    vertices.Add(FVector(0.0f, 0.0f, 0.0f));
	vertices.Add(FVector(100.0f, 0.0f, 0.0f));
	vertices.Add(FVector(0.0f, 100.0f, 0.0f));

	triangles.Add(0);
	triangles.Add(2);
	triangles.Add(1);

	uvs.Init(FVector2D(0.0f, 0.0f), 3);
	normals.Init(FVector(0.0f, 0.0f, 1.0f), 3);
	vertexColors.Init(FLinearColor(0.0f, 0.0f, 0.0f, 1.0f), 3);
	tangents.Init(FProcMeshTangent(1.0f, 0.0f, 0.0f), 3);
    
    //Function that creates mesh section
	pm->CreateMeshSection_LinearColor(0, vertices, triangles, normals, uvs, vertexColors, tangents, false);
}

在编译之前的最后一件事,是你必须实现ClearMeshData函数。

void AMyProceduralMesh::ClearMeshData()
{
	vertices.Empty();
	triangles.Empty();
	uvs.Empty();
	normals.Empty();
	vertexColors.Empty();
	tangents.Empty();
}

这将删除所有数组。之所以这样做,是因为每次更改对象时,构建脚本(OnConstruction)都会运行。这将导致每次修改后添加3 个新的 顶点和1个新三角形,这是你不想要的(它可能会崩溃,因为其他数组的长度不会匹配顶点数组)。这不是干净的解决方案, 但它可以工作。我们现在可以编译了。

创建一个新的蓝图,选择刚刚创建的程序化网格类作为父类。然后,您可以将该蓝图添加到关卡,并且应该看到一个三角形。

2、三角形纹理处理

为了正确处理三角形的纹理,我们需要为每个顶点提供 0-1 范围内的 2D  UV坐标。在上面示例中这很容易,因为对边和临边等长(三角形是正方形的一半):

uvs.Add(FVector2D(0.0f, 0.0f));
uvs.Add(FVector2D(1.0f, 0.0f));
uvs.Add(FVector2D(0.0f, 1.0f));

//uvs.Init(FVector2D(0.0f, 0.0f), 3);

3、结论

好了,第一个教程结束,下一部分将介绍细分平面,还将有一个额外的实验。本教程源码如下:

#include "GameFramework/Actor.h"
#include "ProceduralMeshComponent.h"
#include "MyProceduralMesh.generated.h"

UCLASS()
class TUTORIALS_API AMyProceduralMesh : public AActor
{
	GENERATED_BODY()

	UPROPERTY(VisibleAnywhere, Category = "MyProceduralMesh")
	UProceduralMeshComponent* pm;

public:	
	AMyProceduralMesh();

	UPROPERTY()
	TArray<FVector> vertices;
	UPROPERTY()
	TArray<FVector> normals;
	UPROPERTY()
	TArray<int32> triangles;
	UPROPERTY()
	TArray<FVector2D> uvs;
	UPROPERTY()
	TArray<FLinearColor> vertexColors;
	UPROPERTY()
	TArray<FProcMeshTangent> tangents;
	
	virtual void OnConstruction(const FTransform& Transform) override;
	
	void ClearMeshData();
};
#include "Tutorials.h"
#include "MyProceduralMesh.h"


AMyProceduralMesh::AMyProceduralMesh()
{
 	PrimaryActorTick.bCanEverTick = true;

	pm = CreateDefaultSubobject<UProceduralMeshComponent>(TEXT("ProceduralMesh"));
	pm->AttachToComponent(GetRootComponent(), FAttachmentTransformRules::KeepRelativeTransform);
	
}

void AMyProceduralMesh::OnConstruction(const FTransform& Transform)
{
	ClearMeshData();

	vertices.Add(FVector(0.0f, 0.0f, 0.0f));
	vertices.Add(FVector(100.0f, 0.0f, 0.0f));
	vertices.Add(FVector(0.0f, 100.0f, 0.0f));

	triangles.Add(0);
	triangles.Add(2);
	triangles.Add(1);

	uvs.Add(FVector2D(0.0f, 0.0f));
	uvs.Add(FVector2D(1.0f, 0.0f));
	uvs.Add(FVector2D(0.0f, 1.0f));

    //uvs.Init(FVector2D(0.0f, 0.0f), 3);
	normals.Init(FVector(0.0f, 0.0f, 1.0f), 3);
	vertexColors.Init(FLinearColor(0.0f, 0.0f, 0.0f, 1.0f), 3);
	tangents.Init(FProcMeshTangent(1.0f, 0.0f, 0.0f), 3);
    
    //Function that creates mesh section
	pm->CreateMeshSection_LinearColor(0, vertices, triangles, normals, uvs, vertexColors, tangents, false);
}


void AMyProceduralMesh::ClearMeshData()
{
	vertices.Empty();
	triangles.Empty();
	uvs.Empty();
	normals.Empty();
	vertexColors.Empty();
	tangents.Empty();
}

原文链接:Procedural Mesh in UE4 #1 – Triangle

BimAnt翻译整理,转载请标明出处