Constant Buffer
상수 버퍼는 Vertex Shader 단계에서 변수를 사용하고 싶을 때 이용할 수 있는 녀석이다.
그 전 포스팅에서 CreateGeometry 함수에서 도형에 대한 기하학정보를 하드코딩으로 정했다.
근데 만약 우리가 진지한 게임을 만든다고 했을 때, 플레이어가 일일이 움직이는 것을 보정할 순 없는 노릇이다.
왜냐하면 CPU에서 우리가 게임을 만든다고 하면 Update 문에서 1틱당 얼만큼의 위치 벡터를 곱해주는 형식으로 플레이어를 이동할 수 있겠지만 우리가 GPU에서 지금까지 작업하면서 했던 것중 하나는 바로 CPU의 정보를 GPU로 넘겨주는 것이었다. 그리고 우리가 작업할 때 Usage를 D3D11_USAGE_IMMUTABLE로 설정했다.
이말은 즉슨, 1회성으로 CPU가 작업을 한 후 GPU에게 넘겨주면 그 뒤로 수정할 수 없다는 것이다.
하지만 플레이어는 계속 움직이기 때문에 문제가 발생한다.
근데 여기서 근본적인 의문이 발생한다. 왜 CPU에서 Index Buffer나 Vertex Buffer를 건네주고 나서 수정이 애초에 불가능하게 소통해야하는가? 이다.
오해를 하면 안되는 것은 바로 우리가 ndex Buffer나 Vertex Buffer를 통해서 정점을 건네줄 때는 어떤 물체 자체의 정점을 건네주는 것이지, 이동할 때마다의 좌표, 정점을 건네주는 것이 아니다.
가령 예를 들어 블랜더나 3D Max를 통해 어떤 모델링을한 캐릭터가 있는데 그 캐릭터의 정점을 GPU로 넘겨주는 것이지 그 캐릭터가 움직일 때 일일이 GPU로 넘겨주는 것이 아니라는 것이다.
그러면 다시 본래 얘기로 돌아와서 플레이어를 이동할 때는 그러면 이미 정점을 고정시키고 이동할 값만 건네주면 되는 것이다. 그러한 변수를 넣고싶을 때 사용하는 것이 Constant Buffer인 셈이다.
한번 직접해보자.
Shader에서 추가적인 정보를 받을 수 있다.
cbuffer TransformData : register(b0)
{
float4 offset;
}
// IA - VS - RS - PS - OM
// 그중 VS
VS_OUTPUT VS(VS_INPUT input)
{
VS_OUTPUT output;
output.position = input.position + offset;
output.uv = input.uv;
return output;
}
CPU에서 열심히 값을 세팅해서 vs를 생성하면 그 인자중 TransformData를 건네 받아 레지스터에 저장해 VS 단계에서 offset을 적용할 수 있다.
private:
TransformData _transformData;
ComPtr<ID3D11Buffer> _constantBuffer = nullptr;
void Game::CreateConstantBuffer()
{
D3D11_BUFFER_DESC desc;
ZeroMemory(&desc, sizeof(desc));
desc.Usage = D3D11_USAGE_DYNAMIC; // CPU_Write + GPU_Read
desc.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
desc.ByteWidth = sizeof(TransformData);
desc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
HRESULT hr = _device->CreateBuffer(&desc, nullptr, _constantBuffer.GetAddressOf());
CHECK(hr);
}
이제 매 프레임 마다 constantBuffer에 특정 값을 복사에서 건네주면 된다.
void Game::Update()
{
_transformData.offset.x = 0.3f;
_transformData.offset.y = 0.3f;
D3D11_MAPPED_SUBRESOURCE subResource;
ZeroMemory(&subResource, sizeof(subResource));
//데이터 뚜껑 열기
_deviceContext->Map(_constantBuffer.Get(), 0, D3D11_MAP_WRITE_DISCARD, 0 , &subResource);
// 데이터 열기
::memcpy(subResource.pData, &_transformData, sizeof(_transformData));
// 데이터 뚜껑 닫기
_deviceContext->Unmap(_constantBuffer.Get(), 0);
}
그 결과 오른쪽 상단으로 이동한 모습이다. =가 아닌 += 을 했다면 이동하는 모습도 볼 수 있다.
결론적으로 말하는건 맨처음에 우리가 넘겨준 vertexBuffer는 물체의 기하학적인 고유 정보인 셈이고 constantBuffer는 그뒤로 사용할 변수를 건네주는 것이다. 정말 매우매우 중요한 개념이다.
State
RasterizerState와 SampleState, BlendState를 알아보자. Rasterizer단계에선 코딩할 수 없지만 설정은 가능하다.
VS에서 건네준 삼각형을 분석해서 보간해주는 역할을 한다. 웬만하면 원서를 읽는 것이 좋다.
실제로 한번 사용해보자.
ComPtr<ID3D11RasterizerState> _rasterizerState = nullptr;
void Game::CreateRasterizerState()
{
D3D11_RASTERIZER_DESC desc;
ZeroMemory(&desc, sizeof(desc));
desc.FillMode = D3D11_FILL_SOLID;
desc.CullMode = D3D11_CULL_BACK;
desc.FrontCounterClockwise = false;
HRESULT hr = _device->CreateRasterizerState(&desc, _rasterizerState.GetAddressOf());
CHECK(hr);
}
이렇게 했을 때 fillMode나 CullMode, FrontCounterClockwise에 따라 그림을 그려줄 지 아닐지 등 옵션을 선택할 수 있다. 가령 바라보는 방향에 따라 그림을 그리거나 그리지 않거나 하는 상황에서 메모리를 아끼기 위해 선택하는 것이다.
SamplerState는 UV와 관련이 있는데 만약 UV가 기존 1을 초과하는 경우에 대신 처리주는 역할을 이 단계에서 한다고 생각하면 된다.
각종 정보는 원서를 보고 채워 넣으면 된다.
void Game::CreateSamplerState()
{
D3D11_SAMPLER_DESC desc;
ZeroMemory(&desc, sizeof(desc));
desc.AddressU = D3D11_TEXTURE_ADDRESS_BORDER;
desc.AddressV = D3D11_TEXTURE_ADDRESS_BORDER;
desc.AddressW = D3D11_TEXTURE_ADDRESS_BORDER;
desc.BorderColor[0] = 1;
desc.BorderColor[1] = 0;
desc.BorderColor[2] = 0;
desc.BorderColor[3] = 1;
desc.ComparisonFunc = D3D11_COMPARISON_ALWAYS;
desc.MaxAnisotropy = 16;
desc.MaxLOD = FLT_MAX;
desc.MinLOD = FLT_MAX;
desc.MipLODBias = 0.0f;
_device->CreateSamplerState(&desc, _samplerBuffer.GetAddressOf());
}
만약 UV를 엄청 크게 1에서 5로 바꿨을때 보면 다음과 같다.
나머지 부분들이 빨간색이 나오는걸 볼 수 있는데 우리가 초반 규칙을 Border로 선택했기 때문에 그런 것이다. 영역을 벗어나면 뭐로 채울지 고민하는 경우 샘플러를 사용할 수 있다.
BlendState는 블랜딩 효과를 낼 수 있다. 어떻게 블랜드를 할지 옵션들을 선택할 수 있다.
void Game::CreateBlendState()
{
D3D11_BLEND_DESC desc;
ZeroMemory(&desc, sizeof(D3D11_BLEND_DESC));
desc.AlphaToCoverageEnable = false;
desc.IndependentBlendEnable = false;
desc.RenderTarget[0].BlendEnable = true;
desc.RenderTarget[0].SrcBlend = D3D11_BLEND_SRC_ALPHA;
desc.RenderTarget[0].DestBlend = D3D11_BLEND_INV_SRC_ALPHA;
desc.RenderTarget[0].BlendOp = D3D11_BLEND_OP_ADD;
desc.RenderTarget[0].SrcBlendAlpha = D3D11_BLEND_ONE;
desc.RenderTarget[0].DestBlendAlpha = D3D11_BLEND_ZERO;
desc.RenderTarget[0].BlendOpAlpha = D3D11_BLEND_OP_ADD;
desc.RenderTarget[0].RenderTargetWriteMask = D3D11_COLOR_WRITE_ENABLE_ALL;
HRESULT hr = _device->CreateBlendState(&desc, _blendState.GetAddressOf());
CHECK(hr);
}
'C++ > DirectX 11' 카테고리의 다른 글
[DirectX] 프레임워크 제작기 - Graphics, IA, Geomotry (1) | 2024.09.02 |
---|---|
[DirectX] 행렬 - SRT 변환 행렬과 좌표계 변환 행렬 및 예제 (0) | 2024.08.31 |
[DirectX] DirectX 11 입문하기 - 텍스쳐와 UV (2) | 2024.08.28 |
[DirectX] DirectX 11 입문하기 - 장치 초기화와 삼각형 띄우기 (0) | 2024.08.28 |
[DirectX] DirectX 11 입문하기 - 그래픽스 OT, 기본 프레임 워크 (0) | 2024.08.27 |