아래의 내용들은 DirectX 11을 이용한 3D 게임 프로그래밍 입문 책의 내용을 바탕으로 작성된 것입니다.
11장 기하 셰이더
- 기하 셰이더 프로그래밍(11.1절 453p)
- 기하 셰이더는 테셀레이션 단계를 사용하지 않는다고 할 때, 렌더링 파이프라인에서 정점 셰이더 단계와 픽셀 셰이더 단계 사이에 있는 생략 가능한 단계이다.
- 기하 셰이더 프로그래밍은 정점 셰이더나 픽셀 셰이더 프로그래밍과 상당히 유사하나 몇 가지 차이점이 있다.
- 기하 셰이더 프로그램을 작성할 때에는 우선 기하 셰이더의 한 실행(호출)에서 출력할 정점들의 최대 개수를 지정해야 한다.(기하 셰이더는 기본도형마다 실행된다.)
=> 기하 셰이더 주 함수 이전에 최대 정점 개수 특성(attribute)을 지정해 주어야 한다. [maxvertexcount (N)]
- 기하 셰이더가 실행 당 출력할 수 있는 정점의 개수는 가변적이나, 여기서 정의된 최대 개수를 넘지는 못한다.
=> 성능을 위해서는 maxvertexcount 특서으이 값을 최소한으로 잡는 것이 좋다.
- 기하 셰이더는 두 종류의 매개변수를 받는다.
1. 입력 매개변수
2. 출력 매개변수
※ 그 외 매개변수도 있긴 하지만 이는 특별한 주제임. 뒤에 언급할 것
- 입력 매개변수는 항상 기본도형을 정의하는 정점들의 배열로, 기본도형이 점이면 정점이 하나, 선(선분)이면 두 개, 삼각형이면 세 개, 인접성 정보를 가진 선이면 네 개, 인접성 정보를 가진 삼각형이면 여섯 개이다.
=> 입력 정점들의 정점 형식은 정점 셰이더가 출력한 정점의 형식(ex: Vertexout)이다.
- 입력 매개변수 앞에는 반드시 기하 셰이더가 받는 기본도형의 종류를 뜻하는 키워드를 붙여주어야 한다.(454p 참고)
- 기하 셰이더의 입력 기본도형은 항상 온전한 기본도형이다.
=> 목록과 띠(strip)를 구분할 필요가 없다.
==> 여러 기본도형이 공유하는 정점들을 기하 셰이더에서 여러 번 처리해야 하는 상황이 발생해서 추가부담이 발생할 수 있다.
- 출력 매개변수 앞에는 항상 inout 수정자가 붙으며, 출력 매개변수는 항상 스트림 형식이다.
=> 스트림 형식은 기하 셰이더가 출력하는 기본도형을 정의하는 정점들의 목록을 담는다.
=> 기하 셰이더에서 출력 스트림 목록에 정점을 추가할 때에는 내장 Append 메서드를 사용한다.
- 스트림 형식은 템플릿 형식으로, 템플릿 인수는 출력 정점의 정점 형식(ex: GeoOut)을 지정한다. 사용 가능한 스트림 형식은 세 가지이다.(454p 참고)
- 기하 셰이더가 출력하는 정점들은 기본도형들을 형성한다.
=> 기본도형들의 종류는 스트림 형식에 의해 결정된다.
==> 선과 삼각형의 경우 출력 기본도형은 항상 띠이다.
==> RestartStrip 메서드를 이용하면 선 목록이나 삼각형 목록을 흉내 낼 수 있다.
- 기하 셰이더가 주어진 입력 기본도형으로부터 그 어떤 기본도형도 출력하지 않을 수 있다.
=> 일부 알고리즘에서는 이처럼 주어진 입력 기본도형을 기하 셰이더가 '파괴'하는 능력이 유용하게 쓰인다.
- 기하 셰이더가 하나의 기본도형을 완성하기에는 모자란 개수의 정점을 출력한 경우 파이프라인은 미완성된 기본도형을 폐기한다.
- 나무 빌보드 예제(11.2절) - 개요(11.2.1절 459p)
- 멀리 있는 나무를 묘사할 때에는 빌보드(bilboard) 기법이 효율적이다.
=> 이 기법에서는 나무를 나타내는 완전한 3차원 기하구조를 렌더링하는 대신, 3차원 나무의 모습을 담은 2차원 텍스처를 하나의 사각형('빌보드')에 입힌다.
==> 멀리 있는 나무의 경우 그것이 빌보드인지 구분하기 어렵다.
- 빌보드 기법이 효과를 보려면 해당 빌보드가 항상 카메라를 향하게 해야 한다.
- 세계 공간에서의 빌보드 중심 위치, 카메라의 위치만 알면 세계 공간을 기준으로 한 빌보드의 국소 좌표계를 구할 수 있다.
=> 세계 공간 기준 빌보드의 국소 좌표계와 빌보드 크기(너비, 높이)를 알면 빌보드 사각형의 정점들을 구할 수 있다.
- 빌보드를 CPU에서 구현할 때에는 일반적으로 빌보드 하나 당 네 개의 정점을 동적 버퍼에 담아 두고, 카메라가 움직일 때마다 CPU에서 ID3D11DeviceContext::Map 메서드를 이용해서 해당 정점들을 갱신한다.
=> 이 접근방식에서는 빌보드 당 네 개의 정점을 입력 조립기 단계에 제출해야 하며 동적 정점 버퍼를 갱신해야 하므로 성능 상에 부담이 생긴다.
=> 반면 기하 셰이더를 이용하는 접근방식에서는 빌보드 사각형을 만들고 카메라로 향하게 하는 작업을 기하 셰이더에서 수행하므로 정적 정점 버퍼를 사용해도 된다.
==> 또한, 빌보드 하나 당 하나만의 정점만 입력 조립기 단계에 제출하면 되므로 빌보드의 메모리 요구량도 작다.
- 나무 빌보드 예제(11.2절) - 정점 구조체(11.2.2절 461p)
- 정점 구조체는 세계 공간에서의 빌보드 중심 위치를 나타내는 점의 좌표, 빌보드의 크기(세계 공간 단위로 비례된 사각형의 너비와 높이)를 담는다.
- 기하셰이더는 주어진 좌표의 점을 주어진 크기만큼의 사각형으로 확장한다.
=> 정점마다 크기 정보가 따로 존재하므로 한 장면에서 다양한 크기의 빌보드를 만들 수 있다.
- 나무 빌보드 예제(11.2절) - 효과 파일(11.2.3절 462p)
- 위에서 얘기했던대로 기하 셰이더는 주어진 점을 빌보드 사각형, 즉 세계 공간의 y축에 정렬된 카메라 방향 사각형으로 확장된다.
- 나무 빌보드 예제(11.2절) - SV_PrimitiveID(11.2.4절 468p)
- SV_PrimtivelD 의미소를 지정하면 입력 조립기 단계는 각 기본도형마다 자동으로 기본도형 ID(식별 번호)를 생성한다.
=> 한 번의 그리기 호출로 n개의 기본도형을 그린다고 할 때, 첫째 기본도형은 0번, 둘째 기본도형은 1번, ..., 마지막 기본도형은 n-1번이 된다.
=> 기본도형 ID들은 주어진 한 번의 그리기 호출에 대해서만 유효하다.
- 나무 빌보드 예제의 경우 기본도형 ID는 기하 셰이더에서 쓰이는 것이 아니다.
=> 원한다면 기하 셰이더에서 사용할 수도 있지만 여기서는 사용하지 않음.
=> 기하 셰이더는 그냥 기본도형 ID를 출력 정점 구조체에 기록해서 픽셀 셰이더에 넘겨주기만 한다.
=> 픽셀 셰이더는 기본도형 ID를 이용해서 텍스처 배열의 특정 텍스처를 참조한다.
- 기하 셰이더 단계를 사용하지 않는 경우에도 픽셀 셰이더가 기본도형 ID를 입력받을 수 있다.
=> 기본도형 ID 매개변수를 픽셀 셰이더의 매개변수 목록에 추가하면 된다.
ex) float4 PS(VertexOut pin, uint primID : SV_PrimitiveID) : SV_Target { ... }
- 기하 셰이더 단계를 사용하는 경우 기본도형 ID 매개변수가 반드시 기하 셰이더의 서명에 존재해야 한다.
=> 기하 셰이더는 ID를 자신이 직접 사용하거나, 픽셀 셰이더 단계에 넘겨주면 된다. (둘 다도 가능)
- 입력 조립기가 정점 ID를 생성하게 하는 것도 가능하다.
ex) VertexOut VS(VertexIn vin, uint vertID : SV_VertexID) { ... }
=> Draw를 호출하는 경우 해당 정점들에 차례로 0, 1, ..., n-1 번이 부여된다.
=> 여기서 n은 해당 그리기 호출의 정점 개수이다.
=> DrawIndexed의 경우에는 각 정점에 해당 정점 색인 값과 동일한 식별 번호가 부여된다.
- 텍스처 배열(11.3절) - 개요(11.3.1절, 469p)
- 텍스처 배열(texture array)은 말 그대로 텍스처들의 배열을 담는 자원이다.
=> C++ 코드에서 텍스처 배열은 ID3D11Texture2D 인터페이스로 대표된다. (한 장짜리 텍스처에 쓰이는 것과 같은 인터페이스이다.)
- 효과 파일에서는 텍스처 배열을 Texture2DArray 형식으로 나타낸다.
- 텍스처 배열(11.3절) - 텍스처 배열의 표본 추출(11.3.2절 470p)
- 2차원 텍스처 배열의 한 표본을 추출하려면 세 가지 좌표성분이 필요하다.
=> 처음 둘은 통상적인 2차원 텍스처 좌표
=> 셋째 것은 텍스처 배열의 한 텍스처를 선택하는 색인이다.
ex) 0은 배열의 첫 텍스처, 1은 둘째 텍스처 등
- 나무 빌보드 예제는 텍스처 원소가 네 개인 텍스처 배열 하나를 사용한다.
=> 텍스처 원소들은 각각 다른 나무 텍스처들이다.
=> 한 번의 그리기 호출에서 나무 빌보드를 네 개 이상 그리기 때문에, 기본도형 ID가 3보다 커질 수 있다.
==> 기본도형 ID를 4로 나눈 나머지(pin.PrimID % 4)를 텍스처 배열 색인으로 사용한다.
- 텍스처 배열은 한 번의 그리기 호출에서 여러 종류의 기본도형들에 대해 서로 다른 텍스처를 입힐 수 있다는 장점이 있다.
=> 각각의 텍스처 설정 및 그리기 호출에는 일정한 추가부담이 따른다.
==> 텍스처 배열을 사용하지 않으면 각각의 텍스처에 따라 전부 설정 및 그리기 호출이 필요했을 것.
==> 텍스처 배열을 이용하면 이를 한 번의 설정 및 그리기 호출로 끝낼 수 있다.
- 텍스처 배열(11.3절) - 텍스처 배열의 적재(11.3.3절 471p)
- 텍스처 배열 적재 과정 요약
1. 시스템 메모리 안에서 각각의 텍스처를 파일로부터 생성한다.
2. 텍스처 배열을 생성한다.
3. 각 텍스처를 텍스처 배열의 각 원소에 복사한다.
4. 텍스처 배열에 대한 셰이더 자원 뷰를 생성한다.
=> d3dUtil.h/.cpp에는 파일 이름 목록을 받아서 텍스처 배열을 생성해주는 보조 함수가 구현되어 있다. ( 텍스처들의 크기가 모두 같아야 한다. )
- 텍스처 배열(11.3절) - 텍스처 부분자원(11.3.4절 475p)
- D3D API에서는 텍스처 배열의 각 원소, 즉 완전한 밉맵 사슬을 가진 텍스처 한 장을 배열 조각(array slice)이라 부른다.
- 텍스처 배열의 모든 텍스처의 밉맵 사슬들 중 특정 수준의 밉맵들 전체를 밉 조각(mip slice)라고 부른다.
=> 이번 절에서 말하는 부분자원은 하나의 텍스처 배열 원소의 특정한 하나의 밉맵 수준이다.
- 텍스처 배열의 어떤 한 부분자원에 접근하려면 해당 텍스처 배열 색인과 밉맵 수준을 알아야 한다.
=> 부분자원을 하나의 선형적인(1차원) 색인으로 식별하는 것도 가능하다.(476p)
- D3D11CalcSubresource(UINT MipSlice, UINT, ArraySlice, UINT MipLevels);
=> 밉맵 수준과 배열 색인, 밉맵 수준 개수로부터 선형 부분자원 색인을 계산해주는 보조 함수
=> 공식 k = ArraySlice * MipLevels + MipSlice
- 알파-포괄도 변환(11.4절 476p)
- 특정 거리에서 예제의 빌보드에 표시된 나무의 외곽선에 계단 현상이 두드러지게 나타난다.
=> 나무의 일부가 아닌 픽셀을 잘라내기 위해 사용한 clip 함수 때문
==> clip함수는 주어진 픽셀 전체를 허용하거나 기각할 뿐이므로, 그 사이의 어떤 매끄러운 전이를 표현하지 못한다.
- 해당 문제를 해결하는 방법은 알파 판정 대신 투명도 혼합을 사용하는 것이다.
=> 선형 텍스처 필터링 덕분에 가장자리 픽셀이 약간 흐려져서 흰색(불투명 픽셀)과 검은색(폐기되는 픽셀) 사이의 매끄러운 전이가 일어난다.
==> 나무 외곽선이 주변 장면에 매끄럽게 녹아들어서 계단 현상이 완화된다.
- 단, 투명도 혼합을 위해서는 나무 빌보드들을 거리에 따라 정렬해서 뒤에서 앞 순서대로 렌더링 해야한다.
=> 숲이나 초원을 렌더링 한다면 매 프레임마다 많은 수의 빌보드를 정렬해야 하므로 비용이 커진다.
- MSAA에서는 포괄도(부분픽셀이 다각형 안쪽에 있는지 바깥쪽에 있는지의 여부)가 다각형 수준에서 결정된다.
=> MSAA는 알파 채널에 의해 결정되는 나무 이미지의 가장자리를 검출하지 못한다.
- Direct3D가 포괄도 계산 시 알파 채널을 고려하게 만들면 문제가 해결된다. 알파-포괄도 변환(alpha-to-coverage)이라는 기법을 사용하면 된다.
- 군엽이나 울타리 같이 알파 채널로 형태를 잘라내는 텍스처에 대해서는 항상 알파-포괄도 변환을 사용하는 것이 바람직하다.
=> 물론 이를 위해서는 MSAA를 활성화해야한다.
틀린 부분이나 이상한 부분이 있으면 댓글로 편하게 지적해주세요.
감사합니다!
'DirectX11 > 정보정리' 카테고리의 다른 글
[DirectX11] 스텐실 적용 (0) | 2023.01.07 |
---|---|
[DirectX11] 혼합 (0) | 2023.01.05 |
[DirectX11] 텍스처 적용 (2) | 2022.11.29 |
[DirectX11] 조명 (0) | 2022.11.23 |
[DirectX11] 효과 프레임워크 (0) | 2022.11.08 |
댓글