🎮Unreal4/C++_Plugin

[UE4] 정점(Vertex)으로 Mesh 그리기 (UProceduralMeshComponent, Positions/Indices/Normals/ Colors/Uvs, UpdateMeshSection)

공대 컴린이 2023. 3. 27. 23:49
728x90

게임 개발을 공부하면서 언리얼 엔진만을 다룬다고 하더라도 DirectX에서 구현하는 것처럼 정점(Vertex)를 이용한 Texture 그리기, Mesh 다루기 등의 개념들을 잘 알고 있어야 한다.

 

따라서 이번에는 정점을 다루는 Vertex 클래스를 생성한 뒤, 정점으로만 그린 Cube를 게임에 배치하는 내용을 구현해보았다. 

추후에는 이러한 내용들을 이용하여 툴바에 추가해두었던 커스텀 버튼(LoadMesh)의 기능으로 어떤 Mesh를 선택할 때 Mesh의 정보를 읽어와 Color를 변경하거나 Mesh 파일을 따로 저장하는 등의 기능을 구현할것이다.

💻 Draw Vertex 클래스

먼저, 정점 데이터를 가지고 Mesh를 직접 그릴 수 있는 DrawVertex 클래스를 생성하였다.

 

UProceduralMeshComponent데이터를 입력해서 직접 형태를 만들 수 있는 Mesh 객체이다.

그밖에 정점의 데이터 Positions, Indices, Normals, Colors, Uvs 변수들을 TArray로 각각 선언해주었다.

💡 Position 추가하기 + Colors, Normals

먼저 ProceduralMeshComponent를 초기화한 뒤, Vertex를 그리기 위해 Positions와 Colors, Normals를 추가하였고

AddTriangles 함수를 통해 삼각형을 그릴 때 사용되는 순서별 Index들을 추가하였다.

 

가로,세로 1을 길이로 두는 사각형을 그리기 위해, 중앙점 P를 (0.5f, 0.5f, 0.5f) 로 초기화하였고, 언리얼과 DirectX는 모두 정점을 그릴 때 반시계방향으로 그려야 한다는 것을 주의하였다.

 

보기 쉽도록 FrontBack, Top, Bottom, Left, Right를 중괄호로 구분하여 코드를 작성하였다.

Front를 먼저 보면 왼쪽하단, 왼쪽 상단, 오른쪽 하단, 오른쪽 상단 순으로 정점들을 저장하였고

각 면들의 색상을 다르게 지정하며 Colors에 색상값을 추가하였다.

 

 

내가 그린 정육면체의 좌표를 그림으로 보자면 위와 같다.

중앙점 P가 존재할 때 각각의 왼쪽, 오른쪽, 상단, 하단, 앞, 뒤의 정점 좌표는 위와 같이 나온다.

💡 Indices 추가하기 (AddTriangles 함수)

AddTriangles 함수시작점 하나를 받으면 그로부터 Index 6개를 추가하여 한 면을 그리기 때문에 Front, Back, Top ,,, 같은 각각의 면을 그릴때 한번씩만 호출하였다.

 

또한 한 면을 그릴 때 사용되는 정점이 4개 이므로, AddTriangles(0), AddTriangles(4), AddTriangles(8), AddTriangles(12), ... , AddTriangles(20) 까지 4 간격으로 함수를 호출하였다.

💡 UV 추가하기

모든 면의 Position, Color, Normal, Index를 다 추가하고 난 후, Uvs는 모든면이 같은 순서로 그려지기 때문에 한번에 추가하였다.

 

마침내 정점의 정보가 모두 갖춰졌으니 생성해두었던 UProceduralMeshComponent 객체인 MeshCreateMeshSection 함수를 이용해 Mesh를 생성하였다.

 

매개변수로 각 정점 데이터를 넘겨주었고, 정점간의 길이가 1 인 작은 크기이므로, Scale을 100씩 늘려 출력해주었다.

 

DrawVertex.cpp의 전체코드는 게시글의 맨 아래 삽입하였다.


이후 C++ 클래스인 DrawVertex를 기반으로 블루프린트 BP_CDrawVertex를 생성하였고,

 

Vertex Color 를 입력데이터로 받은 머티리얼을 블루프린트에 설정하면 위와 같이 각 면마다 설정했던 색상값들이 정상적으로 출력되는것을 확인할 수 있었다.

 

또한 정점의 UV 정보를 TexCoord로 읽어와 Texture Sample을 베이스컬러로 함께 출력한다면 

 

위와 같이 Texture와 Color 정보가 혼합되어 잘 출력되는것을 볼 수 있었다.

💡 정점 수정하기 (Vertex Update)

한번 만들어진 정점을 잘 수정하진 않지만, 만약 수정이 필요한 경우에는 MeshUpdateMeshSection 함수를 이용하여 정점을 Update 시켜줄 수 있다.

 

 

UpdateMeshSection 함수의 매개변수는 CreateMeshSection 함수와 거의 동일하지만, Indices와 true 매개변수 두 개만 제외시키면 된다.

Indices는 처음 Mesh를 생성한 뒤로 변화하지 않기 때문에 Update 목록에서 제외되고, 마지막 true 변수는 Create Collision에 관련된 변수이므로 지워준다.

 

Tick 함수를 1초마다 실행되도록 PrimaryActorTick.TickInterval = 1.0f; 을 설정해두었으니 1초마다 Mesh의 색상을 랜덤으로 변경시키는 코드를 작성해보았다.

 

 

프로그램 실행 결과, 1초마다 Mesh의 정점이 정상적으로 Update 되며 색상이 랜덤 변경됨을 확인할 수 있었다.


📄 DrawVertex.cpp 전체 코드

#include "CDrawVertex.h"
#include "Global.h"
#include "ProceduralMeshComponent.h"

ACDrawVertex::ACDrawVertex()
{
	PrimaryActorTick.bCanEverTick = true;
	PrimaryActorTick.TickInterval = 1.0f;

	CHelpers::CreateComponent<UProceduralMeshComponent>(this, &Mesh, "Mesh");

	// 중앙을 0으로 잡기 위함
	FVector p = FVector(0.5f, 0.5f, 0.5f);

	// 정점을 그리는 방향: 반시계
	// Front
	{
		Positions.Add(FVector(-p.X, -p.Y, -p.Z)); // 0
		Positions.Add(FVector(-p.X, -p.Y, +p.Z)); // 1
		Positions.Add(FVector(-p.X, +p.Y, -p.Z)); // 2
		Positions.Add(FVector(-p.X, +p.Y, +p.Z)); // 3

		for (int32 i = 0; i < 4; i++)
		{
			Colors.Add(FColor(128, 0, 0, 255));
			Normals.Add(FVector(-1, 0, 0));
		}
		AddTriangles(0);
	}

	// Back
	{
		Positions.Add(FVector(+p.X, +p.Y, -p.Z));
		Positions.Add(FVector(+p.X, +p.Y, +p.Z));
		Positions.Add(FVector(+p.X, -p.Y, -p.Z));
		Positions.Add(FVector(+p.X, -p.Y, +p.Z));

		for (int32 i = 0; i < 4; i++)
		{
			Colors.Add(FColor(0, 128, 0, 255));
			Normals.Add(FVector(+1, 0, 0));
		}
		AddTriangles(4);
	}

	// Top
	{
		Positions.Add(FVector(-p.X, -p.Y, +p.Z));
		Positions.Add(FVector(+p.X, -p.Y, +p.Z));
		Positions.Add(FVector(-p.X, +p.Y, +p.Z));
		Positions.Add(FVector(+p.X, +p.Y, +p.Z));

		for (int32 i = 0; i < 4; i++)
		{
			Colors.Add(FColor(0, 0, 128, 255));
			Normals.Add(FVector(0, 0, +1));
		}
		AddTriangles(8);
	}

	// Bottom
	{
		Positions.Add(FVector(-p.X, -p.Y, -p.Z));
		Positions.Add(FVector(-p.X, +p.Y, -p.Z));
		Positions.Add(FVector(+p.X, -p.Y, -p.Z));
		Positions.Add(FVector(+p.X, +p.Y, -p.Z));

		for (int32 i = 0; i < 4; i++)
		{
			Colors.Add(FColor(0, 128, 128, 255));
			Normals.Add(FVector(0, 0, -1));
		}
		AddTriangles(12);
	}

	// Left
	{
		Positions.Add(FVector(+p.X, -p.Y, -p.Z));
		Positions.Add(FVector(+p.X, -p.Y, +p.Z));
		Positions.Add(FVector(-p.X, -p.Y, -p.Z));
		Positions.Add(FVector(-p.X, -p.Y, +p.Z));

		for (int32 i = 0; i < 4; i++)
		{
			Colors.Add(FColor(128, 0, 128, 255));
			Normals.Add(FVector(0, -1, 0));
		}
		AddTriangles(16);
	}

	// Right
	{
		Positions.Add(FVector(-p.X, +p.Y, -p.Z));
		Positions.Add(FVector(-p.X, +p.Y, +p.Z));
		Positions.Add(FVector(+p.X, +p.Y, -p.Z));
		Positions.Add(FVector(+p.X, +p.Y, +p.Z));

		for (int32 i = 0; i < 4; i++)
		{
			Colors.Add(FColor(128, 128, 128, 255));
			Normals.Add(FVector(0, +1, 0));
		}
		AddTriangles(20);
	}

	/* Uvs는 공통
	0,0 ㅡㅡ 1,0
	|          |
	|          |
	|          |
	0,1 ㅡㅡ 1,1
	*/
	for(int32 i = 0; i < 6; i++) // 6 = Front, Back, Top, Bottom, Left, Right
	{
		Uvs.Add(FVector2D(0, 1));
		Uvs.Add(FVector2D(0, 0));
		Uvs.Add(FVector2D(1, 1));
		Uvs.Add(FVector2D(1, 0));
	}

	// Create Mesh
	Mesh->CreateMeshSection(0, Positions, Indices, Normals, Uvs, Colors, TArray<FProcMeshTangent>(), true);
	Mesh->SetRelativeScale3D(FVector(100));
}

void ACDrawVertex::BeginPlay()
{
	Super::BeginPlay();
	
}

void ACDrawVertex::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);

}

/* 삼각형을 그릴땐 반시계방향으로 그린다.
	1 ㅡㅡㅡ 3
	|       |
	|       |
	0 ㅡㅡㅡ 2
 */
void ACDrawVertex::AddTriangles(int32 InStart)
{
	// 시계방향 : 0 1 2
	// 반시계방향 : 2 1 0 = 0 2 1 = 1 0 2 시작지점은 중요하지 않다.
	Indices.Add(InStart + 2);
	Indices.Add(InStart + 1);
	Indices.Add(InStart + 0);

	// 시계방향 : 2 1 3
	// 반시계방향 : 3 1 2
	Indices.Add(InStart + 3);
	Indices.Add(InStart + 1);
	Indices.Add(InStart + 2);
}

 

728x90