Direct X InitDirect 2
들어가기 앞서
Init 관련 코드를 포함하기에
문자량이 전반적으로 많아지고 있다고 느끼긴 한다
일단 Init& Run 부분까지는 이렇게 진행을 하며
차후 DX 포스팅 부분은 내가 이해한 개념과 간략한 코드 등을 위주로 해보려 한다
AppBase::InitGUI()
bool AppBase::InitGUI() {
IMGUI_CHECKVERSION();
ImGui::CreateContext();
ImGuiIO &io = ImGui::GetIO();
(void)io;
io.DisplaySize = ImVec2(float(m_screenWidth), float(m_screenHeight));
ImGui::StyleColorsLight();
// Setup Platform/Renderer backends
if (!ImGui_ImplDX11_Init(m_device.Get(), m_context.Get())) {
return false;
}
if (!ImGui_ImplWin32_Init(m_mainWindow)) {
return false;
}
return true;
}
AppBase의 Init의 가장 마지막인
GUI에 대한 초기화 단계이다
여기서는 ImGui를 사용
-
- Imgui
- C++ 기반의 GUI 라이브러리
디버깅/툴 용 UI 제작 및 에디터 등에 사용 가능
매우 빠르게 테스트용 UI를 만들 수 있기에
선호되는 내부 디버깅용 라이브러리다
- Imgui
- vcpkg로 Imgui를 설치
-
- vcpkg?
- C++의 패키징 매니저
imgui,boost 등의 라이브러리를 가볍게 설치 가능
(일반적으로 소스코드 같은 저장소 쪽 루트나 개발환경에 맞추어 로컬에 설치)
- vcpkg?
-
- vcpkg 설치법
- git clone https://github.com/microsoft/vcpkg.git 명령어를 통해
해당 위치의 vcpkg를 현재 폴더 위치에 복사
이후 vcpkg.bat를 실행하면 세팅 끝
- vcpkg 설치법
-
- vcpkg를 이용한 라이브러리 설치법
- ‘vcpkg’가 설치된 폴더의 위치에서
vcpkg install imgui:x64-windows
(:x64-windows - 동적 링크(DLL 기반))
이런식으로 해당 라이브러리 설치 가능
- vcpkg를 이용한 라이브러리 설치법
- vcpkg integrate install 를 통해
visual studio가 vcpkg 패키지를 인식하게 가능하다
(include/lib 경로에 세팅해준다)
-> 기본적으로 x64 등으로 빌드한다면 잡아줌
-> x86 등으로 빌드하는 경우는 ‘프로젝트 속성 > VC++ 디렉터리 > 포함 디렉터리 / 라이브러리 디렉터리’에
수동 추가 해야 한다
(아니면 설치할때 :x86-windows로 설치하던가)
-
-
- ImGui_ImplDX11_Init
- Imgui가 사용하는 backend 초기화를 위해
Device와 Context를 넘겨준다
- ImGui_ImplDX11_Init
ExampleApp::Initialize() 1. 개념 정리
돌고 돌아 다시 온 ExampleApp::Initialize()
AppBase 쪽은 사실 정형화된 Windows,DX,GUI 초기화에 가깝고
ExampleApp 쪽의 Initialize 부분이
우리가 커스텀할 여지가 많은 초기화 부분이다
사용하는 개념들
-
- Buffer
- GPU 메모리에 넣을 데이터들
(Texture2D)
- Buffer
-
- Vertex Buffer
- ‘정점’에 대한 정보를 담은 버퍼
(ex : 좌표,색상,Normal, UV 좌표 등)
- Vertex Buffer
-
- Index Buffer
- ‘정점’의 ‘그리는 순서’를 저장하는 버퍼
(‘동일한 정점’을 여러번 쓰지 않고
Index로 지정하여 재활용 한다)
원래 사각형 만들 때,
‘삼각형 2개’ -> 정점 6개 가 필요하나
서로 ‘붙는’ 2개의 정점은
각각의 삼각형이 ‘공유’하여 사용할 수 있음
‘따라서’ 그리는 순서만 맞도록 설정하여 정점을 공유
(CCW/CW에 따라 그리는 순서를 다르게 해야 함)
- Index Buffer
-
- Constant Buffer (or Uniform Buffer)
- GPU 쉐이더에 일정 기간동안 ‘고정적’으로 넘길 값을 담은 버퍼
(ex : model,view,projection 행렬 정보, 조명 방향 등)
매 정점마다 달라지는 것이 ‘아닌’
Draw Call 단위로 동일하게 적용되는 값들을 담는다
(DX에서 CBuffer 라고도 말함)
(OpenGL에선 Uniform Buffer Object 라 한다)
- Constant Buffer (or Uniform Buffer)
-
- Shader
- GPU에서 실행되는 일종의 프로그램
- Shader
-
- Vertex Shader
- 정점 하나하나를 입력으로 받아 처리하는 쉐이더
(파이프라인에서 이 단계를 VS라 함)
- 정점의 좌표를 Model -> World -> View -> Clip으로 변환
- 조명 계산을 위한 Normal 변환
- 텍스쳐 좌표를 다음 과정으로 전달
등의 처리 -
- 출력값
- 보통 NDC(정규화된 장치 좌표)와
Pixel Shader로 넘길 보간 데이터들
- 출력값
- Vertex Shader
좌표계의 의미
단계 | 공간(Space) | 변환 수식 | 특징 / 의미 |
---|---|---|---|
1 | Object(Local) Space | 모델 정점 좌표 v_local |
- 모델이 자기 기준(모델 원점)에서 갖는 좌표 - 예: 큐브라면 (±1, ±1, ±1) - 모델링 툴(Blender, Maya 등)에서 만들어진 좌표계 |
2 | World Space | v_world = WorldMatrix * v_local |
- 월드 내 배치(위치, 회전, 스케일)가 반영된 좌표 - 모든 객체가 하나의 공통 월드 좌표계 안에 놓임 - 광원, 충돌 처리, 물리 연산 등이 이 공간에서 계산됨 |
3 | View (Eye/Camera) Space | v_view = ViewMatrix * v_world |
- 카메라가 (0,0,0) 원점에 있고 +Z or -Z 축을 바라보는 좌표계로 변환 - 즉, 카메라 입장에서 바라본 장면 - 카메라 이동은 사실상 월드 전체를 반대로 이동/회전시키는 것 |
4 | Clip Space | v_clip = ProjectionMatrix * v_view |
- 투영(Perspective/Orthographic) 행렬을 곱한 결과 - 아직 w 로 나누지 않은 상태 ((x, y, z, w) )- 좌표 유효 범위: -w ≤ x,y,z ≤ w (이걸 View Frustum이라 함)- 이 범위를 벗어나면 Clipping 단계에서 잘림 |
5 | NDC (Normalized Device Coordinates) | v_ndc = (x_clip/w, y_clip/w, z_clip/w) |
- Clip 좌표를 w 로 나눠 정규화- 좌표 범위: [-1, 1] (x,y,z 모두)- (-1,-1) = 왼쪽 아래, (1,1) = 오른쪽 위, z는 보통 [0,1] 또는 [-1,1] API에 따라 다름- 이 단계까지는 해상도와 무관 |
6 | Viewport / Screen Space | v_screen.x = (v_ndc.x+1)/2 * Width v_screen.y = (1-v_ndc.y)/2 * Height v_screen.z = DepthBuffer |
- 실제 모니터 해상도 픽셀 좌표 - NDC → 픽셀 단위 좌표로 매핑 - Y축 뒤집힘 주의 (DirectX vs OpenGL 차이 존재) - 최종적으로 **백버퍼(Render Target)**에 기록됨 |
VS의 결과물은 Clip 좌표계
RS는 입력으로 NDC 좌표계를 받음
(Clip->NDC는 GPU 내부에서 처리해준다)
(VS->RS 라고 그 중간 단계가 없는 것은 아니다)
ViewPort와 Screen Space는 매우 유사하나
더 좁게 나누자면
ViewPort : GPU에서 계산된 렌더 타겟 픽셀 기준의 좌표
Screen : 실제 OS/ 모니터에서 보이는 최종 윈도우 픽셀 좌표
(ex : Viewport는 화면을 기준으로 잡았으나
사용자가 ‘창 모드’를 키면 Screen 좌표는
그에 맞게 최종 설정된다)
-
- Pixel Shader
- 래스터라이저(RS)가 만든 픽셀 단위의 데이터를 입력으로 받아 처리하는 쉐이더
(파이프라인에서 이 단계를 PS라 함)
- 최종 색상(Color) 계산
- 텍스쳐 샘플링
- 조명/그림자 연산
등의 처리 -
- 출력값
- 렌더 타겟(RTV가 가리키는 버퍼)에 쓸 색상값
- 출력값
- Pixel Shader
ExampleApp::Initialize() 2. 기하 정보 만들기
bool ExampleApp::Initialize() {
// 기초 초기화 완료
if (!AppBase::Initialize())
return false;
// Geometry 정의
auto [vertices, indices] = MakeBox();
...
}
- auto [vertices, indices] = MakeBox();를 통해 ‘기하정보’를 획득
(여기서는 정육면체)
MakeBox() 코드
auto MakeBox() {
vector<Vector3> positions;
vector<Vector3> colors;
vector<Vector3> normals;
const float scale = 1.0f;
// 윗면
positions.push_back(Vector3(-1.0f, 1.0f, -1.0f) * scale);
positions.push_back(Vector3(-1.0f, 1.0f, 1.0f) * scale);
positions.push_back(Vector3(1.0f, 1.0f, 1.0f) * scale);
positions.push_back(Vector3(1.0f, 1.0f, -1.0f) * scale);
colors.push_back(Vector3(1.0f, 0.0f, 0.0f));
colors.push_back(Vector3(1.0f, 0.0f, 0.0f));
colors.push_back(Vector3(1.0f, 0.0f, 0.0f));
colors.push_back(Vector3(1.0f, 0.0f, 0.0f));
normals.push_back(Vector3(0.0f, 1.0f, 0.0f));
normals.push_back(Vector3(0.0f, 1.0f, 0.0f));
normals.push_back(Vector3(0.0f, 1.0f, 0.0f));
normals.push_back(Vector3(0.0f, 1.0f, 0.0f));
// 아랫면
positions.push_back(Vector3(-1.0f, -1.0f, -1.0f) * scale);
positions.push_back(Vector3(-1.0f, -1.0f, 1.0f) * scale);
positions.push_back(Vector3(1.0f, -1.0f, 1.0f) * scale);
positions.push_back(Vector3(1.0f, -1.0f, -1.0f) * scale);
colors.push_back(Vector3(1.0f, 1.0f, 0.0f));
colors.push_back(Vector3(1.0f, 1.0f, 0.0f));
colors.push_back(Vector3(1.0f, 1.0f, 0.0f));
colors.push_back(Vector3(1.0f, 1.0f, 0.0f));
normals.push_back(Vector3(0.0f, -1.0f, 0.0f));
normals.push_back(Vector3(0.0f, -1.0f, 0.0f));
normals.push_back(Vector3(0.0f, -1.0f, 0.0f));
normals.push_back(Vector3(0.0f, -1.0f, 0.0f));
// 앞면
positions.push_back(Vector3(-1.0f, -1.0f, 1.0f) * scale);
positions.push_back(Vector3(-1.0f, 1.0f, 1.0f) * scale);
positions.push_back(Vector3(1.0f, 1.0f, 1.0f) * scale);
positions.push_back(Vector3(1.0f, -1.0f, 1.0f) * scale);
colors.push_back(Vector3(0.0f, 1.0f, 0.0f));
colors.push_back(Vector3(0.0f, 1.0f, 0.0f));
colors.push_back(Vector3(0.0f, 1.0f, 0.0f));
colors.push_back(Vector3(0.0f, 1.0f, 0.0f));
normals.push_back(Vector3(0.0f, 0.0f, -1.0f));
normals.push_back(Vector3(0.0f, 0.0f, -1.0f));
normals.push_back(Vector3(0.0f, 0.0f, -1.0f));
normals.push_back(Vector3(0.0f, 0.0f, -1.0f));
// 뒷면
positions.push_back(Vector3(-1.0f, -1.0f, -1.0f) * scale);
positions.push_back(Vector3(-1.0f, 1.0f, -1.0f) * scale);
positions.push_back(Vector3(1.0f, 1.0f, -1.0f) * scale);
positions.push_back(Vector3(1.0f, -1.0f, -1.0f) * scale);
colors.push_back(Vector3(0.0f, 0.0f, 1.0f));
colors.push_back(Vector3(0.0f, 0.0f, 1.0f));
colors.push_back(Vector3(0.0f, 0.0f, 1.0f));
colors.push_back(Vector3(0.0f, 0.0f, 1.0f));
normals.push_back(Vector3(0.0f, 0.0f, 1.0f));
normals.push_back(Vector3(0.0f, 0.0f, 1.0f));
normals.push_back(Vector3(0.0f, 0.0f, 1.0f));
normals.push_back(Vector3(0.0f, 0.0f, 1.0f));
// 왼쪽
positions.push_back(Vector3(1.0f, -1.0f, -1.0f) * scale);
positions.push_back(Vector3(1.0f, 1.0f, -1.0f) * scale);
positions.push_back(Vector3(1.0f, 1.0f, 1.0f) * scale);
positions.push_back(Vector3(1.0f, -1.0f, 1.0f) * scale);
colors.push_back(Vector3(1.0f, 0.0f, 1.0f));
colors.push_back(Vector3(1.0f, 0.0f, 1.0f));
colors.push_back(Vector3(1.0f, 0.0f, 1.0f));
colors.push_back(Vector3(1.0f, 0.0f, 1.0f));
normals.push_back(Vector3(-1.0f, 0.0f, 0.0f));
normals.push_back(Vector3(-1.0f, 0.0f, 0.0f));
normals.push_back(Vector3(-1.0f, 0.0f, 0.0f));
normals.push_back(Vector3(-1.0f, 0.0f, 0.0f));
// 오른쪽
positions.push_back(Vector3(-1.0f, -1.0f, -1.0f) * scale);
positions.push_back(Vector3(-1.0f, 1.0f, -1.0f) * scale);
positions.push_back(Vector3(-1.0f, 1.0f, 1.0f) * scale);
positions.push_back(Vector3(-1.0f, -1.0f, 1.0f) * scale);
colors.push_back(Vector3(0.0f, 1.0f, 1.0f));
colors.push_back(Vector3(0.0f, 1.0f, 1.0f));
colors.push_back(Vector3(0.0f, 1.0f, 1.0f));
colors.push_back(Vector3(0.0f, 1.0f, 1.0f));
normals.push_back(Vector3(1.0f, 0.0f, 0.0f));
normals.push_back(Vector3(1.0f, 0.0f, 0.0f));
normals.push_back(Vector3(1.0f, 0.0f, 0.0f));
normals.push_back(Vector3(1.0f, 0.0f, 0.0f));
vector<Vertex> vertices;
for (size_t i = 0; i < positions.size(); i++) {
Vertex v;
v.position = positions[i];
v.color = colors[i];
vertices.push_back(v);
}
vector<uint16_t> indices = {
0, 1, 2, 0, 2, 3, // 윗면
4, 5, 6, 4, 6, 7, // 아랫면
8, 9, 10, 8, 10, 11, // 앞면
12, 13, 14, 12, 14, 15, // 뒷면
16, 17, 18, 16, 18, 19, // 왼쪽
20, 21, 22, 20, 22, 23, // 오른쪽
};
return tuple{vertices, indices};
}
정육면체 Box 만드는 코드
각각의 position과 color, normal을 고려하여
각각의 위치에 넣어주면 된다
이후 Index를 CCW/CW에 맞게 설정
ExampleApp::Initialize() 3. Vertex Buffer 만들기
bool ExampleApp::Initialize() {
// MakeBox...
AppBase::CreateVertexBuffer(vertices, m_vertexBuffer);
...
}
그릴 정보의 정점(vertices)를 기반으로
Vertex Buffer를 만든다
AppBase::CreateVertexBuffer 코드
template <typename T_VERTEX>
void CreateVertexBuffer(const vector<T_VERTEX> &vertices, ComPtr<ID3D11Buffer> &vertexBuffer) {
// D3D11_USAGE enumeration (d3d11.h)
// https://learn.microsoft.com/en-us/windows/win32/api/d3d11/ne-d3d11-d3d11_usage
D3D11_BUFFER_DESC bufferDesc;
ZeroMemory(&bufferDesc, sizeof(bufferDesc));
bufferDesc.Usage = D3D11_USAGE_IMMUTABLE; // 초기화 후 변경X
bufferDesc.ByteWidth = UINT(sizeof(T_VERTEX) * vertices.size());
bufferDesc.BindFlags = D3D11_BIND_VERTEX_BUFFER;
bufferDesc.CPUAccessFlags = 0; // 0 if no CPU access is necessary.
bufferDesc.StructureByteStride = sizeof(T_VERTEX);
D3D11_SUBRESOURCE_DATA vertexBufferData = {0}; // MS 예제에서 초기화하는 방식
vertexBufferData.pSysMem = vertices.data();
vertexBufferData.SysMemPitch = 0;
vertexBufferData.SysMemSlicePitch = 0;
const HRESULT hr =
m_device->CreateBuffer(&bufferDesc, &vertexBufferData, vertexBuffer.GetAddressOf());
if (FAILED(hr)) {
std::cout << "CreateBuffer() failed. " << std::hex << hr << std::endl;
};
}
-
- Template 함수로 선언한 이유?
- 일반 함수라면 Vertex 구조체에 추가적인 정보가 들어오는 경우
추가적인 수정이 필요하거나 함수 오버로딩이 발생하기에
템플릿 함수를 사용
(ex : 지금 pos,color 값만 있지만 normal, uv 좌표 등이
vertex에 추가될 수 있음)
- Template 함수로 선언한 이유?
-
Vertex 버퍼는 ID3D11Buffer 타입
-
D3D11_BUFFER_DESC 로 버퍼 세팅
(당장은 모델 하나만 사용하기에 IMMUTABLE)
(CPUAccessFlags : 0 - 어차피 IMMUTABLE)
(StructureByteStride : 하나의 요소가 가지는 크기) -
- D3D11_SUBRESOURCE_DATA?
- 버퍼나 텍스쳐 등을 생성할 때
‘초기 데이터’를 전달하기 위한 구조체
(GPU 메모리 잡은 후, 그 안에 당장 넣을 데이터를 뜻함)
(어떤 데이터를 어떻게 보낼지를 설정한다 봐도 된다)
- psystem : 초기 데이터 포인터(CPU의 메모리 주소)
- SysMemPitch : 한 행(row)의 메모리 간격(byte 단위, 2D 텍스쳐용)
- SysMemSlicePitch : 한 슬라이스(layer)의 메모리 간격(byte 단위, 2D 텍스쳐용)
(여기서는 1D 데이터이므로 둘 다 0 설정)
- D3D11_SUBRESOURCE_DATA?
- m_device->CreateBuffer 를 통해
Device에 Buffer 생성 요청
(GPU에 메모리 할당 요청)
ID3D11Buffer Vs ID3D11Texture2D?
구분 | ID3D11Buffer | ID3D11Texture2D |
---|---|---|
메모리 배치 | 1차원 선형 배열 | 2차원 텍셀 그리드 (타일/스위즐 등 GPU 친화 배치) |
포맷 지정 | 옵션 (Raw, Structured, 정수/실수) | DXGI_FORMAT 필수 (R8G8B8A8, R32_FLOAT 등) |
차원/레벨 | 1D, mipmap 없음 | 2D (배열/큐브맵/MSAA 가능), mipmap 지원 |
셰이더 접근 | Buffer<T> , StructuredBuffer<T> , ByteAddressBuffer , RWStructuredBuffer |
Texture2D<T> , RWTexture2D<T> (샘플러/LOD/필터링 지원) |
필터링/샘플링 | 불가 (그냥 로드) | 가능 (포인트/선형/애니소, 샘플러 상태 활용) |
렌더 타겟/깊이 | 불가 | 가능 (RenderTargetView , DepthStencilView ) |
UAV(SRV) | 둘 다 가능 (바인드 플래그 필요) | 둘 다 가능 (바인드 플래그 필요) |
주요 활용 예시 | - 정점/인덱스/상수 데이터 저장 - 구조체 배열, 플래그 테이블, GPGPU 계산(스캔/리덕션) - Append/Consume 큐 (StructuredBuffer + UAV) - 정밀한 1D 랜덤 액세스 (ByteAddressBuffer) |
- 렌더 타겟/깊이 버퍼/포스트프로세스 중간 결과(G-Buffer) - 샘플링/필터링/LOD 기반 룩업 (예: 라이트 쿠키, LUT) - 2D 지역성 활용, 자연스러운 정수 좌표 접근 - RWTexture2D 기반 이미지 처리 (블러, 컨볼루션, 타일드 라이팅 등) |
두 인터페이스 모두
ID3D11Resource 를 상속받는 리소스 타입이며
‘버퍼’라는 넓은 개념에 들어간다
요약
- ID3D11Buffer : 1차원 데이터 컨테이너, 계산/ 정점 데이터 등에 적합 (일부 파이프라인은 Buffer를 고정으로 요구)
- ID3D11Texture2D : 2차원 데이터 컨테이너, 샘플링/렌더타겟/이미지 연상 등에 적합
ExampleApp::Initialize() 4. Index Buffer 만들기
bool ExampleApp::Initialize() {
// Vertex Buffer...
// 인덱스 버퍼 만들기
m_indexCount = UINT(indices.size());
AppBase::CreateIndexBuffer(indices, m_indexBuffer);
...
}
인덱스의 길이를 통해 m_indexCount를 계산하고
AppBase::CreateIndexBuffer 를 통해 Index Buffer를 계산
AppBase::CreateIndexBuffer 코드
void AppBase::CreateIndexBuffer(const std::vector<uint16_t> &indices,
ComPtr<ID3D11Buffer> &m_indexBuffer) {
D3D11_BUFFER_DESC bufferDesc = {};
bufferDesc.Usage = D3D11_USAGE_IMMUTABLE; // 초기화 후 변경X
bufferDesc.ByteWidth = UINT(sizeof(uint16_t) * indices.size());
bufferDesc.BindFlags = D3D11_BIND_INDEX_BUFFER;
bufferDesc.CPUAccessFlags = 0; // 0 if no CPU access is necessary.
bufferDesc.StructureByteStride = sizeof(uint16_t);
D3D11_SUBRESOURCE_DATA indexBufferData = {0};
indexBufferData.pSysMem = indices.data();
indexBufferData.SysMemPitch = 0;
indexBufferData.SysMemSlicePitch = 0;
m_device->CreateBuffer(&bufferDesc, &indexBufferData, m_indexBuffer.GetAddressOf());
}
_DESC를 통해 버퍼의 용도 등을 설정한 후
device를 통해 버퍼를 생성
- IndexBuffer는 항상 자연수값을 받기에
uint16_t를 사용하고
‘템플릿 함수’로 선언하지 않음
(create Vertex와 pixel이 헤더에 있는 이유)
ExampleApp::Initialize() 5. Constant Buffer 만들기
bool ExampleApp::Initialize() {
// Index Buffer...
// ConstantBuffer 만들기
m_constantBufferData.model = Matrix();
m_constantBufferData.view = Matrix();
m_constantBufferData.projection = Matrix();
AppBase::CreateConstantBuffer(m_constantBufferData, m_constantBuffer);
...
}
model,view,projection은
정점마다 바뀌는 것이 아닌 drawCall 동안 유지되기에
Constant 버퍼에 넣어준다
(지금은 임시로 빈 배열을 넣어줌)
AppBase::CreateConstantBuffer 코드
template <typename T_CONSTANT>
void CreateConstantBuffer(const T_CONSTANT &constantBufferData,
ComPtr<ID3D11Buffer> &constantBuffer) {
D3D11_BUFFER_DESC cbDesc;
cbDesc.ByteWidth = sizeof(constantBufferData);
cbDesc.Usage = D3D11_USAGE_DYNAMIC;
cbDesc.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
cbDesc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
cbDesc.MiscFlags = 0;
cbDesc.StructureByteStride = 0;
// Fill in the subresource data.
D3D11_SUBRESOURCE_DATA InitData;
InitData.pSysMem = &constantBufferData;
InitData.SysMemPitch = 0;
InitData.SysMemSlicePitch = 0;
m_device->CreateBuffer(&cbDesc, &InitData, constantBuffer.GetAddressOf());
}
-
Constant에 넣어야 하는 정보가 추가될 수 있으므로
Template 선언 -
- Usage_Dynamic 설정된 이유?
- 회전 행렬 등이 적용되면
해당 constant 정보가 변할 수 있기에
바뀐 행렬을 drawcall 때마다 보내줘야 함
따라서 Dynamic 설정 + CPU_ACCESS_WRITE
- Usage_Dynamic 설정된 이유?
ExampleApp::Initialize() 6. Vertex Shader 생성
bool ExampleApp::Initialize() {
// Constant Buffer...
// 쉐이더 만들기
// Input-layout objects describe how vertex buffer data is streamed into the
// IA(Input-Assembler) pipeline stage.
// https://learn.microsoft.com/en-us/windows/win32/api/d3d11/nf-d3d11-id3d11devicecontext-iasetinputlayout
// Input-Assembler Stage
// The purpose of the input-assembler stage is to read primitive data
// (points, lines and/or triangles) from user-filled buffers and assemble
// the data into primitives that will be used by the other pipeline stages.
// https://learn.microsoft.com/en-us/windows/win32/direct3d11/d3d10-graphics-programming-guide-input-assembler-stage
vector<D3D11_INPUT_ELEMENT_DESC> inputElements = {
{"POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0},
{"COLOR", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 4 * 3, D3D11_INPUT_PER_VERTEX_DATA, 0},
};
AppBase::CreateVertexShaderAndInputLayout(L"ColorVertexShader.hlsl", inputElements,
m_colorVertexShader, m_colorInputLayout);
...
}
-
- D3D11_INPUT_ELEMENT_DESC
- 정점 데이터의 구조를 GPU에게 알려주기 위한 구조체
ex)
SemanticName : 시멘틱이름 - “POSITION”
SemanticIndex : 동일 이름 시멘틱 구분용 인덱스 - 0
Format : 데이터 포맷 - DXGI_FORMAT_R32G32B32_FLOAT
InputSlot : 입력 슬롯(여러 버퍼 구분용) - 0
AlignedByteOffset : 버퍼에서 이 요소가 시작하는 바이트 오프셋 - 0
(바로 아래의 COLOR는 Position의 크기를 건너띄어 시작한다)
InputSlotClass : 데이터의 종류 - D3D11_INPUT_PER_VERTEX_DATA
InstanceDataStepRate : 몇 개 인스턴스를 그릴 때마다 데이터를 다음으로 넘길지를 지정 - 0
(이 옵션은 여러 인스턴스를 다양한 버퍼 데이터를 사용할 때 활용)
(0 - 모든 인스턴스가 같은 옵션 사용)
- D3D11_INPUT_ELEMENT_DESC
- SemanticName을 VS의 Semantic과 맞춰줘야 한다
SematicName + Index 로 VS에서 COLOR0 등의 Semantic으로 연결
(참고 : https://learn.microsoft.com/en-us/windows/win32/direct3dhlsl/dx-graphics-hlsl-semantics)
AppBase::CreateVertexShaderAndInputLayout 코드
void AppBase::CreateVertexShaderAndInputLayout(
const wstring &filename, const vector<D3D11_INPUT_ELEMENT_DESC> &inputElements,
ComPtr<ID3D11VertexShader> &vertexShader, ComPtr<ID3D11InputLayout> &inputLayout) {
ComPtr<ID3DBlob> shaderBlob;
ComPtr<ID3DBlob> errorBlob;
// 주의: 쉐이더의 시작점의 이름이 "main"인 함수로 지정
HRESULT hr =
D3DCompileFromFile(filename.c_str(), 0, 0, "main", "vs_5_0", 0, 0, &shaderBlob, &errorBlob);
CheckResult(hr, errorBlob.Get());
m_device->CreateVertexShader(shaderBlob->GetBufferPointer(), shaderBlob->GetBufferSize(), NULL,
&vertexShader);
m_device->CreateInputLayout(inputElements.data(), UINT(inputElements.size()),
shaderBlob->GetBufferPointer(), shaderBlob->GetBufferSize(),
&inputLayout);
}
-
- ID3DBlob
- Direct3D의 바이너리 데이터 컨테이너
단순한 메모리 버퍼로 ‘임시 데이터’ 등을 담는데 사용
GetBufferPointer() - 내부 메모리 시작 주소 반환
GetBufferSize() - 버퍼 크기(바이트 단위) 반환
- ID3DBlob
-
- D3DCompileFromFile
- HLSL 셰이더를 파일로부터 읽어와 컴파일하는 함수
(지금은 vs 이므로 셰이더 모델(버전)을 vs_5_0 로 설정)
(0,0 - 매크로나 include 를 넣어줄 수 있으나 여기선 pass)
(main - 셰이더의 엔트리 포인트가 될 함수)
(shaderBlob - 성공 시의 바이트 코드 반환)
(errorBlob - 실패 시의 에러 메시지 반환)
- D3DCompileFromFile
-
CheckResult를 통해 성공여부 체크
(파일이름 틀린 경우, 에러 메시지 등을 체크)
(AppBase에 있는 체크용 함수다) -
CreateVertexShader를 통해 Device에
쉐이더 객체를 실제로 생성한다 - CreateInputLayout를 통해
만들어진 쉐이더 객체와 inputElements를 연결
ExampleApp::Initialize() 7. Pixel Shader 생성
bool ExampleApp::Initialize() {
// Vertex Shader...
AppBase::CreatePixelShader(L"ColorPixelShader.hlsl", m_colorPixelShader);
return true;
}
별도의 hlsl 파일로 픽셀 쉐이더를 생성
AppBase::CreatePixelShader 코드
void AppBase::CreatePixelShader(const wstring &filename, ComPtr<ID3D11PixelShader> &pixelShader) {
ComPtr<ID3DBlob> shaderBlob;
ComPtr<ID3DBlob> errorBlob;
// 주의: 쉐이더의 시작점의 이름이 "main"인 함수로 지정
HRESULT hr =
D3DCompileFromFile(filename.c_str(), 0, 0, "main", "ps_5_0", 0, 0, &shaderBlob, &errorBlob);
CheckResult(hr, errorBlob.Get());
m_device->CreatePixelShader(shaderBlob->GetBufferPointer(), shaderBlob->GetBufferSize(), NULL,
&pixelShader);
}
vs에 유사하나 ps로 지정
댓글남기기