기본 지식
1.정점 특성 / 입력 배치 서술★
directX의 정점셰이더에서 처리되는 정점들은 위치정보외의 다른 정보들도 포함할 수 있다. 예를 들면 법선 벡터나 텍스처 좌표, 또는 절대 색상정보등을 가질 수 있다.
ditectX에서 cpu에서의 정점 데이터들을 관리할때는 그냥 구조체로 정점들의 특성을 나열하면 된다.
ex)
struct Vectex1
{
XMFLOAT3 Pos;
XMFLOAT4 Color;
}
이러한 구조체들의 배열은 바이트 그대로 gpu의 자원으로 전달 생성되어 사용된다.(void*로 전달)
파이프라인이 실행될때 여러 슬롯에 등록된 정점버퍼(gpu의 정점배열데이터)들로부터 어떤 방식으로 각 정점의 특성들을 가져올지는 입력배치서술이라는 것으로 서술된다.
입력배치서술은 정점특성 입력 매개변수의 배열로 표현된다.
입력 매개변수들은 각 특성들이 어느 슬롯에 있는지, 한 정점데이터 안에서 몇번째 바이트부터인지, 무슨 포맷인지, 무슨 의미소로 정점셰이더에서 받아 사용할 지등, 각 특성들을 서술한다.
이러한 입력배치서술을 완성하면 이는 이후 PSO를 생성할 때 사용된다.
2.디폴트 힙 영역의 버퍼★
directX에서 gpu자원들은 gpu 힙의 여러 영역에 할당된다. 이때 힙의 종류에 따라 여러 특성을 가지는데 대표적으로 디폴트 영역은 기본적인 성능이 좋은대신 cpu에서 직접적으로 수정할 수 없다. 반대로 업로드 영역은 성능이 상대적으로 낮은대신 cpu에서 직접적으로 수정할 수 있다.
한번 값을 지정한 뒤로는 cpu에서 수정할일 없는데 gpu의 자원들(로컬 정점 데이터를 가지는 정점버퍼 나 색인버퍼 등)은 디폴트 영역에 할당하는 것이 성능에 좋다.
이때 디폴트 영역의 자원들을 생성 후 기본값으로 초기화를 해주어야하는데 cpu에서 직접 데이터를 넣을 수 없기에 잠시 사용할 업로드 영역의 버퍼를 하나 생성하여 초기화해준다. 초기화는 다음의 단계로 진행된다.
- 디폴트 버퍼 생성
- 업로드 버퍼 생성 (해당 버퍼의 포인터는 이후 할당해제를 위해 따로 저장해둔다.)
- 초기화할 데이터를 subresource_data라는 구조체에 담기
- 디폴트 버퍼의 상태를 copy용으로 수정
- 커맨드 리스트 명령 생성 함수를 이용하여 업로드 버퍼를 통해 디폴트 버퍼에 데이터를 카피하는 함수 실행
- 디폴트 버퍼의 상태를 read용으로 수정
- 함수 종료
- 이후 호출자쪽에서 flush등으로 커맨드리스트의 명령이 모두 수행된 뒤 업로드 버퍼를 할당 해제
한번
3.정점 버퍼★
directX에서 정점 특성 데이터들은 정점 버퍼로 관리한다. 정점 버퍼는 보통 디폴트 버퍼로 생성하며 이때 정점 특성 구조체들의 배열 데이터로 위의 방식대로 생성한다.
정점버퍼들은 파이프라인의 여러 슬롯에 등록될 수 있다. 동시에 여러 슬롯을 사용하는 것은 각자 다른 정점을 나타내는 것이 아닌 각 슬롯이 동일한 정점들의 특성을 나누어 표현할 때 사용된다. 예를 들어 a,b,c,d,e 5개의 특성을 가진 정점을 사용할때 슬롯 0에 a,b를, 슬롯 1에 c,d를, 슬롯 2에 e 특성을 각 정점들의 순서에 맞게 나열하여 사용한다.
정점의 각 특성이 어떤 슬롯의 몇번째 로컬 바이트에 존재하는지는 입력 배치 서술에서 지정된다.
정점 버퍼는 커맨드 리스트의 명령어에 의해 특정 슬롯에 등록 시킬 수 있다.
색인 버퍼없이 정점 버퍼자체를 위상구조로 해석하여 파이프라인을 실행 시키고 싶다면 drawInsanced로 명령을 추가 할 수 있다.
정점 버퍼 서술자는 힙을 필요로 하지않으며 정점 버퍼의 gpu 가상 주소와 총 크기와 정점 크기만을 요구한다.
4.색인 버퍼★
directX에서 색인 데이터는 색인 버퍼로 관리한다. 색인 버퍼도 보통 디폴트 버퍼로 생성하며 이때 색인 타입(주로 int16,int32) 배열들의 데이터로 위의 방식대로 생성한다.
정점 버퍼의 정점들과 색인 버퍼의 위상구조로 파이프라인을 실행 시키고 싶다면 drawindexedinstanced로 명령을 추가 할 수 있다. 이때 색인 버퍼의 범위(위상구조 범위)와 해당 색인 버퍼들의 색인 값 0을 정점버퍼의 몇번째 정점으로 할것인지를 설정 할 수 있다. 이로인해 정점버퍼에 여러 도형을 나타내는 정점이 나란히 있을 경우 각 도형을 나타내는 색인 버퍼의 각 영역들의 색인을 도형 로컬 기준으로 설정할 수 있다.
색인 버퍼 서술자는 힙을 필요로 하지않으며 색인 버퍼의 gpu 가상 주소와 총 크기와 색인 포맷만을 요구한다.
5.정점 셰이더 작성★
정점 셰이더는 HLSL로 작성된다.
대표적인 구조는 진입점 함수, 여러 입력 버퍼 선언 등이 있다.
핵심은 입력 배치 서술에서 명시된 의미소에 따라 등록된 정점들의 특성을 가져와 적절히 사용하면된다.
그리고 출력은 다음단계의 입력에 맞게 출력 해주면 된다.
추가로 입력 배치 서술은 정점 셰이더의 요구를 무조건 충족 시켜야 하지만 정점 셰이더는 입력 배치 서술의 모든 출력을 받을 필요는 없다. 또한 각 입력과 출력의 데이터 타입이 달라도 바이트를 재해석하여 사용 할 수 있다.
6.픽셀 셰이더 작성★
픽셀 셰이더는 HLSL로 작성된다.
대표적인 구조는 진입점 함수, 여러 입력 버퍼 선언 등이 있다.
핵심은 이전 단계의 출력들의 의미소에 따라 등록된 픽셀들의 특성을 가져와 적절히 사용하면 된다.
이때 입력과 출력은 동일할 것이 요구된다.
출력으로는 해당 픽셀단편의 색상을 출력한다.
7.상수 버퍼★
directx의 셰이더들은 여러 상수버퍼의 입력을 받아 cpu와 추가적인 통신을 할 수 있다.
상수버퍼역시 cpu에서는 데이터들의 구조체 나열로 표현된다. 이후 이 바이트들을 그대로 전달하여 사용한다.(memcpu등 바이트 통 copy)
이때 대부분의 상수버퍼는 프레임마다 cpu에서 계속 수정해주어야하는 데이터이기에 업로드 버퍼로 생성한다.(파이프 라인 입장에서는 파이프라인 시작시에 입력받아 상수처럼 사용되기에 상수버퍼라 표현)
상수버퍼의 특징 중 하나는 한 버퍼의 크기는 256바이트 단위가 되어야한다는 것이다. 그렇기에 여러 상수버퍼의 배열로 사용되는 하나의 거대한 상수버퍼를 만들어 사용할때는 한 상수버퍼크기를 256바이트단위로 만들어야한다.
이렇게 업로드 버퍼로 만들어진 상수버퍼의 갱신은 업로드 버퍼의 Map 메소드를 이용하여 cpu의 한 메모리영역에 해당 버퍼메모리를 바운딩하여 갱신한다.
해당 서적에서는 업로드 버퍼 보조 클래스를 만들어 사용하는데 이는 한 cpu구조체에 해당하는 데이터를 한 상수버퍼로 하여 그러한 상수버퍼 여러개를 배열로 가지는 하나의 거대한 상수버퍼를 만들어 사용하는 클래스이다.
해당 클래스는 인덱스와 데이터로 특정 인덱스의 상수버퍼를 갱신하는 메소드를 제공한다.
상수 버퍼 서술자는 힙을 요구하기에 상수버퍼 힙을 먼저 생성해주어여한다.
상수 버퍼 서술자는 상수 버퍼 gpu 가상 주소 시작 위치와 데이터 크기를 받아 한 상수버퍼에 대한 서술자를 생성한다. 이때 여러 상수 버퍼의 배열을 가진 하나의 거대한 상수 버퍼로 부터 한 상수 버퍼의 시작위치와 크기를 넘겨 서술자를 생성해도 그 일부에 해당하는 작은 상수버퍼에 대한 서술자가 정상적으로 생성된다. 서술자는 생성될때 힙의 핸들에 등록되어 사용할 수 있게 된다.
7.루트 서명★
입력배치서술과 마찬가지로 셰이더에서 요구하는 상수버퍼들을 서술할 방법이 필요하다. 이는 루트 서명으로 서술한다.
루트 서명은 루트 매개변수의 배열이라고 볼 수 있다. 루트 매개변수는 해당 매개변수로 사용 될 상수버퍼를 서술한다. 상수버퍼의 의미소 인덱스(종류에 따라 b(index)같은 형식의 의미소로 셰이더에서 접근한다), 매개변수 종류등이 서술된다.
루트 매개변수의 종류는 3가지인데 루트 상수, 루트 서술자, 서술자 테이블이다. 여기서는 서술자테이블을 사용한다.
서술자 테이블은 한 힙에 나란히 있는 여러개의 서술자를 나타내는 매개변수이다. 해당 매개변수에 서술자 갯수를 지정하여 생성하고 이후 한 힙의 서술자 핸들을 등록하면 그 갯수만큼 연속된 서술자의 데이터들이 매개변수로 전달된다.
루트 서명은 PSO를 만들때 초기상태로 포함되며, 매 파이프라인마다 커맨드 리스트의 명령어로 재 설정하여 사용된다.
실제 자원 등록은 매 프레임 커맨드 리스트의 명령어로 특정 번째 매개변수에 특정 데이터를 등록하여 매번 재 등록하며 사용된다.
8.셰이더 컴파일★
HLSL형태로 작성된 셰이더파일은 바이트형태로 컴파일되어야한다. 이렇게 컴파일된 셰이더는 PSO에 포힘되어 파이프라인에 전달된다. 그러면 해당 파일이 그래픽 드라이버를 거쳐 그래픽 하드웨어용 명령어로 재 해석되어 사용된다.
프로그래머는 .hlsl파일을 바이트형태로 컴파일하여야하는데 이에는 두가지 방법이 있다.
- 런타임 중 특정 함수를 통해 파일을 바로 Blob(범용 바이트 버퍼)로 컴파일 하여 사용
- DirectX에 포함된 FXC라는 프로그램으로 사전에 .hlsl파일을 .cso라는 바이트파일로 컴파일 해두고 사용
2번의 경우 별도의 입출력 함수로 파일의 바이트를 읽어와서 사용하면 된다.
fxc에 /Fc옵션을 주면 어셈블리 코드로의 컴파일도 가능하다. 이를 이용하여 성능 확인이나 고찰등을 할 수 있다.
9.레스터화기 상태★
파이프라인의 단계중 레스터화 단계에 여러 설정을 주기 위해서는 해당 desc구조체를 채워야한다.
여러 고급 기법이 있지만 지금 사용하는것들은 다음과 같다.
FillMode: 픽셀 보간을 할때 soild로 할지, 와이어 프레임으로 할지 등을 설정할 수 있다.
CullMode:삼각형 선별을 끌지켤지, 후면을 선별할지, 전면을 선별할지 등을 설정할 수 있다.
FrontCounterClockWise: ccw를 전면으로 할지, cw를 전면으로 할지 설정할 수 있다.
ScissorEnable: 가위 판정 활성화 여부
해당 구조체는 PSO에 포함되어 사용된다.
9.파이프라인 상태 객체★
파이프라인의 여러 설정이나 상태들은 파이프라인 상태 객체로 서술할 수 있다.
이는 desc 구조체로 서술되어 PSO라는 객체로 생성된다.
PSO는 CommandList의 reset이나 다른 명령어들로 커맨드 리스트에 등록된다. 그렇게 PSO가 등록된 커맨드 리스트는 해당 파이프라인으로 draw등을 실행하게 된다.
PSO의 주요 요소들은 다음과 같다.
초기 루트서명
각 셰이더(컴파일된 바이트와 크기)
래스터화 상태
깊이 스텐실 상태
입력 배치 서술
기본 도형(점,선,삼각형,미지정)
렌더 타켓 수
렌더 타켓 원소 포멧 배열
깊이 스텐실 원소 포멧
다중 표본화 설정
그외 혼합 상태등의 고급 기법
10.기하구조 보조 구조체★
d3dUtill.h에는 기하구조들을 표현하는 MeshGeometry라는 구조체를 제공한다.
MeshGeometry의 한 인스턴스는 한 정점버퍼와 그에 대응되는 한 색인 버퍼로 표현되는 여러 mesh들을 표현한다.
그러한 정점버퍼와 색인버퍼의 cpu byte표현 및 gpu자원 comptr을 멤버로 가지며 여러 사이즈 정보도 가진다.
(업로드용 임시 버퍼의 comptr도 소유하여 이후 할당해제를 할 수 있도록 한다.)
메소드로는 정점 버퍼 뷰, 색인 버퍼 뷰 리턴 메소드와 업로드용 버퍼 할당 해제 메소드를 제공한다.
그리고 포함하고 있는 각 메시를 색인 범위(시작 색인, 갯수)와 기준 정점 인덱스를 가진 submeshGemetry라는 구조체로 표현하여 map<string, SubmeshGemetry>에 이들을 저장할 수 있도록 한다.
참고서적
DirectX 12를 이용한 3D 게임 프로그래밍 입문
'그래픽스 > DirectX12' 카테고리의 다른 글
[Directx12][7장]파이프라이닝2 (0) | 2023.03.21 |
---|---|
[Directx12][6장][2]파이프라이닝 구현 (0) | 2023.03.21 |
[Directx12][5장]렌더링 파이프라인 (0) | 2023.03.21 |
[Directx12][4장][2]Direct3D 기본 구조 (0) | 2023.03.21 |
[Directx12][4장][1]Direct3D 초기화 - 기본 지식 (0) | 2023.03.21 |