【转载】UE4 Procedural Mesh 程序化模型

1,095 阅读2分钟

原文链接:UE4 Procedural Mesh 程序化模型

UE4 用 Procedural Mesh Component 调用 CreateMeshSection 可以生成模型

Vertices 是这个模型的所有顶点,下面的 Normal 法线向量、UV 贴图坐标、VertexColor 顶点颜色、Tangent 切线方向,都是和顶点对应的,但是 Triangles 这个整数数组比较特殊,它表示的是这个模型所有的面。比如说一个矩形平面有四个顶点,你还需要用这四个顶点连出来两个三角面,模型才能显示,Triangles 用 Vertices 的数组下标连线,每个三角面可以顺时针连和逆时针连,区别就在于面的朝向。

用三个数组下标连一个三角面,两个三角面分别是 0 2 1 和 1 2 3

例1

Procedural Mesh 可以生成一些简单的面片做特效很有用

juejuejue.gif

例2:生成地形

代码:

// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

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

UCLASS()
class DOORROOMS_API AMyActor : public AActor
{
	GENERATED_BODY()
	
public:	
	// Sets default values for this actor's properties
	AMyActor();

protected:
	// Called when the game starts or when spawned
	virtual void BeginPlay() override;

public:	
	// Called every frame
	virtual void Tick(float DeltaTime) override;

	// 网格大小
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Grid Generate")
	FVector2D GridSize = FVector2D(100, 100);
	// X 轴顶点数量
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Grid Generate")
	int32 Length = 10;
	// Y 轴顶点数量
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Grid Generate")
	int32 Width = 10;
        
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Noise")
	int32 NoiseSeed = 7777;
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Noise")
	float NoiseScale = 1;
	UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (UIMin = "0.00001", UIMax = "0.1"), Category = "Noise")
	float NoiseFactor = 0.1f;

	UPROPERTY(EditAnywhere, BlueprintReadWrite)
	UProceduralMeshComponent* Mesh;

	UFUNCTION(BlueprintCallable)
	void GenerateMesh();
	UFUNCTION()
	void GenerateGrid(TArray<FVector>& InVertices, TArray<int32>& InTriangles, TArray<FVector>& InNormals, TArray<FVector2D>& InUV0, TArray<FColor>& InVertexColor, TArray<float>InNoiseHeight, FVector2D InSize, int32 InLength, int32 InWidth);
private:
	void GetHeights(TArray<float>& InHeights);
};
// Fill out your copyright notice in the Description page of Project Settings.


#include "MyActor.h"
#include "SimplexNoiseBPLibrary.h"

// Sets default values
AMyActor::AMyActor()
{
 	// 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;
	Mesh = CreateDefaultSubobject<UProceduralMeshComponent>(FName("Mesh"));
	SetRootComponent(Mesh);
}

// Called when the game starts or when spawned
void AMyActor::BeginPlay()
{
	Super::BeginPlay();

}

// Called every frame
void AMyActor::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);

}
void AMyActor::GenerateMesh()
{
	if (GridSize.X <= 0 || GridSize.Y <= 0 || Length < 1 || Width < 1) return;
	Mesh->ClearAllMeshSections();

	// Mesh buffers
	TArray<FVector> vertices;
	TArray<int32> triangles;
	TArray<FVector> normals;
	TArray<FVector2D> UV0;
	TArray<FProcMeshTangent> tangents;
	TArray<FColor> vertexColors;

	TArray<float> heights;
	GetHeights(heights);

	GenerateGrid(vertices, triangles, normals, UV0, vertexColors, heights, GridSize, Length, Width);
	Mesh->CreateMeshSection(0,vertices, triangles, normals, UV0, vertexColors, tangents, true);
}

void AMyActor::GenerateGrid(TArray<FVector>& InVertices, TArray<int32>& InTriangles, TArray<FVector>& InNormals, TArray<FVector2D>& InUV0, TArray<FColor>& InVertexColor, TArray<float> InNoiseHeight, FVector2D InSize, int32 InLength, int32 InWidth)
{
	FVector2D SectionSize = FVector2D(InSize.X / InLength, InSize.Y / InWidth);
	int32 VertexIndex = 0;

	for (int X = 0; X < InLength + 1; X++)
	{
		for (int Y = 0; Y < InWidth + 1; Y++)
		{
			float z = VertexIndex < InNoiseHeight.Num() ? InNoiseHeight[VertexIndex] : 0;
			InVertices.Add(FVector(X * SectionSize.X, Y * SectionSize.Y, z * NoiseScale));
			FLinearColor color = FLinearColor(0, 0, 0, z);
			InVertexColor.Add(color.ToFColor(true));
			// UV
			FVector2D uv = FVector2D((float)X / (float)InLength, (float)Y / (float)InWidth);
			InUV0.Add(uv);
			// Once we've created enough verts we can start adding polygons
			if (X > 0 && Y > 0)
			{
				int32 bTopRightIndex = (X * (InWidth + 1)) + Y;
				int32 bTopLeftIndex = bTopRightIndex - 1;
				int32 pBottomRightIndex = ((X - 1) * (InWidth + 1)) + Y;
				int32 pBottomLeftIndex = pBottomRightIndex - 1;

				// Now create two triangles from those four vertices
				// The order of these (clockwise/counter-clockwise) dictates which way the normal will face. 
				InTriangles.Add(pBottomLeftIndex);
				InTriangles.Add(bTopRightIndex);
				InTriangles.Add(bTopLeftIndex);

				InTriangles.Add(pBottomLeftIndex);
				InTriangles.Add(pBottomRightIndex);
				InTriangles.Add(bTopRightIndex);
			}
			VertexIndex++;
		}
	}

	// normal
	for (int X = 0; X < InLength + 1; X++)
	{
		for (int Y = 0; Y < InWidth + 1; Y++)
		{
			int32 c = (X * (InWidth + 1)) + Y;
			int32 centerUp = c + InWidth + 1;
			int32 centerBottom = c - (InWidth + 1);
			int32 centerRight = c + 1;
			if (centerRight > ((X * (InWidth + 1)) + InWidth))
			{
				centerRight = -1;
			}
			int32 centerLeft = c - 1;
			if (centerLeft < (X * (InWidth + 1)))
			{
				centerLeft = -1;
			}
			int32 centerUpRight = centerUp + 1;
			if (centerUpRight > (X + 1) * (InWidth + 1) + InWidth)
			{
				centerUpRight = -1;
			}
			int32 centerBottomLeft = centerBottom - 1;
			if (centerBottomLeft < (X - 1) * (InWidth + 1))
			{
				centerBottomLeft = -1;
			}

			int32 indexs[6] = { centerUp,centerUpRight,centerRight,centerBottom,centerBottomLeft,centerLeft };
			TArray<FVector> vers;
			// get all the normal of triangles that using the current vertex
			for (int i = 0; i < 6; i++)
			{
				int32 NextIndex = (i + 1 >= 6) ? 0 : (i + 1);
				if (indexs[i] >= 0 && indexs[i] < InVertices.Num() && indexs[NextIndex] >= 0 && indexs[NextIndex] < InVertices.Num())
				{
					FVector a = (InVertices[indexs[i]] - InVertices[c]).GetUnsafeNormal();
					FVector b = (InVertices[indexs[NextIndex]] - InVertices[c]).GetUnsafeNormal();
					vers.Add(FVector::CrossProduct(a, b).GetUnsafeNormal());
					break;
				}
			}

			// get the average Vector
			FVector NormalSum = FVector(0, 0, 1);
			for (auto& i : vers)
			{
				NormalSum += i;
			}
			InNormals.Add(NormalSum / vers.Num());
		}
	}
}

void AMyActor::GetHeights(TArray<float>& InHeights)
{
	if (NoiseScale == 0) return;
	USimplexNoiseBPLibrary::setNoiseSeed(NoiseSeed);
	for (auto X = 0; X < Length + 1; X++)
	{
		for (auto Y = 0; Y < Width + 1; Y++)
		{
			InHeights.Add(USimplexNoiseBPLibrary::SimplexNoise2D(X, Y, NoiseFactor));
		}
	}
}

参考