지금까지 렌더링한 사각형을 크기 및 위치를 변화시켜보자. 정점 버퍼의 내용을 수정해도 할 수 있지만, 매번 정점 버퍼의 내용을 수정하면서 렌더링하기엔 꽤나 비효율적이다. 무엇보다 강력한 GPU의 힘을 이용하지 못한다. 정점 버퍼의 내용은 모양을 정의하는 것이다. 이 모양을 Matrix를 통해 변환을 해야한다. 그리고 GPU를 이용하기 위해 해당 변환은 Vertex Shader에서 할 것이다. 이를 위해 필요한 것이 바로 Constant Buffer
이다. 이 Constant Buffer
에 대해 알아보자.
Screen 좌표

렌더링되고 있는 사각형의 4개의 정점의 값은 위 이미지와 같다. 즉 DirectX11기준으로 스크린 좌표는 중앙이 0,0
이고 X, Y축 모두 -1
부터 1
의 범위값을 가지고 있는 것을 알 수 있다. 따라서 정점 버퍼의 내용만 수정해도 다음과 같이 렌더링 되는 것을 알 수 있다.

이런식으로 변환을 할 수 있지만 서두에 말했다시피 이건 매우 비효율적이기 때문에 Constant Buffer
를 통해 쉐이더에서의 변환을 통해 위와 같이 스케일링을 해 볼 것이다.
Constant Buffer의 만들기
Constant Buffer
의 만들기도 정점 버퍼나 인덱스 버퍼와 별반 다르지 않다.
ID3D11Buffer* ConstantBuffer;
D3D11_BUFFER_DESC bd;
ZeroMemory( &bd, sizeof( D3D11_BUFFER_DESC ) );
bd.Usage = D3D11_USAGE_DYNAMIC;
bd.ByteWidth = sizeof( Matrix );
bd.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
bd.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
ID3D11Device->CreateBuffer( &bd, nullptr, &ConstantBuffer );
역시나 가장 다른건 BindFlags
를 D3D11_BIND_CONSTANT_BUFFER
으로 지정한 것이다. 이 버퍼는 Constant Buffer
로 사용하겠다는 의미이다. 그리고 기존에 버퍼들을 생성할때와 설정부분들이 다른게 몇몇 있다. Usage
를 D3D11_USAGE_DYNAMIC
으로 지정한 것은 GPU는 읽기만 하고, CPU는 쓰기만 하겠다는 것이다. CPU 쓰기를 위해 CPUAccessFlags
를 D3D11_CPU_ACCESS_WRITE
으로 추가로 지정해주었다. 또한 이 버퍼는 Matrix
타입으로 사용하기 때문에 ByteWidth
는 Matrix
의 크기로 지정하였다.
지금 사용하는 Constant Buffer
의 용도는 정점들을 변환하기 위함이므로 GPU에서는 읽기만 하면 된다. 또한 Draw
하기에 앞서 프로그램에서 한번씩만 값을 갱신해주면 된다. 따라서 사용 용도를 D3D11_USAGE_DYNAMIC
으로 지정한 것이다.
Rendering Pipeline에 연결하기
생성한 Constant Buffer
역시 렌더링 파이프라인에 연결을 해주어야 사용될 수 있다.
ID3D11DeviceContext->VSSetConstantBuffers( 0, 1, &ConstantBuffer );
위 코드는 Vertex Shader
스테이지 0
번 슬롯에 이 버퍼를 연결하겠다는 뜻이다. DirectX11
에서 Constant Buffer
는 렌더링 파이프라인의 6개 스테이지에 연결이 가능하다.
연결 함수 이름 | 연결 스테이지 |
---|---|
VSSetConstantBuffers | Vertex Shader Stage |
GSSetConstantBuffers | Geometry Shader Stage |
DSSetConstantBuffers | Domain Shader Stage |
HSSetConstantBuffers | Hull Shader Stage |
CSSetConstantBuffers | Compute Shader Stage |
PSSetConstantBuffers | Pixel Shader Stage |
위와 같은 Stage들에서 연결 및 사용이 가능한데 곧 모든 쉐이더 스테이지에서 사용이 가능한 것이다. 그리고 각 스테이지마다 D3D11_COMMONSHADER_CONSTANT_BUFFER_API_SLOT_COUNT
개만큼 연결할 수 있는데 현재 기준으로 14개이다. 다시 정리하면 Constant Buffer
는 모든 쉐이더 스테이지에서 14개까지 연결해서 사용이 가능하다. 일단 나는 정점 쉐이더에서 정점의 변환 용도로 버퍼를 사용하므로 VSSetConstantBuffers
함수를 통해 연결하였다. 언제나처럼 레퍼런스는 마이크로 소프트에서 참고 가능하다.
Buffer의 내용 갱신하기
일단 버퍼의 내용은 버퍼를 만들면서 지정해줄 수도 있다.
ID3D11Buffer* ConstantBuffer;
D3D11_BUFFER_DESC bd;
ZeroMemory( &bd, sizeof( D3D11_BUFFER_DESC ) );
bd.Usage = D3D11_USAGE_DYNAMIC;
bd.ByteWidth = sizeof( Matrix );
bd.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
bd.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
D3D11_SUBRESOURCE_DATA sd;
ZeroMemory( &sd, sizeof( D3D11_SUBRESOURCE_DATA ) );
Matrix matrix = DirectX::XMMatrixTranspose( Matrix::CreateScale( 0.5f, 0.5f, 1.f ) );
sd.pSysMem = &matrix;
ID3D11Device->CreateBuffer( &bd, &sd, &ConstantBuffer );
이렇게 하면 Identity Matrix
로 버퍼의 내용이 갱신된다. 하지만 버퍼의 내용이 변경될 때마다 새롭게 버퍼를 재할당할 수는 없는 노릇이다. 이미 생성된 버퍼의 내용을 프로그램에서 수정할 수 있도록 옵션들을 지정해 생성해 두었다. 생성된 버퍼의 내용을 갱신하는 코드는 다음과 같다.
Matrix matrix = DirectX::XMMatrixTranspose( Matrix::CreateScale( 0.5f, 0.5f, 1.f ) );
D3D11_MAPPED_SUBRESOURCE mappedResource;
ID3D11DeviceContext->Map( ConstantBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource );
{
memcpy_s( mappedResource.pData, sizeof( Matrix ), &matrix, sizeof( Matrix ) );
}
ID3D11DeviceContext->Unmap( ConstantBuffer, 0 );
ConstantBuffer
를 우선 Map
함수로 D3D11_MAPPED_SUBRESOURCE
에 맵핑하여 잡아둔 후 버퍼의 내용을 변경하고 Unmap
으로 해제해준다. Map
과 Unmap
사이 ConstantBuffer
에 GPU는 접근할 수 없다. 자세한 내용은 레퍼런스 Map과 Unmap을 참고하도록 한다. 여기까지 하면 프로그램 단계에서 버퍼를 사용할 준비를 모두 마쳤다. 아직 쉐이더에서 반영되지 않았기 때문에 이 상태에서 렌더링을 해도 기존과 변화가 없다.
DirectX::XMMatrixTranspose
Constant Buffer의 데이터형인 Matrix
는 DirectXTK에서 정의된 데이터형이다. 문제는 이 데이터형은 쉐이더에서 사용되는 matrix
와 행
과 열
의 방향이 다르다. 그래서 이 방향을 바꿔주지 않으면 기대한대로 변환이 일어나지 않는다. 쉐이더에서 Transpose
를 해줘도 되지만 굳이 정점마다 해줄 필요는 없기 때문에 CPU에서 미리 한번 Transpose
를 하여 방향을 쉐이더에서 사용하는 것과 일치 시켜 버퍼에 복사되도록 하는 것이다.
Shader 수정
ConstantBuffer
의 용도는 정점 쉐이더에서 정점 변환에 사용하기 위한 것이기 때문에 정점 쉐이더만 변경하면 된다.
matrix transform : register( c0 );
PixelIn VS( float4 position : POSITION, float2 texCoord : TEXCOORD )
{
PixelIn output;
output.position = mul( position, transform );
output.texCoord = texCoord;
return output;
}
일단 0
번 슬롯에 버퍼를 연결했기 때문에 matrix transform : register( c0 );
와 같이 레지스터를 이용하여 transform
matrix는 0
번 슬롯의 Constant Buffer
의 내용을 사용한다고 명시하였다. 그리고 정점 쉐이더에서 정점의 위치값에 해당 matrix
의 곱을 통해 변환을 해주었다. 이후 렌더링을 하면 다음과 같은 결과를 얻을 수 있다.

register( c0 )
matrix transform : register( c0 );
이 hlsl 코드는 DirectX9
버전에서 사용하는 hlsl
코드이다. DirectX9
에서는 상수 레지스터를 통해 쉐이더에 상수값들을 전달했다. DirectX11
에서는 상수 버퍼를 통해 전달하며, 올바른 코드는 다음과 같다. matrix transform : register( c0 );
도 일단 동작은 했지만 호환성 측면에서 예외처리된 것으로 보이며 모든 환경에서 동일하게 동작하는 것을 기대하기 어렵다. 따라서 쉐이더쪽 코드는 다음 코드로 교체하도록 한다.
cbuffer TransformBuffer : register( b0 )
{
matrix transform;
}
여러번 Draw하기
여기에 Constant Buffer
의 내용만 갱신하면서 Draw
를 여러번 호출할 수 있다.
Matrix matrix = DirectX::XMMatrixTranspose( Matrix::CreateScale( 0.5f, 0.5f, 1.f ) * Matrix::CreateTranslation( -0.5f, 0.5f, 0.0f ) );
D3D11_MAPPED_SUBRESOURCE mappedResource;
ID3D11DeviceContext->Map( ConstantBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource );
{
memcpy_s( mappedResource.pData, sizeof( Matrix ), &matrix, sizeof( Matrix ) );
}
ID3D11DeviceContext->Unmap( ConstantBuffer, 0 );
ID3D11DeviceContext->DrawIndexed( 6, 0, 0 );
matrix = DirectX::XMMatrixTranspose( Matrix::CreateScale( 0.5f, 0.5f, 1.f ) * Matrix::CreateTranslation( 0.5f, -0.5f, 0.0f ) );
D3D11_MAPPED_SUBRESOURCE mappedResource;
ID3D11DeviceContext->Map( ConstantBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource );
{
memcpy_s( mappedResource.pData, sizeof( Matrix ), &matrix, sizeof( Matrix ) );
}
ID3D11DeviceContext->Unmap( ConstantBuffer, 0 );
ID3D11DeviceContext->DrawIndexed( 6, 0, 0 );
IDXGISwapChain->Present( 0, 0 );
이렇게 Present
하기전 2번 Draw
를 호출하고, Draw
를 호출하기전 ConstantBuffer
의 내용을 갱신하여 정점 변환 내용을 바꿔주면 아래와 같이 Draw
가 누적된 렌더링 결과를 얻을 수 있다.

여기까지의 코드는 yunei0313/CRY at Transform-used-constant-buffer에서 볼 수 있다.
이전글 : [DirectX11] 11. 이미지 – Texture2D에 대해 알아보자.
다음글 : [DirectX11] 13. 라이트( Directional Light ) – Constant Buffer 활용하기.