그래픽스 파이프라인 -> 물체 하나가 그려지는 과정 (GPU로 연산)
우선은 아래와 같이 간소하게 사용함
Input-Assenbler (IA) -> 화면에 그리기 위해서 필요한 데이터를 입력받는 단계
Vertex Shader -> 버텍스버퍼에 만들어진 정점 정보를 IA 단계가 받고, 버텍스 쉐이더에서 Rasterizer로 정점정보를 넘긴다. 정점 하나당 버텍스스테이지를 병렬로 수행한다.
Rasterizer -> 받은 정점 세개를 정규화된 좌표(NDC, NormarizedDeviceCordinate)로 변환된 걸 받는다.
*NDC 좌표계 -> 중심이 0,0 이고 화면의 전체 영역을 -1 ~ 1 범위로 잡은 좌표계이다.
정규화 한다는 것은 비율을 적용해서 값을 -1 ~ 1 사이의 값으로 변환하는 것이다.
Z축으로도 깊이가 1이다.
Pixel Shader
Output-Merger(OM) -> 최종적으로 랜더타겟에 그림을 출력하는 과정
화면에 그림을 그리기 위해서 '정점' 이 필요하다.
ID3D11Buffer는 ID3D11Resource 상속을 받음.(Texture 2D도 얘를 상속받는다)
ID3D11Resource(리소스) -> 버퍼
-> 텍스쳐
gpu 메모리상에서 무언가를 만들어낸다. 이미지형태인지(텍스쳐) 아님 단순 메모리 버퍼인지(버퍼)
버퍼의 대표적인 용도가 버텍스버퍼, 인덱스버퍼, 콘스탄트버퍼(상수), 스트럭트버퍼 가 있다.
버텍스버퍼에다 정점 정보를 저장시킬 것이다.
정점(버텍스)은 삼차원 공간 상에서의 점의 정보이다.
정점 구성은 프로그래머가 구성할 수 있다.
정점은 삼차원 상에서의 위치정보가 필요하다.
Vector3로 Pos를 표현한다.
0,1,1
1,0,1
-1,0,1
이렇게 만든 좌표는 지역변수이기 때문에 init이 끝나면 메모리가 사라진다.
그렇기 때문에 gpu메모리로 할당해서 옮겨야한다.
버퍼에서 gpu로 보내기 위해서는 D3D11_BUFFER_DESC bufferdesc = {}; 을 채워야한다.
버퍼를 만드는 시점에는 디바이스 클래스가 초기회가 되어있어야 한다.
bufferdesc.bindflags -> 버퍼가 gpu 장치에 전달될 때 (바인딩) 어떤식으로 연결될 것인가의 용도를 설정
정점정보를 수정하고 덮어쓰고 해야되기 때문에
usage = dynamic 으로 하고
CPUAccessFlags = Write로 해야한다.
서브리소스데이터 -> gpu에 넣어주기 전에 초기값 한 번 전달할 수 있다.
전역변수로 버퍼를 생성해서 릴리즈를 해주어야한다.
GPU에 버텍스버퍼 36byte 정점3개 들어가있다.
파이프라인 시작을 할 때, IA 단계에서 버텍스버퍼를 IA 단계에서 사용하라고 전달을 시킨다.
그럼 IA 단계에 입력될 버텍스가 얘구나 하고 인지를 하게 된다.
stride -> 간격, 장치입장에서는 버텍스버퍼라고 주면 36byte가 정점 하나인지 아니면 몇 바이트당 정점 하나인지
모르기 때문에 간격을 정해주어야한다. -> 버택스구조체사이즈를 주면 된다. -> sizeof()
offset -> 시작점으로부터 얼마나 떨어져있는지
버텍스 버퍼 안에 정점이 수천개~수만개가 들어있을 경우, 하나의 버텍스 버퍼안에 시작주소를 옮겨서
0이 아닌 다른 곳으로 시작주소를 줄 수가 있다.
버텍스 쉐이더는 HLSL이라는 쉐이더 언어로 코딩을 해야한다.
이걸 컴파일 한 뒤에 바이너리 코드로 바꾸고 gpu가 이해할 수 있는 기계어로 바꾼 다음에 ID3D11VertexShader 객체를 만들어낸다.
HLSL은 C++ 코드가 아니기 때문에 #pragma once 를 사용하지 못한다.
그래서 C 스타일로 중복참조를 방지해주어야한다.
#ifndef 파일명
#define 파일명
#endif
HLSL은 .fx 파일이고, 엔진이 컴파일될때 포함되는 파일이 아니고 별도의 파일이다.
같이 빌드되는건 아니다.
HLSL컴파일러에서 셰이더 형식을 fx로 바꾸고, 모델은 5.0 하고 저장.
버텍스 쉐이더 만들기 전에 Input layout 을 만들어야한다.
Input layout 도 IA 단계에 전달이 된다.
Input layout -> 정점 구조 정보
IA에 버텍스버퍼의 정보가 넘어오면(간격 28byte만 넘어옴) 앞쪽의 12byte가 좌표고 나머지 16byte가 컬러인걸 모른다.
그래서 하나의 정점 안을 어떻게 구성되어 있는지 정보를 알려주는 것이 Input layout 이다.
D3D11_INPUT_ELEMENT_DESC arrLayout[2] -> 정점에 들어있는 데이터 개수만큼 있어야한다.
지금은 포지션, 컬러로만 구성되어 있기 때문에 [2]가 필요하다.
구조체 하나마다 어떤 녀석인지 설명을 할 것이다.
AlignedByteOffset -> 메모리 시작위치가 어디인지?
포지션의 시작위치는 -> 0
사이즈는 12byte 기 때문에 -> DXGI_FORMAT_R32G32B32_FLOAT; ( 플롯은 4바이트씩)
SemanticName = "POSITION";
SemanticIndex = 0;
컬러의 시작위치는 -> 12 -> 포지션이 12바이트이기때문~
사이즈는 16byte기 때문에 -> DXGI_FORMAT_R32G32B32A32_FLOAT;
SemanticName = "COLOR";
SemanticIndex = 0;
만약, 시멘틱 네임이 컬러 정보지만, POSITION 이라면.. 인덱스는 1로 변경해주어야 겹치지 않는다.
(같은 이름은 존재 할 수가 없다.)
최종 이름은 시멘틱네임+시멘틱인덱스 해서 작성해야한다. EX) POSITION0, COLOR0
시멘틱 인덱스가 0일 경우, 0을 작성하지 않아도 자동으로 0으로 생각한다.
Input layout 이 꼭 필요한 이유.
구조체가 정점의 형태랑 맞춰진다는 보장이 없다.
포지션, 컬러 중에 원하는 것만 받아올 수도 있다.
받아온 데이터를 그대로 OUT 구조체를 만들어서 반환한다.
반환할 때, 입력된 pos 는 float3 지만 float4로 반환한다.
시멘틱은 이 녀석이 어떤 녀석인지 설명하는 역할을 한다.
입력 받을 때는 layout이랑 똑같이 매칭시켜서 가져온다.
아웃시킬 때는 SV_Position을 사용한다. 컬러는 그대로 동일..
레스터라이저 단계에 넘어가면 전달한 데이터 중에 sv_position을 NDC좌표계로 인식을 한다.
애초에 입력한 좌표가 NDC를 생각해서 정점단위를 생각했기 때문이다.
x, y, z, 그리고 w는 동차좌표로 1로 넣어줘야한다.
타입이 안맞을땐 float3 를 float4로 확장해서 float4(_in.vPos, 1.f); w를 동차좌표 1로 넣어준다.
(_in.vPos 까지가 float3 이기 때문에 뒤에 w값만 넣어준다)
*HLSL 구조체 초기화 문법
VTX_OUT output = (VTX_OUT) 0.f;
output.vPos = _in.vPos;
output.vColor = _in.vColor;
return output;
* SV가 붙은 시멘틱
시스템 벨류라고해서 특수한 시멘틱이다.
버텍스 쉐이더는 하는 일 없이 입력 받은 값을 그대로 레스터라이저에게 리턴시키는 역할만 한다.