DX11) ConstantBuffer
ConstantBuffer
HLSL
이나 GLSL
, Vulkan
같은 그래픽 API에서 셰이더에 전달되는 데이터를 담는 버퍼이다.
ConstantBuffer의 역할
중복 데이터 관리
- 그래픽 렌더링에서 여러 Mesh나 Material, FX를 그릴 때 계속 사용되는 공통 데이터(조명 정보, 카메라 변환 행렬, 전역 변수 등)를 한 곳에 모아두고 셰이더에 전달한다.
- 뷰 행렬(View Matrix), 투영 행렬(Projection Matrix), 조명 관련 매개변수, 프레임 단위로만 달라지는 전역 변수 등이 이에 해당한다.
데이터 동기화/성능 최적화
- CPU에서 셰이더로 전달해야 하는 작은 데이터들을 개별적으로 전달하면 오버헤드가 발생한다.
- Constant Buffer를 사용해 한꺼번에 묶어서 전송하면, API 호출 횟수와 메모리 접근 비용을 줄일 수 있다.
- 프레임마다 혹은 이벤트가 발생할 때마다 변동 사항을 빠르고 효율적으로 반영할 수 있다.
셰이더 접근 방식 표준화
- HLSL(DirectX)이나 GLSL(OpenGL) 등에서 상수 버퍼는 표준 인터페이스(Uniform Block, cbuffer 등)로 정의 가능.
- 셰이더 내부에서는 “Uniform/Constant”으로 인식되어, Vertex Shader, Pixel Shader 등 여러 파이프라인 스테이지에서 동일하게 참조할 수 있다.
ConstantBuffer의 필요성
공통 데이터 전달
- 3D(또는 2D) 렌더링에서는 여러 정점에 대해 반복적으로 사용되는 정보가 많다.
- 예: 월드 변환 행렬(World Matrix), 뷰/투영 행렬(View/Projection), 조명 정보, 화면 해상도, 시간 값 등.
- 이런 전역 데이터를 각 정점마다 별도로 전달하거나 텍스처 리소스로 사용하는 것은 비효율적이다.
- 전용 버퍼(상수 버퍼)에 담아 한 번에 넘길 필요가 있다.
빈번한 업데이트 & 효율
- 게임과 그래픽 애플리케이션에서는 매 프레임 물체 위치, 애니메이션 시간, 조명 세기 등이 바뀔 수 있다.
- 이런 자주 변하는(하지만 정점 수만큼 방대하지는 않은) 데이터를 CPU→GPU로 일괄 전달해야 하는데, 개별 변수를 일일이 전송하면 오버헤드가 커진다.
- 상수 버퍼는 이러한 자잘한 데이터를 한 덩어리로 묶어서 GPU에 전송하는 구조로 만들어져 있어, API 호출 횟수와 메모리 접근 비용을 낮출 수 있습니다.
파이프라인 표준화
- DirectX(특히 HLSL)는 상수 버퍼를 b0, b1, b2 같은 슬롯으로 관리하여, Vertex Shader, Pixel Shader 등 여러 셰이더 스테이지에서 공통 데이터에 쉽게 접근 가능하다.
- 이를 통해 여러 스테이지 간에 공유되는 전역 변수를 구조적으로 관리할 수 있다.
HLSL에서의 사용법
cbuffer
cbuffer FrameData : register(b0)
{
float4x4 gWorldViewProj; // 행렬
float4 gAmbientColor; // 앰비언트 조명 색
float3 gLightDir; // 광원 방향
float gTime; // 현재 시간 (예: 애니메이션 등)
};
-
cbuffer
키워드 뒤에 이름(FrameData
)을 지정 : register(b0)
는 이 상수 버퍼가 GPU 바인딩 슬롯의 b0 레지스터에 할당됨을 의미- DirectX에서는 Vertex/Pixel 등 여러 셰이더 스테이지에서 각기 최대 14~15개(DX11 기준)의 cbuffer 슬롯(b0, b1, b2…)을 사용할 수 있음
- 내부에는 HLSL 타입(
float4x4
,float3
,float4
등)들을 선언
정렬(Alignment) 규칙
- HLSL의 cbuffer는 내부적으로 16바이트(128비트) 경계 기준으로 정렬된다. (패킹 규칙)
- ex)
float4
하나로 16바이트를 차지 float3
는 12바이트지만, 실제 cbuffer 내에서는 16바이트 공간을 차지하고, 다음 멤버는 새 16바이트 슬롯에서 시작
- ex)
- 일반적으로 구조체로 묶을 때 16바이트를 기준으로 생각해야 한다.
C++에서의 사용법
ConstantBuffer생성
D3D11_BUFFER_DESC ConstantBufferDesc = {};
ConstantBufferDesc.Usage = D3D11_USAGE_DEFAULT; // 또는 D3D11_USAGE_DYNAMIC
ConstantBufferDesc.ByteWidth = sizeof(Transform); // 16바이트 배수 권장
ConstantBufferDesc.BindFlags = D3D11_BIND_CONSTANT_BUFFER; // 만들어질때 용도(Constant) 지정
ConstantBufferDesc.CPUAccessFlags = 0; // (Dynamic일 경우 D3D11_CPU_ACCESS_WRITE)
if (FAILED(_DEVICE->CreateBuffer(&ConstantBufferDesc, nullptr, ConstantBuffer.GetAddressOf())))
{
return E_FAIL;
}
용도 지정
BindFlags = D3D11_BIND_CONSTANT_BUFFER
: 이 버퍼가 상수 버퍼로 사용됨을 Direct3D에 선언한다.
크기(ByteWidth) 설정
sizeof(Transform)
: 버퍼에 넣을 구조체(Transform)의 크기- 주의: 일반적으로
16바이트(16-byte) 배수
로 맞추는 것이 좋다. (HLSL cbuffer 정렬 이슈를 피하기 위해)
CPUAccessFlags & Usage
D3D11_USAGE_DYNAMIC
+D3D11_CPU_ACCESS_WRITE
로 지정- 이렇게 하면 매 프레임마다(또는 필요할 때마다) CPU가 Map/Unmap을 통해 버퍼 내용을 쉽게 업데이트할 수 있다.
구조체(Transform) 예시
struct Transform
{
Vector3 Position; // (x, y, z)
float pad; // 패딩:16바이트 정렬 맞추기용(필요시)
// 회전, 스케일 등이 있다면 추가
};
HLSL 상수 버퍼(cbuffer)는 16바이트 단위로 패킹(packing)되므로, float3
뒤에는 패딩(float
) 등을 넣어 16바이트 경계를 맞춰주는 것이 좋다.
댓글남기기