57일차. DirectX11->Renders
DirectX의 Renders부분에 대하여 설명한다.
Renders는 그래픽 리소스와 관련된 여러 구성 요소를 포함한다.
IA ( ImputAssembler )
그래픽 파이프라인의 시작 부분에 위치한 단계이다.
그래픽 하드웨어에 그래픽 데이터를 전달하기 전에 데이터를 준비하고 정리하는 역할을 한다.
IA는 그래픽 데이터의 형식을 정의하고, 버텍스 데이터를 읽어들여 삼각형으로 조립한다.
이렇게 조립된 프리미티브는 다음 그래픽 파이프라인 단계로 전달되어 처리된다.
VertexBuffer
VertexBuffer는 DirectX의 그래픽 어플리케이션에 사용되는 정점 데이터를 저장하는 버퍼이다.
정점 데이터는 3D모델의 점, 선, 면 등의 기하학적 정보를 포함한다.
각 정점은 위치, 법선 벡터, 텍스처 좌표 및 추가적인 사용자 지정 데이터와 같은 속성을 가질 수 있다.
VertexBuffer는 정점데이터를 저장하고, 그래픽 파이프라인의 다음단계로 전달하게 된다.
#pragma once
class VertexBuffer
{
public:
~VertexBuffer();
template<typename T>
void Create(const vector<T>& vertices,
const D3D11_USAGE& usage = D3D11_USAGE_DEFAULT);
uint GetStride() { return stride; }
uint GetOffset() { return offset; }
uint GetCount() { return count; }
void SetIA();
private:
ID3D11Buffer* buffer = nullptr;
uint stride = 0;
// 정점 한개의 크기
uint offset = 0;
// 앞부터 몃번째 떨어져 있는걸로 시작해라.
// 필요한 정보만 다시 호출할때 그만큼 띄어놓아라.
uint count = 0;
};
template <typename T>
inline void VertexBuffer::Create(const vector<T>& vertices, const D3D11_USAGE& usage)
{
stride = sizeof(T);
count = vertices.size();
D3D11_BUFFER_DESC desc;
// 버퍼는 내용저장이기 때문에, 보통 DESC만 만들어서 할당해서 사용한다.
ZeroMemory(&desc, sizeof(D3D11_BUFFER_DESC)); // 메모리 초기화
// desc 설정
{
desc.Usage = usage;
desc.BindFlags = D3D11_BIND_VERTEX_BUFFER;
desc.ByteWidth = stride * count;
// Usage에 따라 cpu에 접근 여부
switch (usage)
{
// GPU 에서만 접근 가능한 버퍼
case D3D11_USAGE_DEFAULT:
// GPU 에서만 접근 가능하고 수정할 수 없는 버퍼
case D3D11_USAGE_IMMUTABLE:
break;
// CPU에서 접근 가능하고 동적으로 수정 가능한 버퍼
case D3D11_USAGE_DYNAMIC:
// CPU에서 수정 가능함을 설정
desc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
break;
// CPU에서 접근 가능, GPU에서 사용할 수 있는 형태로 변환 가능한 버퍼
case D3D11_USAGE_STAGING:
desc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE | D3D11_CPU_ACCESS_READ;
break;
}
// 권한 할당.
}
// 생성 단계
{
D3D11_SUBRESOURCE_DATA subData; // 정점 데이터를 담을 구조체
ZeroMemory(&subData, sizeof(D3D11_SUBRESOURCE_DATA)); // 구조체 초기화
subData.pSysMem = vertices.data(); // 정점 데이터를 할당한 메모리에 복사
HRESULT hr = DEVICE->CreateBuffer(&desc, &subData, &buffer); // 정점 버퍼 생성
CHECK(hr); // 실패시 펑
}
}
VertexBuffer에서 Create는, VertexBuffer의 데이터를 직접 지정해주고 생성하는 역할을 한다.
VertexBuffer에 대한 각 항목인 ID3D11Buffer* , unsinged int 형인 stride , offset, count 를 초기화한다.
정점 한개의 크기는 sizeof(T)로, 정점 한개에 대한 사이즈를 받아온 형식으로 지정해주고
(대부분 float을 사용한다)
count 를, vertices의 size를 가져와 배열의 개수가 몃개인지 확인한다. (항목이 몃개인지?)
그 후 D3D11_BUFFER_DESC 형식을 ZeroMemory 로 초기화해준다.
D3D11_BUFFER_DESC
- "ByteWidth": 생성할 버퍼의 크기를 바이트 단위로 지정한다.
- "Usage": 버퍼의 사용 용도를 정의한다. 예를 들어, 버퍼를 정점 버퍼로 사용할 것인지, 상수 버퍼로 사용할 것인지를 지정한다.
- "BindFlags": 생성된 버퍼를 어떤 용도로 바인딩할지를 지정한다.
예를 들어, 정점 버퍼로 사용할 경우 D3D11_BIND_VERTEX_BUFFER를 지정한다. - "CPUAccessFlags": CPU에서 버퍼에 액세스할 수 있는지를 지정한다.
예를 들어, 버퍼를 동적으로 업데이트해야 하는 경우 D3D11_CPU_ACCESS_WRITE를 지정한다. - "MiscFlags": 버퍼의 기타 특성을 정의한다. 대부분의 경우 0으로 설정된다.
- "StructureByteStride": 구조적 버퍼를 사용하는 경우, 각 요소의 크기를 바이트 단위로 지정한다.
구조적 버퍼를 사용하지 않을 경우 0으로 설정된다.
Usage에 따라, 해당 버퍼가 GPU에서만 접근이 가능한지, 접근이 가능하나 상수인지, CPU에서 접근이 가능하고 동적수정이 가능한지 등의 값으로 설정해준다.
권한을 할당한다.
그후, D3D11_SUBRESOURCE_DATA 형 subData를 생성한 뒤, 구조체를 ZeroMemory로 초기화 한다.
D3D11_SUBRESOURCE_DATA
- "pSysMem": 초기 데이터가 있는 메모리 블록의 포인터를 가리킨다.
이 포인터는 초기화할 리소스의 데이터를 가리키는 포인터이다. - "SysMemPitch": 데이터가 저장된 메모리의 각 행(텍스처의 경우) 또는 요소(버퍼의 경우)의
바이트 단위 크기를 나타낸다. - "SysMemSlicePitch": 데이터가 저장된 메모리의 각 슬라이스(텍스처 배열의 경우)의
바이트 단위 크기를 나타낸다.
그러니까, D3D11_BUFFER_DESC 는, 데이터의 형식, 접근여부, 특성 등 정점버퍼의 특징을 담아낸다.
D3D11_SUBRESOURCE_DATA는, 버퍼의 데이터 블록의 사이즈, 메모리 슬라이스 사이즈 등 버퍼 자체의 데이터 사이즈를 확인한다.
그후
HRESULT hr = DEVICE->CreateBuffer(&desc, &subData, &buffer); 를 통하여
buffer에 desc와 subData의 값을 삽입해주고 HRESULT 로 실패와 성공여부를 확인한다.
CHCK(hr)을 통해 실패가 반환시 프로그램을 터트린다.
Resources
리소스는 그래픽 어플리케이션에 사용되는 그래픽 데이터를 나타낸다. 자원은 GPU 에서 처리되는 텍스처, 버퍼, 셰이더 상수 등을 포함할 수 있다.
VertexType
버텍스 자체의 정보를 저장하기 위한 구조체이다.
버텍스는 3D또는2D모델의 정점에 대한 정보를 포함하며, 위치, 색상, 텍스처 좌표 등의 속성을 저장하는 역할을 한다.
이 구조체는 그래픽 어플리케이션의 정점 데이터를 정의하고 사용하는데 사용된다.
// 버텍스 정보를 저장하기 위한 구조체
#pragma once
// 색상 정보
struct VertexColor
{
VertexColor()
: position(0, 0, 0), color(0, 0, 0) {}
VertexColor(Vector3 position, Vector3 color)
: position(position), color(color) {}
Vector3 position;
Vector3 color;
// 정점데이터 구조를 서술
static D3D11_INPUT_ELEMENT_DESC descs[];
static const uint count = 2; // 원소 개수
};
// 텍스처
struct VertexTexture
{
VertexTexture()
: position(0, 0, 0), uv(0, 0) {}
VertexTexture(Vector3 position, Vector2 uv)
: position(0, 0, 0), uv(uv) {}
Vector3 position;
Vector2 uv;
// 정점데이터 구조를 서술
static D3D11_INPUT_ELEMENT_DESC descs[];
static const uint count = 2; // 원소 개수
};
/*
D3D11_INPUT_ELEMENT_DESC
SemanticName : 정점 데이터의 의미를 나타내는 문구
SemanticIndex : 위 이름이 중복되는 경우 구분하기 위한 인덱스 값
Format : 데이터 형식을 나타냄
InputSlot : 입력 슬롯의 인덱스
AlignedByteOffset : 정점 데이터의 바이트 단위 오프셋 (0 = 자동계산)
InputSlotClass : 사용되는 데이터 형식을 지정
InstanceDataStepRate : 인스턴스마다 데이터를 읽어들이는데 사용할 스텝 레이트 값
*/
D3D11_INPUT_ELEMENT_DESC VertexColor::descs[]
{
{"POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0},
{"COLOR", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, D3D11_APPEND_ALIGNED_ELEMENT, D3D11_INPUT_PER_VERTEX_DATA, 0}
};
D3D11_INPUT_ELEMENT_DESC VertexTexture::descs[]
{
{"POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0},
{"TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, D3D11_APPEND_ALIGNED_ELEMENT, D3D11_INPUT_PER_VERTEX_DATA, 0}
};
VertexColor에 생성자로써 Color값과 Position값을 삽입하여 동작시킨다.
또한, static으로 전역선언하여, D3D11_INPUT_ELEMENT_DESC 형의 배열을 한개 만들어준다.
그리고, 사용하는 데이터의 종류가 position과 color 두가지 임으로, count는 2로 두개의 값을 가짐을 명시해준다.
VertexTexture도 같은 방식으로 uv와 position을 정의해준다.
D3D11_INPUT_ELEMENT_DESC
- SemanticName: 정점 데이터의 의미를 나타내는 문자열이다.
예를 들어, "POSITION", "NORMAL", "TEXCOORD" 등이 사용될 수 있다.. - SemanticIndex: 위의 SemanticName이 중복되는 경우 구분하기 위한 인덱스 값이다.
- Format: 데이터 형식을 나타낸다.
예를 들어, 32비트 부동소수점 좌표, 8비트 정수 색상 등을 나타낼 수 있다. - InputSlot: 입력 슬롯의 인덱스를 나타낸다.
여러 입력 슬롯이 있는 경우 해당 정점 데이터가 어떤 입력 슬롯에 할당되는지를 지정한다. - AlignedByteOffset: 정점 데이터의 바이트 단위 오프셋을 나타낸다.
이 값을 0으로 설정하면 자동으로 계산된다. - InputSlotClass: 사용되는 데이터 형식을 지정한다.
주로 D3D11_INPUT_PER_VERTEX_DATA 또는 D3D11_INPUT_PER_INSTANCE_DATA가 사용된다. - InstanceDataStepRate: 인스턴스마다 데이터를 읽어들이는 데 사용할 스텝 레이트 값을 나타낸다.
ShaderBuffer
GPU에서 사용하는 데이터를 CPU에서 생성하고 수정할 수 있도록 버퍼를 제공하는 역할을 한다.
// GPU 에서 사용하는 데이터를 CPU에서 생성하고 수정할 수 있도록 버퍼를 제공하는 역할
#pragma once
#include "Framework.h"
class ShaderBuffer
{
public:
void SetVSBuffer(uint slot)
{
MapData();
DC->VSSetConstantBuffers(slot, 1, &buffer); // 버텍스 슬롯에 한개의 버퍼를 이 버퍼로 세팅하겠다.
}
void SetPSBuffer(uint slot)
{
MapData();
DC->PSSetConstantBuffers(slot, 1, &buffer); // 버텍스 슬롯에 한개의 버퍼를 이 버퍼로 세팅하겠다.
}
protected:
// GPU 쪽에서 사용하는 데이터를 CPU 쪽에서 생성하고 수정할 수 있도록 제공하는 버퍼
ShaderBuffer(void* data, uint dataSize)
{
desc.Usage = D3D11_USAGE_DYNAMIC;
desc.ByteWidth = dataSize;
desc.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
desc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
HRESULT hr = DEVICE->CreateBuffer(&desc, nullptr, &buffer);
CHECK(hr);
}
private:
// 데이터를 열어주는 용도의 MapData이다.
void MapData()
{
D3D11_MAPPED_SUBRESOURCE subResource;
HRESULT hr = DC->Map
(
buffer, // 매핑할 그래픽 리소스
0, // 시작 바이트 위치
D3D11_MAP_WRITE_DISCARD, // 매핑 방법 ( 완전히 새로 쓴다 )
0, // 매핑을 원하는 하위 리소스
&subResource // 매핑된 메모리 주소를 전달
);
CHECK(hr);
memcpy(subResource.pData, data, dataSize);
DC->Unmap(buffer, 0);
}
private:
D3D11_BUFFER_DESC desc = { 0 };
ID3D11Buffer* buffer = nullptr;
void* data = nullptr;
uint dataSize = 0;
};
ShaderBuffer에서 SetVSBuffer을 통해, 버텍스 슬롯에 한개의 버퍼를 지정된 버퍼로 세팅한다는 의미이다.
SetPSBuffer 또한 버텍스 슬롯에 한개의 버퍼를 지정된 버퍼로 세팅하겠다는 의미이다.
ShaderBuffer의 생성자로 data와 uint 형태의 dataSize를 받아온다.
그 후, desc를 지정해준뒤, hr로 buffer에 값을 밀어넣어준다.쉐이더 값 임으로, 버텍스값은 기입되지 않는다.
MapData로 hr에 값을 넣어서 buffer값을 넣어준뒤, memcpy로 지정된 값들을 전부 외부로 빼내어 도출시켜
값이 어떤것이 들어있는지 확인해줄 수 있다.
private에 기입하여. 함수를 상속받는 위치에서만 작동할 수 있게 정의해준다.