목차
- [[#6.1 정점과 입력 배치|6.1 정점과 입력 배치]]
- [[#6.2 정점 버퍼|6.2 정점 버퍼]]
- [[#6.2.2 기본 버퍼 생성 편의용 함수 작성|6.2.2 기본 버퍼 생성 편의용 함수 작성]]
- [[#6.3 색인과 색인 버퍼|6.3 색인과 색인 버퍼]]
- [[#6.4 예제 정점 셰이더|6.4 예제 정점 셰이더]]
- [[#6.4 예제 정점 셰이더#6.4.1 입력 배치 서술과 입력 서명 연결|6.4.1 입력 배치 서술과 입력 서명 연결]]
- [[#6.5 예제 픽셀 셰이더|6.5 예제 픽셀 셰이더]]
- [[#번외|번외]]
6.1 정점과 입력 배치
- 원하는 자료(특성)를 가진 커스텀 정점 형식을 만들려면 그러한 자료를 담을 구조체 정의
- 입력 배치 서술(input layout descrpitino) :
- 정점의 각 성분으로 무엇을 해야 하는지를 D3D에 알려주어야 함
D3D12_INPUT_LAYOUT_DESC
구조체로 대표 typedef struct D3D12_INPUT_LAYOUT_DESC
{
const D3D12_INPUT_ELEMENT_DESC *pInputElementDesccs;
UINT NumElements;
} D3D12_INPUT_LAYOUT_DESC;
D3D12_INPUT_ELEMENT_DESC
형식 원소들을 담은 배열과 개수를 나타내는 구조체
- 각 원소는 정점 구조체의 각 성분을 서술
- 배열의 원소들과 정점 구조체의 성분들 은 일대일로 대응
D3D12_INPUT_ELEMENT_DESC
정의 typedef struct D3D12_INPUT_ELEMENT_DESC
{
LPCSTR SemanticName;
UINT SematicIndex;
DXGI_FORMAT Format;
UINT InputSlot;
UINT AlignedByteOffset;
D3D12_INPUT_CLASSIFICATION InputSlotClass;
UINT InstanceDataStepRate;
} D3D12_INPUT_ELEMENT_DESC;
SemanticName
- 성분에 부여된 문자열 이름
- 정점 셰이더에서 의미소(semantic) 이름으로 쓰이므로, 반드시 유효한 변수이름이어야 함
- 정점 구조체의 성분을 정점 셰이더 입력 서명과 대응시키는 역할
SematicIndex
- 의미소 색인
- 하나의 정점 구조체에 텍스처 좌표가 여러 개 있을 수 있는데, 각 텍스처 좌표에 개별적인 의미소 이름을 부여하는 대신 색인을 통해 구별
- 셰이더 코드에서 색인이 지정되지 않은 의미소는
0
인 의미소로 간주
Format
DXGI_FORMAT
열거형의 한 멤버
- 4.1.3 텍스쳐 형식 참고
InputSlot
- 성분의 자료를 가져올 정점 버퍼 슬롯 색인
- 총 16개의 정점 버퍼 슬롯(0~15)를 통해서 정점 자료 공급 가능
AlignedByteOffset
- 지정 입력 슬롯에서 정점 구조체의 시작 위치와 정점 성분의 시작 위치 사이의 거리를 나타내는 오프셋(바이트 단위)
- Pos 성분 바이트 시작 ~ 12byte 이므로, 다음 성분인 Normal은 12byte가 시작위치이므로 offset 값 설정
InputSlotClass
- 입력 슬롯의 데이터 클래스를 정의
- 셰이더가 처리하는 데이터가 정점 데이터인지 아니면 인스턴스 데이터인지 구분하는 데 사용
D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA
:
- 슬롯이 정점 데이터를 포함
- 각 정점에 대해 데이터가 셰이더로 전달
- Instance 수 = 0
D3D12_INPUT_CLASSIFICATION_PER_INSTANCE_DATA
:
- 슬롯이 인스턴스 데이터를 포함
- 각 인스턴스에 대해 데이터를 전달할 때 사용
- 여러 오브젝트를 인스턴스 렌더링할 때, 각각의 인스턴스에 고유한 데이터가 필요
InstanceDataStepRate
- 인스턴스 데이터의 전달 주기를 제어
- 셰이더가 데이터를 몇 개의 인스턴스마다 업데이트해야 하는지를 결정
- 1 : 모든 인스턴스에 대해 새로운 데이터를 제공
- N : 값이 2라면 두 개의 인스턴스가 동일한 데이터를 공유
- 인스턴스 데이터 사용 시만 적용(PER_INSTANCE_DATA)

6.2 정점 버퍼
- 정점버퍼 : 정점들을 저장하는 버퍼
- GPU가 정점 배열에 접근하려면, 정점들을 버퍼라고 부르는 GPU자원 (ID3D12Resource)에 넣어 두어야 함
- 버퍼는 텍스처보다 단순 자원
- 정점 같은 자료 원소들의 배열을 GPU에 제공할 떄, 항상 버퍼 사용
- 버퍼 자원을 채우고,
ID3D12Device::CreateCommittedResource
메서드를 호출해서
ID3D12Resource
객체 생성
- 버퍼 자원 서술 :
D3D12_RESOURCE_DESC
- D12에서 제공하는 해당 구조체를 상속해서 쓰는 편의용 생성자들과 메서드들을 추가한 C++ 래퍼 클래스
CD3DX12_RESOURCE_DESC
제공 static inline CD3DX12_RESOURCE_DESC Buffer(
UINT64 width,
D3D12_RESOURCE_FLAGS flags = D3D12_RESOURCE_FALG_NONE,
UINT64 alignment = 0 )
{
return CD3DX12_RESOURCE_DESC(
D3D12_RESOURCE_DIMENSION_BUFFER,
aligment, width, 1, 1, 1,
DXGI_FORMAT_UNKNOWN, 1, 0,
D3D12_TEXTURE_LAYOUT_ROW_MAJOR, flags );
}
width
:
- 버퍼의 바이트 개수를 나타냄
- flaot 64개 담는 버퍼 =>
64*sizeof(flaot)
D3D12_RESOURCE_DESC::D3D12_RESOURCE_DIMENSION
필드에 의해서 자원의 구체적인 종류 지정
CD3DX12_RESOURCE_DESC bufferDesc = CD3DX12_RESOURCE_DESC::Buffer(1024); // 1024바이트 버퍼 생성
- 정적 기하구조(프레임마다 변하지는 않는 기하구조)를 그릴 떄, 최적 성능을 위해 정점 버퍼들을 기본 힙(D3D12_HEAP_TYPE_DEFAULT)에 넣음
- 정점 버퍼를 초기화한 후에는 GPU만 버퍼의 정점들을 읽으므로(기하구조 그리기위해), 기본 힙에 넣는 것이 합당
- CPU는 기본 힙에 있는 정점 버퍼 수정 불가
- 그렇기에 업로드 힙에서 값을 복사해서 와야함
D3D12__SUBRESOURCE_DATA
사용
- 요즘에는 텍스쳐 같은 큰 데이터 외에는 사용x
- uploadHeap에서 직접
Map
한 후, defaultHeap에 memcopy
CopyBufferRegion()
으로 GPU에서 복사
6.2.2 기본 버퍼 생성 편의용 함수 작성
기본 버퍼 생성
Microsoft::WRL::ComPtr<ID3D12Resource> d3dUtil::CreateDefaultBuffer(
ID3D12Device* device,
ID3D12GraphicsCommandList* cmdList,
const void* initData,
UINT64 byteSize,
Microsoft::WRL::ComPtr<ID3D12Resource>& uploadBuffer)
{
ComPtr<ID3D12Resource> defaultBuffer;
// 실제 기본 버퍼 자원 생성
ThrowIfFailed(device->CreateCommittedResource(
&CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_DEFAULT),
D3D12_HEAP_FLAG_NONE,
&CD3DX12_RESOURCE_DESC::Buffer(byteSize),
D3D12_RESOURCE_STATE_COMMON,
nullptr,
IID_PPV_ARGS(defaultBuffer.GetAddressOf())));
// CPU 메모리를 기본 버퍼에 복사하기 위해, 임시 업로드 힙 생성
ThrowIfFailed(device->CreateCommittedResource(
&CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_UPLOAD),
D3D12_HEAP_FLAG_NONE,
&CD3DX12_RESOURCE_DESC::Buffer(byteSize),
D3D12_RESOURCE_STATE_GENERIC_READ,
nullptr,
IID_PPV_ARGS(uploadBuffer.GetAddressOf())));
// 기본 버퍼에 복사할 자료 서술
D3D12_SUBRESOURCE_DATA subResourceData = {};
subResourceData.pData = initData;
subResourceData.RowPitch = byteSize;
subResourceData.SlicePitch = subResourceData.RowPitch;
// 기본 버퍼 자원으로의 자료 복사 요청
// 보조 함수 UpdateSubresources는 CPU 메모리를 임시 업로드 힙에 복사,
// ID3D12CommandList::CopySubresourceRegion을 이용해서 임시 업로드 힘의 자료를
// mBuffer에 복사
cmdList->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(defaultBuffer.Get(),
D3D12_RESOURCE_STATE_COMMON, D3D12_RESOURCE_STATE_COPY_DEST));
UpdateSubresources<1>(cmdList, defaultBuffer.Get(), uploadBuffer.Get(), 0, 0, 1, &subResourceData);
cmdList->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(defaultBuffer.Get(),
D3D12_RESOURCE_STATE_COPY_DEST, D3D12_RESOURCE_STATE_GENERIC_READ));
// 주의 : 위의 함수 호출 이후에도 uploadBuffer를 계속 유지
// 실제로 복사를 수행하는 명령 목록이 아직 실행 X
// 복사 완료되었음이 확실해진 후에 호출자가 UploadBuffer 해제
return defaultBuffer;
}
```
D3D12_SUBRESOURCE_DATA
구조체
typedef struct D3D12_SUBRESOURCE_DATA
{
const void *pData;
LONG_PTR RowPitch;
LONG_PTR SlicePitch;
} D3D12_SUBRESOURCE_DATA;
pData
:
- 버퍼 초기화용 자료를 담은 시스템 메모리 배열 가리키는 포인터
- 버퍼에 n 개의 정점을 담을 수 있을 떄, 전체를 초기화 하려면 시스템 메모리 배열에 최소 n개의 정점 필요
RowPitch
:
- 버퍼의 경우, 복사할 자료이 크기(바이트 개수)
SlicePitch
:
- 버퍼의 경우, 복사할 자료이 크기(바이트 개수)
정점 버퍼 기본 버퍼 예시
Vertex vertices[] = {
{ XMFLOAT3(-1.0f, -1.0f, -1.0f), XMFLOAT4(COLORS::White)},
...
};
const UINT vbByteSize = 8 * sizeof(Vertex); // 8개 정점
ComPtr<ID3D12Resource> VertexBufferGPU = nullptr;
ComPtr<ID3D12Resource> VertexBufferUploader = nullptr;
VertexBufferGPU = d3dUtil::CreateDefaultBuffer(md3dDevice.Get(),
mCommandList.Get(), vertices, vbByteSize, VertexBufferUploader);
// 사용자 정의 정점 버퍼 생성 메서드
D3D12_VERTEX_BUFFER_VIEW vbv;
vbv.BufferLocation = VertexBufferGPU->GetGPUVirtualAddress();
vbv.StrideInBytes = sizeof(Vertex);
vbv.SizeInByte = vbByteSize;
D3D12_VERTEX_BUFFER_VIEW vertexBuffers[1] = { vbv };
mCommandList->IASetVertexBuffers(0, 1, vertexBuffers);
- RTV와 달리 정점 버퍼 뷰에는 서술자 힙이 필요x
정점 버퍼 뷰 대표 형식 D3D12_VERTEX_BUFFER_VIEW_DESC
구조체
typedef struct D3D12_VERTEX_BUFFER_VIEW
{
D3D12_GPU_VIRTUAL_ADDRESS BufferLocation;
UINT SizeInBytes;
UINT StrideInBytes;
} D3D12_VERTEX_BUFFER_VIEW;
BufferLocation
:
- 생성할 뷰의 대상이 되는 정점 버퍼 자원의 가상 주소
ID3D12Resource::GetGPUVirtualAddress
메서드로 얻을 수 있음
SizeInBytes
:
- BufferLocation에서 시작하는 정점 버퍼의 크기(바이트 개수)
StrideInBytes
:
- 버퍼에 담긴 한 정점의 원소의 크기 (바이트 개수)
정점 버퍼를 IA 파이프라인에 묶는 메서드
void ID3D12GraphicsCommandList::IASetVertexBuffers(
UINT StartSlot,
UINT NumBuffers,
const D3D12_VERTEX_BUFFER_VIEW *pViews );
StartSlot
:
- 시작슬롯, 첫쨰 정점 버퍼를 묶을 입력 슬롯의 색인
- 총 16개 (0~15)
NumBuffers
:
pViews
:
- 정점 버퍼 뷰 배열의 첫 원소를 가리키는 포인터
정점들을 이용해 기본도형을 그리려면 ID3D12GraphicsCommandList::DrawInstanced
메서드 사용
void ID3D12GraphicsCommandList::DrawInstanced(
UINT VertexCountPerInstance,
UINT InstanceCount,
UINT StartVertexLocation,
UINT StartInstanceLocation);
VertexCountPerInstance
: 그리기에 사용할 정점들의 개수 (인스턴스 당)
InstanceCount
: 그릴 인스턴스 개수
StartVertexLocation
:
- 정점 버퍼의 시작 위치를 지정;
- 정점 버퍼의 색인들 중 이 그리기 호출에서 사용할 첫 색인( 0 기반)
StartInstanceLocation
:
- 첫 번째 인스턴스의 시작 위치 지정
- 특정 인스턴스부터 렌더링을 시작 가능
위상구조 상태 결정은 5.5.2 참고
cmdList->IASetPrivitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST)
6.3 색인과 색인 버퍼
색인 버퍼
- GPU가 색인들의 배열에 접근 할 수 있을려면, 색인들을 GPU 자원에 넣어 두어야 함
- 서술자 힙을 필요로 하지 않음
색인 버퍼 뷰 대표 형식
ID3D12CommandList::IASetIndexBuffer
메서드를 통해서 입력 조립기 단계에 묶음
사각형 색인 버퍼 IA바인드 예시
std::uint16_t indices[] = {
// 앞면
0, 1, 2,
0, 2, 3,
// 뒷면
...
};
const UINT ibByteSize = 36 * sizeof(std::uint_16_t); // 36개 정점
ComPtr<ID3D12Resource> IndexBufferGPU = nullptr;
ComPtr<ID3D12Resource> IndexBufferUploader = nullptr;
IndexBufferGPU = d3dUtil::CreateDefaultBuffer(md3dDevice.Get(),
mCommandList.Get(), indices, ibByteSize, IndexBufferUploader);
// 사용자 정의 정점 버퍼 생성 메서드
D3D12_INDEX_BUFFER_VIEW ibv;
ibv.BufferLocation = IndexBufferGPU->GetGPUVirtualAddress();
ibv.Foramt = DXGI_FORMAT_R16_UINT;
ibv.SizeInBytes = ibByteSize;
mCommandList->IASetIndexBuffer(&ibv);
색인들을 이용해 기본도형을 그리려면 ID3D12GraphicsCommandList::DrawIndexedInstanced
메서드 사용
void ID3D12GraphicsCommandList::DrawIndexedInstanced(
UINT IndexCountPerInstance,
UINT InstanceCount,
UINT StartIndexLocation,
INT BaseVertexLocation,
UINT StartInstanceLocation);
IndexCountPerInstance
: 그리기에 사용할 색인들의 개수 (인스턴스 당)
InstanceCount
: 그릴 인스턴스 개수
StartIndexLocation
:
- 인덱스 버퍼의 시작 위치를 지정
- 색인 버퍼의 색인들 중 이 그리기 호출에서 사용할 첫 색인( 0 기반)
BaseVertexLocation
:
- 정점 버퍼에서의 기준 오프셋 지정
- 호출에 쓰이는 색인들에 더할 정수 값, 더한 결과를 최종 색인으로사용해서 정점 버퍼에서 정점 가져옴, 음수 값 허용
StartInstanceLocation
:
- 첫 번째 인스턴스의 시작 위치 지정
- 특정 인스턴스부터 렌더링을 시작 가능

6.4 예제 정점 셰이더
- 셰이더는 HLSL(High Level Shading Language)로 작성
- 정점 셰이더는 하나의 함수
- 매개 변수 4 개 중 앞의 2개는 입력 매개변수, 뒤의 2개는 출력 매개변수
- 입력 매개변수는 입력 서명(input signature) 을 형성
- 현재의 그리기 작업에 쓰이는 커스텀 정점 구조체의 멤버들에 대응
- 출력 매개변수
- 정점 셰이더의 출력을 파이프라인 다음 단계(기하셰이더, 픽셀셰이더)의 해당 입력에 대응 시키는 역할
SV_POSITION
- SV는 SystemValue 라는 의미를 담고 있음
- GPU는
SV_POSITION
으로 명시된 데이터를 하드웨어 수준에서 특별히 처리
- 렌더링 엔진과 DirectX가 정점의 위치 데이터를 명확히 처리할 수 있도록 보장
COLOR
D3D12_INPUT_ELEMENT_DESC
배열을 통해 지정한 이름
- HLSL의 유효 식별자이기만 하면, 아무 이름이나 상관 x
- HLSL에는 참조나 포인터가 없어, 함수가 여러개의 값을 돌려주려면 구조체 사용 or
out
이 지정된 출력 매개변수 사용
- HLSL에서 함수는 항상 인라인화
cbuffer cbPerObject : register(b0)
{
float4x4 gWorldViewProj;
}
void VS(
float iPosL : POSITION,
float iColor : COLOR,
out float4 oPOSH : SV_POSTIION, // SV는 SystemValue 라는 의미를 담고 있음
out float4 oColor : COLOR)
{
// 동차 절단 공간 변환
oPosH = mul(float4(iPosL, 1.0f), gWorldViewProj);
// 정점 색상을 그대로 픽셀 셰이더에 전달
oColor = iColor;
}
mul
함수 :
gWorldViewProj
:
- 기하 셰이더 사용 시, 동차 절단 공간 위치의 출력을 기하 셰이더에 미룰 수 있음
- 원근 나누기는 나중에 하드웨어가 수행하기에, 투영 핼렬 곱하는 부분만 책임
6.4.1 입력 배치 서술과 입력 서명 연결
- 입력 배치 서술
- 파이프라인에 공급되는 정점들의 특성들과 정점 셰이더의 매개변수들 사이의 연관 관계 정의
- 정점 셰이더가 기대하는 모든 입력을 제공하지못하면 오류 발생
- 추가 공급은 되지만, 적은 건 용납 x
- D3D에서 입력 레지스터 비트들의 재해석을 허용하기에 float -> int 의 공급을 허용하지만 VC++의 디버그 출력 창은 경고 메시지
//--------------
// C++ app code
//--------------
struct Vertex
{
XMFLOAT3 Pos;
XMFLOAT4 Color;
XMFLOAT3 Normal;
};
D3D12_INPUT_ELEMENT_DESC desc[] =
{
{"POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0};
D3D12_INPUT_PER_VERTEX_DATA, 0 },
{ "COLOR", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 12,
D3D12_INPUT_PER_VERTEX_DATA, 0 },
{ "NORMAL", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 28,
D3D12_INPUT_PER_VERTEX_DATA, 0 }
//--------------
// Vertex shader
//--------------
struct VertexIn
{
float3 PosL : POSITION;
float4 Color : COLOR;
};
struct VertexOut
{
float4 PosH : SV_POSITION;
float4 Color : COLOR;
};
6.5 예제 픽셀 셰이더
- 정점 셰이더와는 달리 픽셀 단편마다 실행 : 이전 단계(정점, 기하)에서 생성된 출력된 정점 값을 바탕으로 레스터라이저가 내부를 픽셀로 채우기때문
번외
모델 행렬 변환
로컬 좌표와 이에 대한 크기,회전,이동에 대한 행렬이 주어질 떄, SRT(크기,회전,이동)
연산을 진행해 modelMatrix
만들어보기.
mul( mul( mul(
translationMatrix,
rotationMatrix),
scaleMatrix),
float4(localPostion, 1.0f))
XMMATRIX CalculateModelMatrix(XMVECTOR localPosition, XMVECTOR scale, XMVECTOR rotationQuat, XMVECTOR translation)
{
// Scale Matrix
XMMATRIX scaleMatrix = XMMatrixScalingFromVector(scale);
// Rotation Matrix (Quaternion to Matrix)
XMMATRIX rotationMatrix = XMMatrixRotationQuaternion(rotationQuat);
// Translation Matrix
XMMATRIX translationMatrix = XMMatrixTranslationFromVector(translation);
// modelMatrix = T * R * S
XMMATRIX modelMatrix = XMMatrixMultiply(XMMatrixMultiply(scaleMatrix, rotationMatrix), translationMatrix);
return modelMatrix;
}
-> XMMatrixMultiply(A, B)
와mul(B,A)
는 행렬 A x B 연산
입니다
-> XMMatrixMultipy(A, B)
는 행렬을 사전에 계산하여 셰이더로 넘길 때 사용되며, HLSL에서 계산하는 것 보다 빠릅니다. (CPU 사용, 고성능 SIMD 연산);
-> 일반적인 물체의 이동, 회전에 대해서 셰이더에 이미 계산된 modelMatrix를 전달합니다
->mul(B, A)
는 셰이더 내에서 각 프레임마다 변환하는 실시간 계산에 적합합니다. (GPU 사용, 비용 비쌈); 물체 표면 물결 파동 등
DirectX 12의 대표적인 힙 타입 3가지
힙 종류 |
특징 |
주 용도 |
DEFAULT 힙 (기본 힙) |
GPU 전용, CPU 접근 불가 |
정적 자원 (Mesh, Texture 등) |
UPLOAD 힙 (업로드 힙) |
CPU가 쓰기 가능, GPU가 읽음 |
매 프레임 갱신되는 상수버퍼, UBO |
READBACK 힙 |
GPU → CPU 데이터 복사 |
GPU 결과 읽기 (디버깅, 피킹 등) |