프로그래밍 공부
작성일
2023. 10. 11. 16:39
작성자
WDmil
728x90

Device까지 나누었으니, 이제 Buffer와 Shader를 나누어야 한다.

 

Buffer는 각각 VertexBuffer, IndexBuffer의 각 객체에 사용하는 버텍스와 버텍스 잇는 순서 Buffer로 두개.

 

Shader상수 데이터를 기입하기 위한 ConstantBuffer 한개.

 

ViewPort 변환을 위한 CamaraMatrix용 Buffer 한개.

 

로. 총 5개의 버퍼가 사용된다.

여기서. Shader상수 데이터가 여러개가 된다면 Buffer 또한 임의의 개수만큼 늘어날 수 있다.

 

Shader는. Vertex의 Color값이 들어가는 VertexShader와 해당 위치좌표의 Pixelcolor값을 결정할 PixelShader로 두개의 Shader가 기입된다.


대강 위와같은 구조로 Shader와 Buffer가 들어가게 된다.

#pragma once

#include "targetver.h"
#define WIN32_LEAN_AND_MEAN

#define WIN_START_X 100
#define WIN_START_Y 100

#define WIN_WIDTH 1280
#define WIN_HEIGHT 720

#define DEVICE Device::Get()->GetDevice()
#define DC     Device::Get()->GetDeviceContext()

#include <windows.h>
#include <string>
#include <vector>
#include <map>
#include <unordered_map>

#include <d3d11.h>
#include <d3dcompiler.h>
#include <DirectXMath.h>

#pragma comment(lib, "d3d11.lib")
#pragma comment(lib, "d3dcompiler.lib")

using namespace std;
using namespace DirectX;

typedef XMFLOAT4 Float4;
typedef XMFLOAT3 Float3;
typedef XMFLOAT2 Float2;
typedef XMMATRIX Matrix;
typedef XMVECTOR Vector4;	// 다양한 사용방식이 보장된 vector
typedef XMFLOAT4X4 Float4x4;

//Framework Header
#include "Framework/Utilities/Singleton.h"

#include "Framework/Device/Device.h"

// Shader Header
#include "Framework/Shader/VertexShader.h"
#include "Framework/Shader/PixelShader.h"

// Buffer Header
#include "Framework/Buffer/VertexBuffer.h"
#include "Framework/Buffer/IndexBuffer.h"
#include "Framework/Buffer/ConstBuffer.h"
#include "Framework/Buffer/GlobalBuffer.h"
#include "Framework/Buffer/VertexLayouts.h"
//Obejct Header
#include "Objects/Basic/Cube.h"

//Scene
#include "Scenes/Scene.h"
#include "Manager/GameManager.h"

extern HWND hWnd;

헤더정리.


VertexBuffer

#pragma once

class VertexBuffer
{
public:
	VertexBuffer(void* data, UINT stride, UINT count);
	~VertexBuffer();

	void Set(D3D11_PRIMITIVE_TOPOLOGY type = D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
private:
	ID3D11Buffer* buffer = nullptr;

	UINT stride, offset = 0;
};

선언시에 생성자로 받아와야 할 값은.

D3D11_BUFFER_DESC에 들어갈 byteWidth(테이터 한개 크기 * 데이터 총 개수) 와 해당 포인터의 첫 위치좌표.(void*)를 받아온다.

 

그리고,세팅한 버퍼를 DeviceContext로 옮겨주기 위한 Set을 구현한다.

#include "Framework.h"

VertexBuffer::VertexBuffer(void* data, UINT stride, UINT count)
	: stride(stride)
{
    //VertexBuffer
    D3D11_BUFFER_DESC bufferDesc = {};
    bufferDesc.Usage = D3D11_USAGE_DEFAULT;
    bufferDesc.ByteWidth = stride * count;                  // 옮길 사이즈
    bufferDesc.BindFlags = D3D11_BIND_VERTEX_BUFFER;        //용도

    D3D11_SUBRESOURCE_DATA subData = {};
    subData.pSysMem = data;                                 // 렘의 데이터 위치값

    DEVICE->CreateBuffer(&bufferDesc, &subData, &buffer);
}

VertexBuffer::~VertexBuffer()
{
    buffer->Release();
}

void VertexBuffer::Set(D3D11_PRIMITIVE_TOPOLOGY type)
{
    DC->IASetVertexBuffers(0, 1, &buffer, &stride, &offset);
    DC->IASetPrimitiveTopology(type);
}

CPP파일은 위와 같다.

 

생성자에서는 인풋 데이터를 사용하여 

DEVICE에 직접 buffer생성명령을 내려서 데이터의 버퍼를 제작한다.

 

소멸자에서는 동적으로 할당한 buffer를 날려준다.

 

Set에서는 해당 Buffer를. hlsl에서 받아올 레지서 번호와 버퍼, 데이터사이즈, 데이터 개수 값들을 기입해준뒤.

Vertex간의 이어지는 방식 ( 위에서는 삼각형으로 그려지는게 기본이다 ) 를 설정해준다.

모든값을 DeviceContext로 넘겨준다.


IndexBuffer

#pragma once

class IndexBuffer
{
public:
	IndexBuffer(void* data, UINT count);
	~IndexBuffer();

	void Set();
private:
	ID3D11Buffer* buffer = nullptr;
};

IndexBuffer의 헤더는 위와 같다.

 

Vertex와 대부분 동일하지만, Index는 항상 사이즈가 UINT로 동일하기에 따로 stride값을 받아오지 않는다.

#include "Framework.h"

IndexBuffer::IndexBuffer(void* data, UINT count)
{
    //IndexBuffer
    D3D11_BUFFER_DESC bufferDesc = {};
    bufferDesc.Usage = D3D11_USAGE_DEFAULT;
    bufferDesc.ByteWidth = sizeof(UINT) * count;
    bufferDesc.BindFlags = D3D11_BIND_INDEX_BUFFER;

    D3D11_SUBRESOURCE_DATA subData = {};
    subData.pSysMem = data;

    DEVICE->CreateBuffer(&bufferDesc, &subData, &buffer);
}

IndexBuffer::~IndexBuffer()
{
    buffer->Release();
}

void IndexBuffer::Set()
{
    DC->IASetIndexBuffer(buffer, DXGI_FORMAT_R32_UINT, 0);
}

생성자로서 배열의 시작점 포인터와 배열개수를 받아온뒤 Vertex값과 동일하게 데이터를 기입해준다.

 

Set에서는 받아온 buffer를 데이터의 포멧, 시작점 과 함께 DeviceContext로 넘겨준다.


ConstBuffer

#pragma once
class ConstBuffer
{
public:
	ConstBuffer(void* data, UINT dataSize);
	~ConstBuffer();

	void SetVS(UINT slot);
	void SetPS(UINT slot);

private:
	ID3D11Buffer* buffer = nullptr;

	void* data;
	UINT dataSize;
};

상수버퍼를 작업해준다.

 

상수버퍼는 다른 버퍼와 다르게 다양한 데이터 방식이 들어올 수 있다는 가정하에 제작한다.

Matrix가 들어오는게 일반적이나. 아닐수도 있기 때문이다.

 

SetVS와 SetPS를 제외한 나머지 는 동일하다.

#include "Framework.h"

ConstBuffer::ConstBuffer(void* data, UINT dataSize)
	: data(data), dataSize(dataSize)
{
    //ConstantBuffer
    D3D11_BUFFER_DESC bufferDesc = {};
    bufferDesc.Usage = D3D11_USAGE_DEFAULT;
    bufferDesc.ByteWidth = dataSize;
    bufferDesc.BindFlags = D3D11_BIND_CONSTANT_BUFFER;

    DEVICE->CreateBuffer(&bufferDesc, nullptr, &buffer);
}

ConstBuffer::~ConstBuffer()
{
    buffer->Release();
}

void ConstBuffer::SetVS(UINT slot)
{
    // 말이 상수버퍼이지 cpu에서 사용할때 에는 cpu에서 큐브 돌릴때의 월드행렬이 들어있다.
    DC->UpdateSubresource(buffer, 0, nullptr, data, 0, 0);
    DC->VSSetConstantBuffers(slot, 1, &buffer); // slot = hlsl slot number
}

void ConstBuffer::SetPS(UINT slot)
{
    DC->UpdateSubresource(buffer, 0, nullptr, data, 0, 0);
    DC->PSSetConstantBuffers(slot, 1, &buffer); // slot = hlsl slot number
}

상수버퍼는 다른 Buffer와 비슷한 방식으로 데이터가 들어가나 BindFlags는 이 Buffer가 CONSTANT 라는 것을 알려주어야 한다.

 

SetVS와 SetPS는 DeviceContext에 데이터가 들어갈 때. 해당되는 slot의 hlsl에 데이터가 어떤식으로 기입되느냐 에 따라 달라진다.

 

VS는 Vertex형태로. PS는 Pixel 형태로 Buffer가 작성되어. 지정된 hlsl의 슬롯에 데이터가 전달된다.

 


MatrixBuffer

#pragma once

class MatrixBuffer : public ConstBuffer
{
public:
	MatrixBuffer() : ConstBuffer(&matrix, sizeof(Matrix))
	{
		matrix = XMMatrixIdentity();
	}
	
	void Set(Matrix value)
	{
		matrix = XMMatrixTranspose(value);
	}

private:
	Matrix matrix;
};

기본행렬 버퍼이다.

ConstBuffer를 상속하여. 생성된 matrix데이터를 Identity()형태의 Matrix로 생성하여 ConstBuffer에 전달해.

 

Matrix형 ConstBuffer를 생성하게 한다.

 

Set을 통해. 지정된 Matrix Transpose값을 지정하여 ConstBuffer형으로. Set을 지정하게 되면. 변환된 Matrix 행렬이 DeviceContext로 전달되게 된다.


VertexLayouts

#pragma once

struct Vertex
{
    Float3 pos = {};

    Vertex() {}

    Vertex(float x, float y, float z)
        : pos(x, y, z)
    {}
};

struct VertexColor
{
    Float3 pos = {};
    Float4 color = { 1, 1, 1, 1 };

    VertexColor() {}

    VertexColor(float x, float y, float z, float r, float g, float b)
        : pos(x, y, z), color(r, g, b, 1)
    {
    }
};

Buffer는 아니지만 같은 폴더에 생성하여 같이 소개한다.

 

Vertex의 기본형태를 MXFLOAT3형태로 지정하여. position값을 기입해준다.

VertexColor는 지정된 MAFLOAT3외에도 color값을 기입하여. VertexColor를 사용할 수 있게 해준다.


VertexShader

#pragma once

class VertexShader
{
public:
	VertexShader(wstring file);
	~VertexShader();
	void Set();

private:
	ID3DBlob* blob;

	ID3D11VertexShader* vertexShader;
	ID3D11InputLayout* inputLayout;
};

위에 설명한 Buffer와 다르게. 데이터가 blob을 통해서 통신한다.

 

VertexShader임으로 VertexShader형태를 사용하고. Vertex외에도 Vertex데이터 구성요소가 지정되어야 함으로 InputLayouteh 또한 VertexShader에서 관리한다.

#include "Framework.h"

VertexShader::VertexShader(wstring file)
{
    DWORD flags = D3DCOMPILE_ENABLE_STRICTNESS | D3DCOMPILE_DEBUG;

    D3DCompileFromFile(file.c_str(), nullptr, nullptr,
        "VS", "vs_5_0", flags, 0, &blob, nullptr);

    DEVICE->CreateVertexShader(blob->GetBufferPointer(), blob->GetBufferSize(),
        nullptr, &vertexShader);

    D3D11_INPUT_ELEMENT_DESC layoutDesc[] =
    {
        {"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}
    };

    UINT layoutSize = ARRAYSIZE(layoutDesc);

    DEVICE->CreateInputLayout(layoutDesc, layoutSize,
        blob->GetBufferPointer(), blob->GetBufferSize(), &inputLayout);
}

VertexShader::~VertexShader()
{
    blob->Release();
    vertexShader->Release();
}

void VertexShader::Set()
{
    DC->IASetInputLayout(inputLayout);
    DC->VSSetShader(vertexShader, nullptr, 0);
}

우선 flags를 지정하여 데이터가 어떤 방식으로 처리가 이루어지는지. 알려주어야한다.

 

그후. CompileFromFile을 통해 인풋으로 받아온 file경로를 c_str()로 변환하여 기입해주고. VS에 데이터가 들어갈 것이다. vs_5.0을 사용할것이다. flags는 이것이고. blob의 포인터를 지정하여 기입해주면 

 

blob DeviceContext와 통신할 기본정보가 들어간다.

 

그 데이터들을 Device의 CreateVertexShader함수를 사용하여. Blob의 위치좌표, Size를 가져와서 vertexShader를 만들어준다.

 

INPUT_ELEMENT_DESC 를 사용하여. 현재 기입될 Vertex값의 데이터 네임과 사이즈 유형, 앞부분에 어떤 데이터가 있는지 누가 사용할 데이터 인지 등 을 넣어준다.

 

COLOR에 12가 들어있는 이유는. POSITION의 데이터사이즈가 R32G32B32로 12byte이기 때문이다.

1byte = 8bit, 32bit = 4byte, 32 * 3 bit = 4byte * 3

 

이렇게 하면 DeviceContext가 COLOR에 접근하기위해서 앞부터 탐색할 필요 없이. 12byte만 띄우고 다음 데이터를 탐색하면 되기 때문이다.

 

그 후 layout의 Size를 ARRAYSIZE를 통해 측정해주고.

 

CreateInputLayout함수를 통해 layoutDesc와 Descsize값. 위에서 입력된 blob의 VertexShader의 Point값과 Size값을 사용하여 inputLayout을 작성한다.

 

이렇게 되면. InputLayout과 VertexShader에 데이터가 기입되고. Blob의 사용이 마무리된다.

 

Blob은 순전 데이터 기입을 위한 도구 라고 이해하면 된다.

 

Set함수를 통해 DeviceContext의 IA라인에 InputLayout을 기입하고. VS에 vertexShader를 넣는다.


PixelShader

#pragma once

class PixelShader
{
public:
	PixelShader(wstring file);
	~PixelShader();
	void Set();

private:
	ID3DBlob* blob;

	ID3D11PixelShader* pixelShader;
};

PixelShader는 VertexShader에 비해 더간단한 데이터구조를 가진다.

 

그저 VS에서 도출된 데이터 결과값을 받아와서 PS에서 사용하기 때문에 많은 데이터가 삽입될 필요가 없다.

#include "Framework.h"

PixelShader::PixelShader(wstring file)
{
    DWORD flags = D3DCOMPILE_ENABLE_STRICTNESS | D3DCOMPILE_DEBUG;

    D3DCompileFromFile(file.c_str(), nullptr, nullptr,
        "PS", "ps_5_0", flags, 0, &blob, nullptr);

    DEVICE->CreatePixelShader(blob->GetBufferPointer(), blob->GetBufferSize(), nullptr, &pixelShader);
}

PixelShader::~PixelShader()
{
    blob->Release();
    pixelShader->Release();
}

void PixelShader::Set()
{
    DC->PSSetShader(pixelShader, nullptr, 0);
}

Pixelshader에서는 VertexShader와 같이 flags를 기입하고. CompileFromFile을 통해 경로, HLSL의 어느부분에서 이 데이터를 활용할것인지, 어떤 버전을 쓸것인지, flags와 blob을 넣어서 기본 데이터를 생성한다.

 

그 후 CreatePixelShader를 통해 Pixelshader값을 blob을 이용해 기입해준다.

 

Set에서는 DeviceContext에 PSSetShader을 통하여 pixelShader내부의 값을 전달해준다.


여기까지 해서. Object에 사용될 Buffer와 Shader의 정리가 끝났다.

 

일단 Viewport설정이 이루어지지 않았음으로 TutorialScene에 임의로 기입해서 사용할 수 있도록 해준다.

#pragma once

class TutorialScene : public Scene
{
private:
    UINT HIGHT = 10;
public:
    TutorialScene();
    ~TutorialScene();

    void Update() override;
    void PreRender() override;
    void Render() override;
    void PostRender() override;
    void GUIRender() override;
private:

    Cube* cube;

    MatrixBuffer* viewBuffer;
    MatrixBuffer* projectionBuffer;
};
#include "Framework.h"
#include "TutorialScene.h"

TutorialScene::TutorialScene()
{
    cube = new Cube();

    XMVECTOR eye = XMVectorSet(3, 30, -30, 0);//Cam Pos
    XMVECTOR focus = XMVectorSet(0, 0, 0, 0);//Cam Look at Pos
    XMVECTOR up = XMVectorSet(0, 1, 0, 0);//Cam Up Vector

    Matrix view = XMMatrixLookAtLH(eye, focus, up);

    Matrix projection = XMMatrixPerspectiveFovLH(XM_PIDIV4, 
        (float)WIN_WIDTH / WIN_HEIGHT, 0.1f, 1000.0f);

    viewBuffer = new MatrixBuffer();
    projectionBuffer = new MatrixBuffer();

    viewBuffer->Set(view);
    viewBuffer->SetVS(1);

    projectionBuffer->Set(projection);
    projectionBuffer->SetVS(2);
}

TutorialScene::~TutorialScene()
{
    delete viewBuffer;
    delete projectionBuffer;
}

void TutorialScene::Update()
{
    cube->Update();
}

void TutorialScene::PreRender()
{
}

void TutorialScene::Render()
{
    cube->Render();
}

void TutorialScene::PostRender()
{
}

void TutorialScene::GUIRender()
{
}

위와같이 ViewBuffer를 선언해주고, Buffer에서 만들어두었던 Matrix를 통해 view를 설정해주고 projection을 기입해준다.

 

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;
	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;
}

 

여기서 WVP를 묶어서 처리해도되고, 각각 Word, View, Projection으로 나누어서 처리해도 무방하다.

어차피 같은 결과가 나오기 때문.

 

viewBuffer의 SetVS(1)의 1이 들어가는 이유는 Buffer가 들어오는 레지스터의 번호가 1 이기 때문이다

마찬가지로 projectionBuffer가 2인 이유도 레지스터 번호가 2이기 때문이다.


뷰와 Buffer, Shader설정이 끝났으면. 이제 Object를 만들 기초단계가 다 끝났다.

 

오브젝트에 위에서 만들어놓은 Buffer중 VertexBuffer, IndexBuffer, MatrixBuffer

셰이더는 VertexShader, PixelShader

를 변수로 선언하고,

 

vertices와 indices를 넣기위해 다음과 같은 private변수를 선언한다.

#pragma once

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

	void Update();
	void Render();

	void SetPos(float x, float y, float z) { pos = { x, y, z }; }
private:
	VertexShader* vertexShader;
	PixelShader* pixelShader;

	VertexBuffer* vertexBuffer;
	IndexBuffer* indexBuffer;

	MatrixBuffer* worldBuffer;

	vector<VertexColor> vertices;
	vector<UINT> indices;

	Float3 pos = {};
};

Shader는 각 버텍스와 픽셀의 색처리에 사용된다.

Buffer는 버텍스와 버텍스를 잇는 순서가 들어간다.

 

MatrixBuffe는 생성한 객체의 World좌표 계산에 사용된다.

 

pos는 오브젝트의 위치값을 의미한다.

#include "Framework.h"

Cube::Cube(Float3 size)
{
	vertexShader = new VertexShader(L"Shaders/Tutorial.hlsl");
	pixelShader = new PixelShader(L"Shaders/Tutorial.hlsl");

    vertices.emplace_back(-1, -1, -1, 1, 0, 0);
    vertices.emplace_back(-1, +1, -1, 0, 1, 0);
    vertices.emplace_back(+1, -1, -1, 0, 0, 1);
    vertices.emplace_back(+1, +1, -1, 1, 1, 0);

    vertices.emplace_back(-1, -1, +1, 1, 0, 1);
    vertices.emplace_back(-1, +1, +1, 0, 1, 1);
    vertices.emplace_back(+1, -1, +1, 1, 1, 1);
    vertices.emplace_back(+1, +1, +1, 0, 0, 0);

    vertexBuffer = new VertexBuffer(vertices.data(), 
        sizeof(VertexColor), vertices.size());

    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
    };
    indexBuffer = new IndexBuffer(indices.data(), indices.size());

    worldBuffer = new MatrixBuffer();
}

Cube::~Cube()
{
    delete vertexShader;
    delete pixelShader;
    delete vertexBuffer;
    delete indexBuffer;
    delete worldBuffer;
}

void Cube::Update()
{
    // SRT
    worldBuffer->Set(XMMatrixTranslation(pos.x, pos.y, pos.z));
}

void Cube::Render()
{
    vertexBuffer->Set();
    indexBuffer->Set();

    worldBuffer->SetVS(0);

    vertexShader->Set();
    pixelShader->Set();

    DC->DrawIndexed(indices.size(), 0, 0);
}

나머지 코드는 전부 작성했음으로.

 

Object에서는 필요한값들이 Shader지정과 vertices의 값 삽입, Buffer의 생성이 있다.

 

update에서는 worldBuffer의 데이터삽입을 통해 현재 Object의 위치값을 갱신해주는 역할을 한다.

 

Render에서는 위에서 지정해놓았던 Buffer와 Shader의 Set을 사용한다.

 

마지막의 DrawIndexed는 지정된 방법대로, 기입된 데이터대로 버텍스의 indices의 사이즈만큼 삼각형이던 선분이던 그려서 나타낸다.

결과값

728x90