UE4 用 Procedural Mesh Component 调用 CreateMeshSection 可以生成模型
Vertices 是这个模型的所有顶点,下面的 Normal 法线向量、UV 贴图坐标、VertexColor 顶点颜色、Tangent 切线方向,都是和顶点对应的,但是 Triangles 这个整数数组比较特殊,它表示的是这个模型所有的面。比如说一个矩形平面有四个顶点,你还需要用这四个顶点连出来两个三角面,模型才能显示,Triangles 用 Vertices 的数组下标连线,每个三角面可以顺时针连和逆时针连,区别就在于面的朝向。
用三个数组下标连一个三角面,两个三角面分别是 0 2 1 和 1 2 3
例1
用 Procedural Mesh 可以生成一些简单的面片做特效很有用
例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));
}
}
}
参考
- 生成高度图的插件: SimplexNoise
- 在 C++ 里使用插件: # Procedural Mesh Component in C++:Getting Started