프로그래밍 공부
작성일
2023. 10. 14. 18:40
작성자
WDmil
728x90

지금까지 만들었던 프레임워크중에 Buffer와 Shader을 나누었던것을 하나의 Mesh와 Material로 나누어보자.

 

원래 Cube에서 사용되었던 

 

(Shader)

PiaxelShader

VertexShader

 

(Vertex Data)

VertexBuffer

vector<T>(Vertex type들)

IndexBuffer

vector<UINT>(indices들)

 

을 각각  Material 와 Mesh로 분리하여 작업한다.


Mesh

#pragma once

template<typename T>
class Mesh
{
public:
	Mesh() = default;
	~Mesh();

	void Draw(D3D11_PRIMITIVE_TOPOLOGY type = D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);

	void CreateMesh();

	vector<T>& GetVertices() { return vertices; }
	vector<UINT>& GetIndices() { return indices; }


private:
	VertexBuffer* vertexBuffer = nullptr;
	IndexBuffer* indexBuffer = nullptr;

	vector<T> vertices;
	vector<UINT> indices;
};

template<typename T>
inline Mesh<T>::~Mesh()
{
	delete vertexBuffer;

	if(indexBuffer != nullptr)
		delete indexBuffer;
}

template<typename T>
inline void Mesh<T>::Draw(D3D11_PRIMITIVE_TOPOLOGY type)
{
	vertexBuffer->Set(type);

	if (indexBuffer)
	{
		indexBuffer->Set();
		DC->DrawIndexed(indices.size(), 0, 0);
	}
	else
	{
		DC->Draw(vertices.size(), 0);
	}
}

template<typename T>
inline void Mesh<T>::CreateMesh()
{
	vertexBuffer = new VertexBuffer(vertices.data(),
		sizeof(T), vertices.size());

	if (indices.size() > 0)
	{
		indexBuffer = new IndexBuffer(indices.data(), indices.size());
	}
}

Mesh코드이다.

 

Vertex의 Type으로 어떤것이 들어올 지 모르기 때문에, MeshType으로 typename T를 받아와서 작업하게된다.

 

원래 사용하던 VertrexBuffer와 IndexBuffer, vertices, indices를 가지게 된다.

 

데이터의 연산처리과정은 항상 GetVertices와 Getindices를 통하여 원본값을 참조하여 연산하게되고.

 

CreateMesh를 통해 버퍼를 생성하고, Draw를 통해 Buffer들을 위로 올려준다.


#pragma once

class Material
{
public:
	Material(wstring shderFIle, int flag);
	~Material();

	void Set();

	void SetShader(wstring shaderFile, int flag);

private:
	VertexShader* vertexShader;
	PixelShader* pixelShader;
};

Material이다.

 

원래 사용하던 객체의 Shader를 담당하게 된다.

 

Set()과 SetShader를 통하여 데이터의 Shader를 보내줄 수 있다.

 

#include "Framework.h"

Material::Material(wstring shderFIle, int flag)
{
	SetShader(shderFIle, flag);
}

Material::~Material()
{
}

void Material::Set()
{
	vertexShader->Set();
	pixelShader->Set();
}

void Material::SetShader(wstring shaderFile, int flag)
{
	vertexShader = Shader::AddVS(shaderFile, flag);
	pixelShader = Shader::AddPS(shaderFile);
}

CPP파일에서는 더 간단하게 구성된다.

 

각각 vertexShader와 PixelShader에 들어있는 Set을 실행시키는것 이고,

 

Shader는 SetShader를 통해 shaderFile, flag를 연결시켜서, AddVS, AddPS를 사용하게 된다.


DirectTex

위와같이 버퍼와 쉐이더를 통합하였으면. UV를 사용하여 이미지픽셀을 기입할 수 있게되었다.

 

원래 사용하던 hlsl을 수정하고, Lib파일을 새롭게 받아서 세팅해보자.

 

https://github.com/microsoft/DirectXTex

 

GitHub - microsoft/DirectXTex: DirectXTex texture processing library

DirectXTex texture processing library. Contribute to microsoft/DirectXTex development by creating an account on GitHub.

github.com

마이크로소프트에서 DIrectXTex 의 GIthub을 운영한다. 해당 링크로 들어가서.

가장 최근에 Releases된 파일을 받아주자.

Source code zip을 받으면 된다.

압축을 풀어주면 다음과 같은 화면이 나타난다.

폴더 경로 내 이미지

여기서 내 Windows버전에 해당하는 최신 DirectTex 라이브러리 컴파일 코드에 들어가준다.

(윈도우 11도 10꺼를 쓴다)

글쓴이는 DirectTex_Desktop_2022_Win10 이 해당한다.

들어가서 냅다 디버그를 돌려버리면,

 

위와같이. 빌드는 성공했으나, 프로그램을 실행할 수는 없었다고 나타난다.

 

위 프로그램은 lib생성 프로그램으로  해당되는 버전의 코드가 각각 다른방식으로 구동할 수도 있기 때문에, 주소값 처리, 라이브러리 처리 등의 이유로. 해당되는 전용 lib파일을 생성해주는 프로그램이다.

 

아무튼 위와같은 상황이 발생했으면 정상동작한 것 이다.

 

lib파일이 생성되었으니, 지금당장 필요한 파일만 옮겨서 써주도록 하자.


위 폴더 안
위 .h 파일, .inl파일

헤더와 inl파일을 옮기고,

저 위의 Bin안의

위 경로를 타고들어가면,

이 파일이 있는데.

 

이 파일과 위 2개의 h,.inl파일로 총 3개의 파일을 사용한다.

visualstudio에는 대강 위와같은 경로로 쑤셔박아준다음에,

VC++ 디렉터리

위 경로로 들어가서. 포함 디렉터리, 라이브러리 디렉터리를 설정해주면 된다.

각각 위와같이 설정해준다.

그리고 위와같이 헤더와 lib를 선언해주면 끝이다!

 


이제 UV를 사용하기 위한 대략적인 준비가 끝났다.

 

이제 저 Tex를 사용하기전에 hlsl과 DESC를 수정해주어야 한다.

 

현재 픽셀의 UV좌표와 지금 사용하는 color값 데이터는 맞지않기 때문이다.

 

그 작업을 하기위해서는 먼저 Element_DESC를 다른 객체로 분리해주어도 되고 그냥 사용해도된다.

 

작성자는 분리해서 사용하는게 편함으로 분리를 해보도록 하자.

 

#pragma once

enum ShaderChois
{
	VERTEX_COLOR,
	VERTEX_UV
};

class Element_desc : public Singleton<Element_desc>
{
public:
	friend class Singleton;
	Element_desc();
	~Element_desc();

	vector<D3D11_INPUT_ELEMENT_DESC>& GetDESC(const int ShaderChois) { return desc[ShaderChois]; }

private:
	vector<vector<D3D11_INPUT_ELEMENT_DESC>> desc;
};

Element_DESC는 위와같이 대충 분리해서 사용하자.

 

enum으로 현재 사용하는 Vertex의 타입을 설정할 수 있도록 정의하였고.

 

Element_desc는 단 한개만 존재해도 됨으로 Singleton형식으로 생성한다.

 

그후, D3D11_INPUT_ELEMENT_DESC를 반환해야 함으로 이중Vector형식으로 Shader를 생성하여 작성한다.

 

CPP는 매우 간단하다.

#include "Framework.h"

Element_desc::Element_desc()
{
	if (desc.empty()) {
		vector<D3D11_INPUT_ELEMENT_DESC> layoutDescColor =
		{
			{"POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0,
				D3D11_INPUT_PER_VERTEX_DATA, 0},
			{ "COLOR", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 12,
				D3D11_INPUT_PER_VERTEX_DATA, 0 }
		};
		desc.emplace_back(layoutDescColor);

		vector<D3D11_INPUT_ELEMENT_DESC> layoutDescUV =
		{
			{"POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0,
				D3D11_INPUT_PER_VERTEX_DATA, 0},
			{"UV", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 12,
				D3D11_INPUT_PER_VERTEX_DATA, 0 }
		};
		desc.emplace_back(layoutDescUV);
	}
}

Element_desc::~Element_desc()
{
}

위에서 바라보듯, desc가 생성되었을때, 비어있다면. 해당되는 DESC를 생성하여 vector에 넣어준다.

 

순서대로 기입함으로. 새롭게 생긴 값과 enum의 순서가 섞이지 않도록 주의해야한다.

물론, 헤더또한 잘 선언해주어야한다.

 

작성자는 더 편하게 사용하기 위해 Define도 설정해주었다.


HLSL

자 그러면 이제 hlsl또한 새롭게 만들어주어야 할것이다.

원래쓰던 hlsl은 ColorShader이라고 이름을 바꾸고 새롭게 만들어서 넣어주자.

//VertexColorShader

cbuffer WordBuffer : register(b0)
{
	matrix world;
}

cbuffer ViewBuffer : register(b1)
{
	matrix view;
}

cbuffer ProjectionBuffer : register(b2)
{
	matrix projection;
}
struct VertexInput
{
	float4 pos : POSITION;
	float4 color : COLOR;
};

struct PixelInput
{
	float4 pos : SV_POSITION;
	float4 color : COLOR;
};

PixelInput VS(VertexInput input)
{
	PixelInput output;
	output.pos = mul(input.pos, world);
	output.pos = mul(output.pos, view);
	output.pos = mul(output.pos, projection);
	
	output.color = input.color;
	
	return output;
}

float4 PS(PixelInput input) : SV_TARGET
{
	return input.color;
}

위의 hlsl은 원래 사용하던 Color값을 이용하는 hlsl이다.

//Tutorial.hlsl

cbuffer WordBuffer : register(b0)
{
	matrix world;
}

cbuffer ViewBuffer : register(b1)
{
	matrix view;
}

cbuffer ProjectionBuffer : register(b2)
{
	matrix projection;
}
struct VertexInput
{
	float4 pos : POSITION;
	float2 uv : UV;
};

struct PixelInput
{
	float4 pos : SV_POSITION;
	float2 uv : UV;
};

PixelInput VS(VertexInput input)
{
	PixelInput output;
	output.pos = mul(input.pos, world);
	output.pos = mul(output.pos, view);
	output.pos = mul(output.pos, projection);
	
	output.uv = input.uv;
	
	return output;
}

Texture2D map : register(t0);
SamplerState samp : register(s0);

float4 PS(PixelInput input) : SV_TARGET
{
	return map.Sample(samp, input.uv);
}

위 hlsl이 새롭게 만드는. UV맵좌표를 활용하는  hlsl이다.

 

위 hlsl은 srv파일 데이터를 register t0와 s0에 각각 받아와서 SamplerState와 Texture2D맵으로. UV좌표를 통해 이미지를 원하는 부분만큼 잘라서 붙여주는 코드이다.

 


자 대충 hlsl과 hlsl을 사용하기위한 사전준비가 끝났다.

 

이제 hlsl을 활용하고, 오브젝트 데이터에 기입하고 활용하기 위해 조금씩 변화를 주어보자.

 

class Cube : public Transform
{
public:
	Cube(Float3 size = { 1, 1, 1});
	~Cube();

	void Update();
	void Render();

private:
	Material* material;
	Mesh<VertexUV>* mesh;

	MatrixBuffer* worldBuffer;

	ID3D11ShaderResourceView* srv;
};

수정된 Cube코드이다.  각 VertexBuffer와 IndexBuffer, VertexShader, PixelShader를 통합시킨모습을 볼 수 있다.

 

여기서 srv가 추가됨을 알 수 있는데, 이 srv파일은 ShaderResourceView로. 이미지파일을 경로상으로 긁어와서 보관하는 파일 이라고 생각하면 된다.


Cube의 생성자이다.

Cube::Cube(Float3 size)
{
    material = new Material(L"Tutorial.hlsl",VERTEX_UV);
    mesh = new Mesh<VertexUV>();
    Float3 halfSize(size.x * 0.5, size.y * 0.5, size.z * 0.5);

    vector<VertexUV>& vertices = mesh->GetVertices();
    vertices.emplace_back(-halfSize.x, -halfSize.y, -halfSize.z, 0, 0);
    vertices.emplace_back(-halfSize.x, +halfSize.y, -halfSize.z, 1, 0);
    vertices.emplace_back(+halfSize.x, -halfSize.y, -halfSize.z, 0, 1);
    vertices.emplace_back(+halfSize.x, +halfSize.y, -halfSize.z, 1, 0);
                                                                
    vertices.emplace_back(-halfSize.x, -halfSize.y, +halfSize.z, 0, 1);
    vertices.emplace_back(-halfSize.x, +halfSize.y, +halfSize.z, 1, 1);
    vertices.emplace_back(+halfSize.x, -halfSize.y, +halfSize.z, 1, 1);
    vertices.emplace_back(+halfSize.x, +halfSize.y, +halfSize.z, 0, 0);

    vector<UINT>& indices = mesh->GetIndices();

    indices =
    {
        // Front
        0, 1, 2, 2, 1, 3,
        // UP
        1, 5, 3, 3, 5, 7,
        // Left
        0, 4, 1, 1, 4, 5,
        // Right
        2, 3, 6, 6, 3, 7,
        // Down
        0, 2, 4, 4, 2, 6,
        // Back
        6, 7, 5, 5, 4, 6
    };

    
    mesh->CreateMesh();

    worldBuffer = new MatrixBuffer();

    ScratchImage image;
    LoadFromWICFile(L"Textures/Landscape/Box.png", WIC_FLAGS_NONE, nullptr, image);
    // srv할당
    CreateShaderResourceView(DEVICE, image.GetImages(), image.GetImageCount(), image.GetMetadata(), &srv);
}

여기에서 mesh 와 material을 new로 새롭게 할당하고 flag를 새워서. 원하는 데이터타입을 받아옴을 알 수 있을것이다.

 

VERTEX_UV로 설정되어있는 flag와 Tutorial.hlsl을 사용함을 명시해주고있다.

 

그후, mesh값의 GetVertices()를 별명선언으로 가져와서 활용하고, indices또한 마찬가지로 쓰는것을 볼 수있다.

 

위와같이 코딩함으로 원래 사용하던 코드를 큰 변화없이 활용할 수 있다.

 

그렇게 한다음에

 

mesh->CreateMesh()를 통하여.지금까지 기입된 데이터의 값들을 Buffer에 넣어주는 함수를 실행시킨다.

 

worldBuffer는 해당 객체의 world 위치를 정의해주는 Buffer임으로 항상 사용함에 유의한다.

 

 

ScratchImage image는, 이번에 가져온 DirectXTex데이터를 활용하는 객체이다.

LoadFromWICFile함수를 사용하여, 경로안에 있는 image파일을 저장하는데 사용된다.

 

그후, CreateShaderResouceView를 통하여  srv 파일에 필요한 데이터들을 전부 삽입해준다.

 

Render에서는 원래 있었던 Buffer값 대신.

 

DeviceContext에 PixelShaderResources값으로 srv를 전달하여. Vertex사이에 Color값이 아닌 Image값이 씌워질 수 있도록 해준다.

 

전부 마무리되었다면 Material값을 Set해주고. mesh의 Draw()함수를 실행시켜서. Vertex를 그려준다.

 

대충 그려놓은 Box. UV좌표가 전혀 맞지않아서 이미지Vertex가 일그러진게 눈에 띈다.

728x90