프로그래밍 공부
작성일
2023. 12. 15. 15:28
작성자
WDmil
728x90

Terrain을 제작하는 Editer를 제작할 것 이다.

 

Editer에는 어떤 기능이 필요한지 생각해보아야 한다.

 

1. 마우스와 Terrain의 위치에 따른 원형 표시.

2. 마우스가 눌렸을 때 해당 위치의 Terrain의 vertice가 변화하여야 한다.

3. 변화한 vertice에 따른, Diffuse값과 normal값을 재정의 해주어야 한다.

4. 맵 을 불러왔을때, 다른사이즈의 map의 경우. 해당 map의 사이즈만큼 Buffer를 재정의 해주어야 한다.

 

제작해보자.


#pragma once

class TerrainEditer : public GameObject
{
private:
	//typedef VertexUVNormal VertexType;
	// VertexType을 정의합니다.
	// VertexType은 위치, 텍스처 좌표, 법선 벡터 및 alpha 값으로 구성됩니다.
	struct VertexType
	{
		Float3 pos = {};
		Float2 uv = {};
		Float3 normal = {};
		float alpha[4] = {};
	};

	// 지형 편집기의 최대 크기 및 최대 높이를 상수로 정의합니다.
	const UINT MAX_SIZE = 256;
	const UINT MAX_HEIGHT = 20.0f;

	// RayBuffer는 지형 편집기에서 레이캐스팅을 수행하기 위한 상수 버퍼입니다.
	class RayBuffer : public ConstBuffer
	{
	private:
		struct Data
		{
			Float3 pos;
			UINT triangleSize;
			Float3 dir;
			float padding;
		};

	public:
		RayBuffer() : ConstBuffer(&data, sizeof(Data))
		{
		}

		Data& Get() { return data; }

	private:
		Data data;
	};

	// BrushBuffer는 지형을 편집할 때 사용되는 브러시의 속성을 담는 상수 버퍼입니다.
	class BrushBuffer : public ConstBuffer
	{
	private:
		struct Data
		{
			int type = 0;
			Float3 pickingPos;
			float range = 20.0f;
			Float3 color = { 0, 1, 0 };
		};

	public:
		BrushBuffer() : ConstBuffer(&data, sizeof(Data))
		{
		}

		Data& Get() { return data; }

	private:
		Data data;
	};

	// 입력과 출력 구조체를 정의합니다.
	// 입력 구조체는 삼각형의 세 꼭지점 좌표를 나타냅니다.
	// 출력 구조체는 피킹 여부와 거리를 나타냅니다.
	struct InputDesc
	{
		Float3 v0, v1, v2;
	};

	struct OutputDesc
	{
		int picked;
		float distance;
	};

public:
	// 지형 편집기의 생성자와 소멸자를 선언합니다.
	TerrainEditer();
	~TerrainEditer();

	// 지형 편집기의 업데이트, 렌더링, GUI 렌더링, 피킹, 컴퓨트 데이터 생성 등의 기능을 정의합니다.
	void Update();
	void Render();
	void GUIRender();
	void Picking();
	bool ComputePicking(Vector3& pos);
	void MakeComputeData();

	// 현재 위치에서 중력 가속도를 계산하는 함수를 정의합니다.
	Vector3 GetOngravityAcceleration(const Vector3 ObjectPos, const Vector3 correction = { 0, 0, 0 });

	// 땅에 닿았는지 여부를 확인하는 함수와 지형의 너비 및 높이를 반환하는 함수를 정의합니다.
	bool ChackOnGround(const Vector3 ObjectPos);
	int GetWidth() { return width; }
	int GetHeight() { return height; }

private:
	// 저점을 찾는 함수와 메시 생성, 높이 맵 및 알파 맵 저장 및 로드 함수, 높이 조절 및 알파 조절 함수를 정의합니다.
	pair<Vector3, Vector3> FindLowPos(Vector3 e0, Vector3 e1, Vector3 e2, Vector3 e3);
	void MakeMesh(bool tile = false, bool flip = false);
	void MakeNormal(bool Flip = false);
	void AdjustHeight();
	void AdjustAlpha();
	void SaveHeightMap();
	void LoadHeightMap();
	void SaveAlphaMap();
	void LoadAlphaMap();
	void UpdateHeight();
	void Resize();

private:
	// 멤버 변수들을 정의합니다.
	UINT width, height, triangleSize;
	Vector3 pickingPos;
	float adjustValue = 20.0f;
	string projectPath;
	Mesh<VertexType>* mesh;
	Mesh<VertexColor>* normalline;
	Texture* heightMap;
	Vector3 defaltsize = {};
	RayBuffer* rayBuffer;
	BrushBuffer* brushBuffer;
	StructuredBuffer* structuredBuffer;
	vector<InputDesc> inputs;
	vector<OutputDesc> outputs;
	ComputeShader* computeShader;
};

 

함수들을 정의한다.

 

Terrain을 기본적으로 정의해줄 Update, Render, GUIRender, 등등은 사용하고,

 

우리가 추가로 넣어야 하는것은

void AdjustHeight();
void AdjustAlpha();

void SaveHeightMap();
void LoadHeightMap();
void SaveAlphaMap();
void LoadAlphaMap();
void UpdateHeight();
void Resize();

 

이것들 이다.

 

지정된 위치에서 데이터를 수정하고 추가해야 하기 때문이다.

 

우선 Shader를 추가하여, Vertice가 마우스 포인터의 위치와 어느정도 거리가 되는지 판별하고,

 

해당 거리안에 존재하면 초록색으로 빛나게 해보자.

 

Buffer는 BrushBuffer를 사용해서 b10버퍼에 데이터를 전달하여 사용할 것 이다.

 

//SpecularLight
#include "../VertexHeader.hlsli"
#include "../PixelHeader.hlsli"

// 꼭짓점 셰이더 함수입니다.
LightPixelInput VS(VertexUVNormalTangent input)
{
	LightPixelInput output;
	// 입력 꼭짓점을 월드 공간으로 변환합니다.
	output.pos = mul(input.pos, world);
	// 월드 공간에서의 위치를 저장합니다.
	output.worldPos = output.pos;
	// 뷰 공간에서의 위치를 저장합니다.
	output.viewPos = invView._41_42_43;
	
	// 위치를 뷰 공간으로 변환합니다.
	output.pos = mul(output.pos, view);
	// 위치를 투영 공간으로 변환합니다.
	output.pos = mul(output.pos, projection);
	
	// UV 좌표를 저장합니다.
	output.uv = input.uv;
	
	// 노멀과 탄젠트를 월드 공간으로 변환하고, 
	// 빈노멀(노멀과 탄젠트에 수직인 벡터)을 계산합니다.
	output.normal = mul(input.normal, (float3x3) world);
	output.tangent = mul(input.tangent, (float3x3) world);
	output.binormal = cross(output.normal, output.tangent);
	
	return output;
}
// 레이드 할 때 스킬쓰기전 범위 표시. 할때 많이 사용하는 방법이다.
cbuffer BrushBuffer : register(b10)
{
	int type;
	float3 pickingPos;
	
	float range;
	float3 color;
}

float4 BrushColor(float3 pos)
{
	float2 direction = pos.xz - pickingPos.xz;
	
	float dist = length(direction);
	
	if (dist < range)
		return float4(color, 0);
	
	return float4(0, 0, 0, 0);
}

// 픽셀 셰이더 함수입니다.
float4 PS(LightPixelInput input) : SV_TARGET
{
	// 광원 데이터를 가져옵니다.
	Material material = GetMaterial(input);
	
	float4 ambient = CalcAmbient(material);
	float4 result = 0;
	for (int i = 0; i < lightCount; i++)
	{
		if (!lights[i].isActive)
			continue;

		if (lights[i].type == 0)
			result += CalcDirectional(material, lights[i]);
		else if (lights[i].type == 1)
			result += CalcPoint(material, lights[i]);
		else if (lights[i].type == 2)
			result += CalcSpot(material, lights[i]);
	}
	
	float4 brush = BrushColor(input.worldPos);
	
	return ambient + result + mEmissive + brush;
}

 

각 vertice는 PS연산시, 위치값과 마우스의 위치에 따라. Position을 구해서. dircetion을 구한다음.

 

길이값을 측정하고, 현재 위치와 vertice의 거리가 일정범위 안에 존재한다면 buffer로 넘겨준 Color값을 추가연산 해줄것 이다.

 

material->SetShader(L"LandScape/TerrainEditer.hlsl");

 

해당 객체의 Shader를 만든 쉐이더로 설정하고 실행해보자.

 

 

각 vertice가 연산처리될 때, 마우스와 vertice의 각 위치를 연산해서, 거리값에 따라 Color값에 보정데이터를 추가한다.

 


// 브러시를 사용하여 지형의 높이를 동적으로 조절하는 함수입니다.
void TerrainEditer::AdjustHeight()
{
	// 지형 메시의 정점들을 가져옵니다.
	vector<VertexType>& vertices = mesh->GetVertices();

	// 각 정점에 대해 브러시 영향을 계산하고 높이를 조절합니다.
	for (VertexType& vertex : vertices)
	{
		// 정점의 x, z 좌표를 포함하는 3D 위치 생성
		Vector3 pos = Vector3(vertex.pos.x, 0.0f, vertex.pos.z);

		// 브러시의 피킹 위치를 가져오고 y 좌표를 0으로 설정
		Vector3 pickingPos = brushBuffer->Get().pickingPos;
		pickingPos.y = 0.0f;

		// 정점과 브러시 피킹 위치 간의 거리 계산
		float distance = MATH->Distance(pos, pickingPos);

		// 거리가 브러시의 효과 범위 내에 있는 경우에만 높이를 조절합니다.
		if (distance <= brushBuffer->Get().range)
		{
			// 높이를 조절하는 부분
			vertex.pos.y += adjustValue * DELTA;

			// 높이를 최대 높이(MAX_HEIGHT)로 제한합니다.
			vertex.pos.y = MATH->Clamp(0.0f, MAX_HEIGHT, vertex.pos.y);
		}
	}

	// 높이가 조절된 후에는 메시의 높이맵 및 노말을 업데이트합니다.
	UpdateHeight();
}

 

이제 색 원이 있는 부분을 마우스로 클릭했을 때, 조정하여 위로 들어올리거나 밑으로 내리는 작업을 진행할 것 이다.

 

브러시 효과 내부에 있는 정점좌표를 위치값으로 pos로 적용하고, 브러시의 피킹위치를 사용해서 정점, 브러시 피킹위치의 거리를 계산하고,

 

브러시 효과범위 내에 있는 경우 높이를 조절하여 높이값을 추가한다.

 

위와같은 데이터 처리방식을 DeviceContext에서 진행할 수 있도록 수정할 수 있을 것 이다.

 

Buffer로 조정하는게 각 객체를 전방순회 하는것보다는 더 빠를것 이다.

 

void TerrainEditer::UpdateHeight()
{
	vector<VertexType>& vertices = mesh->GetVertices();
	for (VertexType& vertex : vertices)
		vertex.normal = {};

	MakeNormal();
	MakeComputeData();
	mesh->UpdateVertices();
	structuredBuffer->UpdateInput(inputs.data());
}

void TerrainEditer::Resize()
{
	MakeMesh();
	MakeNormal();
	MakeComputeData();

	mesh->UpdateVertices();
	mesh->UpdateIndices();

	structuredBuffer->UpdateInput(inputs.data());
}

 

변경된 데이터를 업데이트하고 조정하는 함수를 정리한다.


 

이번에는 Map에 대한 Diffusemap을 여러개 적용하고 블렌딩을 해보자.

 

Terrain에 DIffuseMap을 n개 이상 적용하고, 해당 컬러값대로 데이터를 적용, 비적용하고, Uv값을 조정할 것 이다.

 

//SpecularLight
#include "../VertexHeader.hlsli"
#include "../PixelHeader.hlsli"

// 꼭짓점 셰이더 함수입니다.
LightPixelInput VS(VertexUVNormalTangent input)
{
	LightPixelInput output;
	// 입력 꼭짓점을 월드 공간으로 변환합니다.
	output.pos = mul(input.pos, world);
	// 월드 공간에서의 위치를 저장합니다.
	output.worldPos = output.pos;
	// 뷰 공간에서의 위치를 저장합니다.
	output.viewPos = invView._41_42_43;
	
	// 위치를 뷰 공간으로 변환합니다.
	output.pos = mul(output.pos, view);
	// 위치를 투영 공간으로 변환합니다.
	output.pos = mul(output.pos, projection);
	
	// UV 좌표를 저장합니다.
	output.uv = input.uv;
	
	// 노멀과 탄젠트를 월드 공간으로 변환하고, 
	// 빈노멀(노멀과 탄젠트에 수직인 벡터)을 계산합니다.
	output.normal = mul(input.normal, (float3x3) world);
	output.tangent = mul(input.tangent, (float3x3) world);
	output.binormal = cross(output.normal, output.tangent);
	
	return output;
}

Texture2D alphaMap : register(t10);
Texture2D secondMap : register(t11);

// 픽셀 셰이더 함수입니다.
float4 PS(LightPixelInput input) : SV_TARGET
{
	// 광원 데이터를 가져옵니다.
	Material material = GetMaterial(input);
	float4 alpha = alphaMap.Sample(samp, input.uv);
	float4 second = secondMap.Sample(samp, input.uv);
	
	material.baseColor = lerp(material.baseColor, second, alpha.r);
	
	float4 ambient = CalcAmbient(material);
	float4 result = 0;
	for (int i = 0; i < lightCount; i++)
	{
		if (!lights[i].isActive)
			continue;

		if (lights[i].type == 0)
			result += CalcDirectional(material, lights[i]);
		else if (lights[i].type == 1)
			result += CalcPoint(material, lights[i]);
		else if (lights[i].type == 2)
			result += CalcSpot(material, lights[i]);
	}
	
	return ambient + result + mEmissive;
}

 

알파값으로 이미지를 긁어온 다음, alpha데이터의 Red데이터를 사용하여 선형보간 할 것 이다.

 

색자체를 Vector로 적용하여 선형보간하여 데이터를 적용하면, 해당 Pixel에 대한 R이 낮으면 BaseColor

R이 높으면, scond가 출력될 것 이다.

 

	Texture* heightMap;
	Texture* alphaMap;
	Texture* secondMap;

Terrain에 heightMap 과 alphaMap, scondMap을 적용하여 Buffer로 넘겨준다.

Terrain::Terrain()
{
	SetLocalPosition({ 0, 0, 0 });
	tag = "Terrain";
	material->SetShader(L"LandScape/Terrain.hlsl");
	material->SetDiffuseMap(L"Textures/Colors/White.png");

	heightMap = Texture::Add(L"Textures/HeightMaps/HeightMap.png");
	alphaMap = Texture::Add(L"Textures/HeightMaps/AlphaMap.png");
	secondMap = Texture::Add(L"Textures/Landscape/Dirt3.png");

	mesh = new Mesh<VertexUVNormal>();
	normalline = new Mesh<VertexColor>();
	RSset = new RasterizerState();

	MakeMesh();
	MakeNormal();
	mesh->CreateMesh();
	normalline->CreateMesh();
	RSset->SetState();
}

 

대충 위와같이 적용하여, 기본 Diffusemap과 scondMap간의 선형보간을 진행할 것 이다.

 

 검정색 부분은 흰색, 붉은색 부분은 집어넣은 DiffuseMap으로 표기될 것이다.

 

위와같은 AlphaMap을 적용한다.

 

위와같이 나타났다면 성공이다.

728x90