산술 연산담당하는 ALU의 차이
연관성이 없는 일들을 병렬 처리한다.
GPU는 독립적인 데이터 처리를 할 때 유용하다.
오브젝트는 개개별이라 독립적인 데이터 처리를 하기에 알맞다.
GPU도 정해진 규칙에 맞게 외주를 맡겨야함.
메모리 정보를 토대로 화면으로 결과물을 출력하기 위한 과정이
"rendering pipeLine"이다.
Input Assembler : vertex정보 넘겨주는 단계
Vertex Shader : VS
1.에서 넘겨준 정점좌표 계산하는 부분
정점전환(변환)
이부분은 새로운 정점을 추가할 때 사용됨.
멀리있는거는 대충 그리고 가까이에 있는 것은
vertex추가를 해서 더 현실성있게 하는 부분이
tessllation이다.
3단계부터 Geometry Shader까지는 9~11에서 추가된 새로운 애들이다.
중요하다.
Rasterizer단계 전까지는 vertex단위로 연산을 하는중이다.
vertax data -> pixel data로? 변환하는 작업 정도.
최종적으로 색상을 입히는 부분.
MS에서 제공하는 GPU를 다룰 수 있게 해주는 Library이다.
GPU제조사는 ms와 협력을 해서 stack을 맞추어간다.
애플리캐이션으로 프젝 만들고
&msg를 받고 nullptr하면은 쓰레드와 과련된 모든 메세지를 받는거다.
0, 0은 특정 메세지만 받을 수 있는데 이렇게하면 필터링 없이 다 받는 것이다.
나중에 프로젝트 유지/보수 및 관리를 용이하게 하기 위해서 게임 엔진 라이브러리를 따로 만들것이다.
두가지 종류가 있는데
정적 라이브러리 : GPU가 내장된 노트북
DLL 라이브러리 : 외장 GPU 노트북
이런 느낌이다.
당연히 다른 프젝에서도 사용하고 공유할려면 DLL라이브러리 사용해야함.
관리하기 쉬운거는 정적 라이브러리라 이것을 만들어 줄 것이다.
이런식으로 EnginePch클래스 만들고 폴더 구조를 이런식으로 짜고... 이런거는 그냥 보고 할 수 있다.
EnginePch의 내용은 이렇게 있음.
그리고 중간에 d3dx12.h 헤더파일은 구글링을 해도 나오는 헤더파일임. 다운로드 받아서 Utils산하에 넣어 주도록 하자.
그리고 출력 Directory를 설정을 해야하는데
Engine -> Property -> 모든 플랫폼으로
솔루션 파일 산하에 Output으로 만들어 주도록 하자.
Client 프젝에서는 Engine프젝의 경로를 몰라서 알려 주어야함.
이렇게 폴더가 따로 분리가 되어있고 상대 경로가 변경이 될 수도 있다.
절대 경로가 아니라 상대 경로를 알려주어야하는데 간단한 방법이
포함 디렉토리는 헤더가 있는 경로를 알려주면되고 라이브러리 디렉토리는 라이브러리가 있던 위치를 알려주면은 된다.
솔루션파일의 이러이러한 폴더로 설정을 해주면된다.
그리고 아까 EnginePch헤더에 작성해놓은 HelloEngine이라는 파일을 사용이 가능한데
(이거두개는 약간 한쌍? 느낌?)
"링킹 에러"난다.
우리가 빌드를 하면은 각각의 obj파일이 만들어져서 링킹단계를 거쳐서 exe파일이 만들어지는데 이 링킹 부분에서 문제가 일어나는데
Client 프로젝트 -> Property -> 입력 -> 추가 종속성 -> 편집 한뒤
Engine.lib 추가를 해주면된다.
근데 라이브러리 파일 생길때마다 추가종속석 -> 편집 이 과정이 너무 귀찮다.
Client의 pch에서
이렇게 #praga comment해주면 이게 아까 속석 -> 링커 -> 입력 -> 추가 종속성 편집 과정을 해주는 것이다.
전처리기를 통해서.
Client 를 담당하는 프로젝트와 Engine을 담당하는 프로젝트를 분리를 하여 관리할 수 있도록 하여
Engine lib를 Client쪽에서 참조가 가능하도록 프로젝트 설정을 했다.
GPU에게 데이터 처리를 넘기기 위한 사전 준비 작업정도로 이해를 하면은 된다.
이렇게 추가를 해주고
Engine의 초기화 함수는 몇가지를 받아 주어야한다.
Client쪽에서 호출 할 것이다.
출력 윈도우 handle, width, height등등 받아야됨.
일단 싱글톤 대신 전역으로 생포인터 대신 이렇게 만들어주는데
.h에서 선언을 해주어야 다른데서도 사용을 할 수 있다.
이떄 extern 키워드를 붙여주는데
참고하기를 바란다...
(static은 scope block )
이렇게 해도 되고
이렇게 해도됨.
링킹단계에서 링크 될꺼라 전방선언해서 알려준다.
이거 ::AdjustWindowRect 에서 '::'이거 global namespace에서 찾아서 반환을 해주겠다라는건데 없어도 상관은 없는데
장점이 일반함수가 아니라 window관련 라이브러리에서 제공하는 함수라고 명시(암시)가 가능하다.
(win32API이임)
머임??
다렉자체는 GPU를 '제어'하고 프로그래밍 하는데 쓰이는 저수준 '그래픽 API'라는 특징이 있다.
거두절미하고 MS랑 제조사들이 협력을 해서 GPU를 제조한다고 했는데
우리는 그 스택? (약속?) 에 따라서 프로그래밍만 하면되는데
이 프로그래밍이 가능하게 해주는 것중 하나가 "Com이라는 기술"이다.
ComPtr안에 COM객체가 < > 안에 들어가있는데 이 COM이라는 객체가 GPU의 객체?이다.
이녀석들을 ComPtr은 COM객체를 받는 스마트 포인터 인데, 이녀석을 통해 위화감 없이 사용이 가능한 것이다 (즉 GPU제어한다는 소리)
그래픽 카드에게 명령을 하고 화면을 제어하는 기능을 담당하는 부분이다.
제일 중요 핵심 적인 부분임
GPU에게 데이터 처리를 계속 맡기기는 할 것인데
9까지는 그냥 데이터 던지면 되는데 12부터는 방식이 조금 복잡하게 변경됨.
이거 세가지 부분 Command Pattern이랑 유사한 부분임 일감을 한번에 모아서 처리를 할 수 있도록 하는 부분.
예전에는 무슨 view, 무슨 view이런식으로 했었는데 이제는 DX12부터 추가된 Descritorview로 전체를 다 관리하기 시작함.
GPU가 이해할 수 있게 기안서를 만들어서 건내주는 부분이다.
거의 설명할꺼는 이까지임. 중요한거는 거의 없고 그냥 초기화 작업이라
여기에서 다이렉트 commit 부분 다운받아서 따라 치면됨 ㅇㅇ.
swapChain의 39번째줄 ComPtr ID3D12Resource _renderTargers의
ID3D12Resource가 말그대로 double buffering에 사용되는 resource이다.
그런데 cmdQueue에 요청을 할 때 이거 자체로 보내는게 아니라 descriptorHeap을 통해서 양식에 맞게 보내는 것이다.
그래서 그 resource를 실제로 설명을 하는 부분이
이 view라는 것이다.
그런데 DX12에서는 그런 view를 그냥
이렇게 한번에 만드는게 아니라 DesscriptorHeap이라는 것을 통해서 만드는 것이다.
DescriptorHead안에 View가 두개가 있고
각각의 view는 resource를 가르키는 형태이다.
RootSignature라는 기능이 필요한데 차근차근 알아볼 것이고
우리가 외주를 주는 것은 주체인 CPU가 GPU에게 외주를 주는 것이다.
CPU는 RAM이랑 친밀한 관계인데 RAM(random Access Memory)에 CPU가 접근을 하여 사용을 하는건데
GPU에서는 RAM에 있는 것을 꺼내서 데이터 처리 하는게 말이 안됨. 물리적으로 너무 멀리 떨어져있다.
그래서 CPU에서 어떻게든 RAM에 있는것을 GPU에게 전달을 해야한다.
"계약서"라는 것은 GPU의 어느정도의 공간을 임대를 해서 사용을 하겠다라는 "계약"이다.
근데 이 "계약"을 공자로 하는게 아니라 사인을 하듯이(서명을 하듯이) RootSignature를 통해서 서명을 할것이다. 계약서에.
즉, 어떠어떠한 Register, 어떠어떠한 buffer를 사용을 한다고 서명하는 것임.
이렇게 만들어주고 Engine.h에 만들어주고 Engine.cpp에서 Init호출 해주는 똑같은 방식이다.
삼각형을 그릴려면 이제 Mesh가 필요한데 나중에 여러 mesh들도 넘겨주어야 하기 때문에
클래스 만들어주도록 하겠다.
즉, 삼각형의 각 정점들을 넘겨주어야함. vertex를 GPU한테 넘겨야함.
먼저 Mesh클래스에 Vertex라는 사용자 정의 자료구조를 사용할 것인데
이런식으로 Vertex를 정의한다. Vec4는 그냥 color => R G B A(Alpha)
클래스의 멤버 변수는 이렇고
GPU의 공간을 _vertexBuffer가 할당을 받아서 계약을 하고
_vertexBuufer를 통해서 데이터(vertex)들을 복사 한다 _vertexBuffer에다가
이후 30줄 부터 _vertexBufferView를 통해서 이제 외주를 맡긴 베트남같은데를 묘사한다고 보면은 된다.
외주를 맡기고싶다면은 _vertexBufferView를 건내주면은된다.
[일감 기술서] 외주 인력들이 뭘 해야할지 기술한 명령어들
rendering pipe line과정을 보면은
vertex shader, hull shader, 뭐뭐 등등 무슨 shader과정이 많다.
각 단계별로 무엇을 할지 따로따로 기술 할 수 있다.
기본적으로 가장 중요하게 활용할 부분은
7번째줄 : vertex shader,
8번째줄 : Pixel Shader 이다.
이런식으로 사실은 shader파일도 만들 수 있다.
여기다가 이런 이름으로 만드셈.
그리고 이 shader파일을 코드로 작성을 해주면 되는데
hlsli라는 언어로 shader언어로 만들어 주어야하는데 C/C++언어랑 굉장히 유사함.
ㅇㅇ.
그래서 아까 vertexShader, Pixel Shader단계를 15~25번째줄에 일감을 기술해놓은것임.
이것도 다 함수임. 반환형이랑 함수 이름이랑 매개변수.
그래서 이렇게 외주 일감을 맡기는 파일들을 어떻게든 Load해서 어떠게든 건내주면 되는데
그 부분이 이제 Shader의 Init()부분이 되겠다!
일단 이까지 강의 따라가는거만으로도 ㄱㅊ음 아직.
DX가 GPU에게 외주를 맡기는 피나는 노력이다.
우리가 해야할 것은
초기화 하는 부분은 거의 중요하지가 않고
Rendering 과 관련된 고급기법들을 익히는게 중요하다.
Renderiong pipline과정이다.
우리가 이전시간에
Vs, PS가
딱 이부분이라
이곳을 수정할 수는 없고
rendering의 다른 과정에서 데이터를 추가해서 위치를 변경한다거나 해야한다.
그리고 이 과정을 이해를 할려면 CPU, GPU의 구조에 대한 이해가 필요하다.
이게 CPu, GPU의 메모리 차이? 설계 차이임.
왼쪽 : CPU, 오른쪽 GPU
Core가 CPU의 연산을 담당하는 ALU를 말하는 부분이다.
CPU랑 RAM의 거리는 물리적으로 멀다.
GPU도 마찬가지로 GDDR4 Ram까지 멀어서 cache Level을 두어서 데이터에 접근을한다.
GPU에는 보통 엄청 큰 데이터를 전달을 하는데 막바로 GPU의 Register에다가 넣지는 못하고
GDDR4 RAM공간에 먼저 넣은다음 필요한 부분을 L2 -> L1 -> Register이렇게 끌어다 쓴다.
GPU의 Register는 고오급 영역이라 RootSignature를 통해서 사용하겠다라는 서명을 해주어야하는 영역이다.
어쨋든 GPU가 동작을 할려면은 Register영역을 활용해야 한다는 점은 변함이 없다.
tlqkf 그냥 뭐하는건지 모르겠는데
일단 계약서를 만들기위해 서명하는 양식을 만든다 정도?
여기 CBV라고 root Constant Buffer View 를 만든다고 한다...
??
일단 서명을 한뒤에 이제 GPU의 공간을 임대를 했기 때문에 사용을 해야한다.
그 시점은
Mesh를 렌더링할 때 코드가 추가가 되어야한다.
어떤 데이터를 다른쪽에다가 복사 하는 부분은...
이런식으로 하고있다.
이부분은 Command List를 이요하는게 아니라 device를 통해서 막바로 실행하도록 하는 부분이다.
Device를 통해 뭔가 실행하는 것은 CommandQueue를 통해서 나중에 이루어지는게 아니라 지금 당장 바로 이루어지는 부분이다.
Command List를 통해서 하는것은 나중에 실행한다는 차이점이있다.
이게 중요한데...왜 그렇냐 하면은
우리가 방금 이런식으로 b0, b1을 사용할 것이라 서명을 한 상태임.
사용을 할려먼은 전달을 하기 위한 buffer를 만들어준 다음에 register로 올려보내야함.
딱 이부분임.
CBV는 C++의 포인터와 비슷하게 다른 녀석을 참조하는 녀석이다.
그래서 b0가 어떤 데이터를 가르키고있는지 RAM의 buffer에다가 연결을 해줄 수 있다.
지금 이부분은
이 단계라고 보면은 된다.
Render의 45번째 줄이하는 부분이
지금 하늘색 buffer에서 register로 데이터 옮기는 부분이다.
그래서 1단계 : 즉시 와 2단계 : 나중에의 실행시점이 달라서
1이라는 데이터를 buffer에 넣어놓고 바로 실행을 안하고 대기하다가
나중에 2라는 데이터를 buffer에 넣으면 1위에 2라는 데이터가 덮어써져서 이제 실행을 할때
b0에서는 2라는 데이터만 실행을 하는 "문제"가 발생을 한다.
그래서 Command List를 사용할 때 중의해아하는데
어떻게 해결을 하나?
이렇게 buffer를 여러개를 만들어준다.
즉 사용하고 있는 buffer는 건드리지않고 다음 buffer에 데이터를 넣을 수 있도록 하는 것이다.
이거 만들면
현재 파란색 영역이 만들어 진다.
상수 버퍼는 256바이트의 배수여야 하는데 항상 0, 256, 512를 계산할 수 없기 때문에
비트연산으로 256바이트의 배수로 상수 버퍼를 만들고 있다.
이런 숫자가 있다고 가정을 하고
255의 반전인 ~255는
이런형태인데 이것을 반전하면은
이상태와
비트 연산 &을 수행하겠다라는 것이다.
그러면 이런식으로 남게됨.
offset0, offset1이 각각 Vec4에 넣어준 값으로 셋팅이 된다.
CommandQueue를 활용한 부분인데 Constant Buffer View를 RAM에 즉시 할당받고 CommandQueue를 통해서 Register에 Root Siganture를 사용해서 b0, b1를 서명받아서 사용하는 부분인데
table을 사용하는 방법은
Root Signature안에서 Root Descritor table이라는 것을 만들어서
그 안에다가 b0, b1, ... b4 이런식으로 무엇을 사용을 할지 정의를 해준다.
RAM에다가 Descriptor Heap이라는 것을 만들어서 이곳에 데이터를 채워넣은 다음에
그 녀석을 다시 Register에다가 전달을 해야한다.
b0, b1 .. b4까지 5개를 사용을 할 것이라면은 Desc Heap도 5개로 이루어진 View를 만들어서 정확히 일치 시켜야한다.
그 연결하고 그런 부분운
Commnad List라는 애가 SetDescriptorHeaps이라는 녀석을 통해서 인지를 할 수 있게 해주고
몇번쨰 주소를 활용해야 하는 지는 SetGraphicsRootDescrtorTable을 통해서 알려줄 것이다.
Desc Heap의 View는 이전시간에 Constant Buffer 를 만들어 준것으로 Constant Buffer View를 만들어 줄 것이다.
그리고 Desc Heap을 만들어 준다. 0 -> v0, 1 -> v1 이렇게 일치하도록.
그다음에 Constant Buffer 를 가르키는 Desc Heap이 중간에 있고 제출용으로 사용할 Desc Heap을 하나 더 만들어서 준다.(Register랑 매핑해서 제출할 용도)
또한,
제출용으로 사용할 Desc Heap도 여러개를 만들어주어야 데이터들이 안 덮어 써진다.
RootSignature부터
이까지 b0~b4까지 사용하겠다라고 해준 부분이
GPU의 Register에 이렇게 선포한 부분이랑 일치함.
25의 _cbvHeap이
이부분 말하는 것이다.
_cpuHandleBegin은 _cbvHeap의 시작주소 v0부터 인덱스가 몇개인지를 나타내는 _handleIncrement로 이루어져있다.
이부분은 지금
DescHeap의 view를 register에 올릴 descHeap에 복사를 시키는 부분이다.
그리고
commitTable을 하게 되면은 register에 올라간다.
먼저 Root Signature에서 table과, b0, b1 ~ b4까지 만든다음에
Constant Buffer.cpp, .h에서 DescHeap을 만들고나서
TableDescriptorHead에서 Register에 올릴 DescHeap을 만들었다.
이제 다 만들었으니 최종적으로 다 조립해서 사용하는 부분만 넣어주면은 된다.
Root Signature를 작성 하는 부분에서
기존에는 Root Descriptor를 이용을 했다면은 이제는
Root Descriptor table을 이용해서 관리하는 방향으로 수정을 함.
지금은 삼각형 좌표가 이렇게 있는데 나중에는 character를 만들어야함.
몬스터 천마리 만들때 그러한 메쉬정보를 천번이나 로드를 해서 만드는게 아니라
삼각형 띄울 때처럼 한번만 로드를 하는 것이다. => 만드는 것이다.
현재 Mesh.cpp에 createCommittedResource를 하면서 GPU쪽에다가 매개변수로 받은 vec의 정점 정보들을 가지고
이렇게 복사를 하는 부분을 만들었었는데, 이부분은 굉장히 부화가 많이 걸리기 때문에 한번만 수행을 해주는 부분이다.
그래서 기타 셋팅들은
이렇게 파이르 라인의 중간 단계에 꽂아 넣는 방식으로 함.
=> 크기 조절이나 다른 위치에 놓는다던지 등등
그래도 어쨋든 몬스터의 크기나 뭐 등등은 어쨋든 같은 mesh 데이터를 이용한다는 점이 중요하다❗
우리가 전전 시간에는
Root Descriptor를 이용해서 데이터를 꽂아 넣었고
이전 시간은 Root Descriptor table을 이용해서 인자를 꽂아 넣었음.
그리고 삼각형 그릴 때, 처음에 Vertex shader에서 받아주는 입력값들이 있는데,
그부분은 우리가 vertex buffer로 받아주고 있었다.
이부분임.
코드로 보면은 매개변수(인자)에 vertex 데이터를 받아서 20번째 줄에서 vertextBuffer를 만들어 주었다...
그리고 밑에서 vertexBuffer View를 추가로 만들어 주었다.
그래서 Render에서는
Init에서 만들어준 vertexBufferView를 사용한다고 IA시리즈로 알려줌.
또한 36번째줄에서 삼각형이라고도 힌트를 주고나서
DrawInstanced를 호출을 하게되면은
vertex buffer view를 Input assembler에다가 밀어 넣는 작업이 끝난다고 보면은 된다.
그리고 shader쪽에
VS_IN input쪽에 input assembler에 넣어준 정보들까지 들어가게 되는 것이다.
그래서 이 vertex buffer view뿐만 아니라 index buffer view도 같이 사용을 할 것이고
이거 두개는 국룰처럼 사용하는 것이다.
언제 사용하노?
일단은 vertex buffer 를 사용하여 삼각형 두개로 사각형을 만들어보는 방법으로 해볼 것이다.
이 순서를 일단 기억을 하도록 하자.
우리는 삼각형이라 정점이 3개만 필요했는데 이제는
이렇게 된다.
근데 vertex buffer만 사용하면 중복된 정점이 생겨서 데이터가 쓸데없이 비대해진다.
그래서 vertex + Index 섞어서 사용을 한다.
이렇게. 정점 데이터만 Vertex로 넘겨주고 이 정점들이 어떻게 연결되어 있는지를
Index Buffer로 넘겨주는 것이다.
Game.cpp에서
우리가 지금 한게 VerTex + Index View를 input assembler단계에 넘겨준 것이다.
이게
Mesh의 Init으로 감.
character를 만들때 어떤 정점을 어떤색으로 해주세요~ => 이거 코드로 하는거 말이 안됨.
이때 등장하는게 texture mapping 이다.
정점마다 texture를 mapping을 하면 이렇게 된다는 것이다.
이렇게 붙일 것이다. Pixel단위로 하는게 아니라 일단, 정점 단위로 할 것이다.
화면을 그릴 때는 "투영좌표계" 사용해서 좌표 잡음.
좌표계는 많다.
근데 좌표계로만 입력을 했고 픽셀단위로는 입력을 하지 않았지만 픽셀이 다 채워진다는 것은 누군가가 이 작업을 대신 해준다는 말이다.
Rendering Pipeline을 다시 보면은
조금 밑에 resterizer라는 애가 있다.
이녀석이 "보간 작업"을 해준다.
정점을 3개만 넘겨주게 되면은 삼각형영역에 있어야하는 점들을 다 찾아주는 역할을 resterizer가 해주게된다.
그리고 "보간"을 해주는데
사진의 삼각형의 각 픽셀마다 각 정점이 얼만큼 떨어져있는지를 "비율"을 잘 계산을 해가지고 알맞는 비율을 적용해서 색상을 골라주고있는 부분이다.
그래서 오늘 할 작업이 Texture를 사각형에 입히는 부분을 해볼 것이다.
여러 texture 포맷(.png, .jpg, ...)이 있는데 DX12에 공식적으로 추가가 안되어 있음.
일단 여기 ㄱㄱ -> 다운로드 ->
이버젼 연다.
우리는 이거 두 헤더파일을 가져와야한다.
일단 Debug모드로 빌드하고
Release모드로 빌드 해준다.
이 경로의 DirectXTex.lib 라이브러리 파일을 복사를 해주어야한다.
이 경로에 폴더 만들고
Include, Lib폴더 만들고 Lib파일에는 .h, inl 파일, Lib에는 아까 라이브러리 파일 두개 넣는다.
이까지 끝.
이거 일단 설정을 해주어야함.
3d물체를 띄워주게하는 필수 부품같은 것이다.
100%확률로필요한 녀석임
pipeline마지막 단계에서 Random Target View로 렌더링 할 이미지를 두개로 만들어서 하나는 GPU가 그리는 용도로 하나는 보여주는 용도로 만들었는데
그 밑에 Depth stencil View라는게있다.
이것도 보면은 마지막 단계에서 뭔가를 한다고 볼 수 있는데,
어떠한 깊이나 위치에 있든지 상관없이
비율을 유지한체 우리 2d화면에 투영이 된다는게
핵심이다.
뭐 이런거.
그런데 비율로만 모든거 판단한다는 게 반은 맞고 반은 틀림.
투영할 때 또하나의 정보가 있는데 "깊이"이다.
그래서 어떤 물체를 그려야할지 말아야할지는
최대깊이값 1, 최소 깊이값 0 을 봐가지고
픽셀단위로 깊이를 봐가지고 그려야한다.
그래서 =>
Depth Stencil View의 Depth가 깊이인데
이 깊이값을 추척을 해서 그려야할지 말야야할지를 추적한다고 보면은 되겠다.
(픽셀 단위로 추적을 한다)
우리가 이렇게 그렸던거
이거는 사실 "투영좌표계"사용한 것이다.
근데
Game.cpp에서 pos의 z축 부분 0~1사이의 다른 값들로 해도 딱히 변화가없음.
투영좌표계를 사용하기 때문에 그런것이다.
여기에서
딱 이 스크린 말하는 것임.
그래서 이제 깊이값도 관리할꺼임.
구멍 뚫는 미술 기법인데
저 좌표하나가 4바이트인데 3바이트 1바이트로 나누어서 1바이트는 0~255숫자로 해서 스텐실 값을 두어서
스텐실 값이 5인 대상인 애로만 색을 칠하거나 할 수 있다.
(당장은 활용한 일은 절대 없다 고급기법이라)
좌표이렇게 해주면은
이렇게 뜨는데 이거 그냥 그려주는 순서때문에 그럼
우리는 이제 깊이값 사용해서 조절할 것이다.
수업 내용에따라 수행을 하다보면 잘된다.