6.1 정점과 입력 배치
- 원하는 자료(특성)를 가진 커스텀 정점 형식을 만들려면 그러한 자료를 담을 구조체 정의
- 사용자 정의 정점 예시
struct Vertex1 { XMFLOAT3 Pos; XMFLOAT3 Color; }; struct Vertex2 { XMFLOAT3 Pos; XMFLOAT3 Normal; XMFLOAT2 Tex0; }
- 사용자 정의 정점 예시
- 입력 배치 서술(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)에 넣어 두어야 함
- 버퍼는 텍스처보다 단순 자원
- 다차원 X
- 밉맵, 다중표본화 기능 X
- 정점 같은 자료 원소들의 배열을 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바이트 버퍼 생성
- D12에서 제공하는 해당 구조체를 상속해서 쓰는 편의용 생성자들과 메서드들을 추가한 C++ 래퍼 클래스
- 정적 기하구조(프레임마다 변하지는 않는 기하구조)를 그릴 떄, 최적 성능을 위해 정점 버퍼들을 기본 힙(D3D12_HEAP_TYPE_DEFAULT)에 넣음
- 정점 버퍼를 초기화한 후에는 GPU만 버퍼의 정점들을 읽으므로(기하구조 그리기위해), 기본 힙에 넣는 것이 합당
- CPU는 기본 힙에 있는 정점 버퍼 수정 불가
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 자원에 넣어 두어야 함
색인 버퍼 뷰 대표 형식
D3D12_INDEX_BUFFER_VIEW
구조체typedef struct D3D12_INDEX_BUFFER_VIEW { D3D12_GPU_VIRTUAL_ADDRESS BufferLocation; UINT SizeInBytes; DXGI_FORMAT Format; } D3D12_INDEX_BUFFER_VIEW;
BufferLoation
- 뷰의 대상이 되는 정점 버퍼 자원의 가상 주소
ID3D12Resource::GetGPUVirtualAddress
메서드로 얻을 수 있음
SizeInBytes
- BufferLocation에서 시작하는 색인 버퍼 크기 (바이트 개수)
Format
- 색인 형식 (R16_INT, R32_UINT) 사용
- 메모리와 대역폭을 절약할려면 16비트 사용
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
- 입력 매개변수는 입력 서명(input signature)을 형성
- HLSL에는 참조나 포인터가 없어, 함수가 여러개의 값을 돌려주려면 구조체 사용 or
out
이 지정된 출력 매개변수 사용 - HLSL에서 함수는 항상 인라인화
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
함수 :- HLSL 내장 함수
- 벡터 대 행렬 곱셈 수행
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; };
'프레임워크 > DirectX' 카테고리의 다른 글
06.그리기_연산01_2 (7) | 2025.04.08 |
---|---|
06.그리기_연산01_1 (0) | 2025.04.05 |
05. 렌더링파이프 개념문제 개인제작 (1) | 2025.03.15 |
05. 렌더링 파이프라인 (7) | 2025.03.11 |
04.Direct3D의_초기화03_요약그림 (0) | 2025.03.07 |