기존의 텍스처 매핑 방법은 정점 자료에 법선 정보를 넣어 삼각형 내에서 3개 정점에 대한 법선을 보간하는 방법으로 법선을 지정했는데, 법선 매핑을 사용하면 더 세밀하게 법선을 지정할 수 있다고 한다.

법선을 높은 해상도(더 세밀한 방법)에서 지정하면 조명의 세부도가 높아지지만, 메시 기하구조 자체의 세부도까지 높아지지는 않는다.

 

법선 맵을 만드는 도구

1. NVIDIA의 Photoshop용 법선 맵 필터

2. CrazyBump

19.2 법선 맵

법선 맵은 하나의 텍스처이다. RGB 를 8비트씩 24비트에 저장하는 텍스처라고 가정하면 RGB에 x y z (법선 벡터) 를 넣는다. 대체로 법선 벡터는 z축과 유사한 방향이기 때문에 z 값이 x y 값보다 높아 법선 맵 텍스처를 색상 이미지로 보게 되면 푸르스름한 모습이 된다. 

 

법선 맵을 압축 텍스처 형식에 저장하고 싶다면 BC7(DXGI_FORMAT_BC7_UNORM) (압축된 텍스처는 렌더링 파이프라인의 셰이더 단계에 대한 입력으로만 사용할 수 있다.)을 사용하는 것이 최상의 품질을 보인다.

 

BC7로 압축된 텍스처맵 gNormalMap을 변환하는 과정

float normalT = gNormalMap.Sample( gTriLinearSam, pin.Tex ); // [0, 255] -> [0, 1]

normalT = 2.0f * normalT - 1.0f // [0, 1] -> [-1, 1] 

 

19.3 텍스처 공간 또는 접공간

ㄴ 삼각형이 있는 평면의 u, v를 기저로 만들어지는 공간

 

텍스처 왜곡이 없다고 가정 (이동과 회전만 있는 강체 변환)

조명 공식을 계산하려면 법선 벡터와 빛이 같은 공간에 있어야 하므로 

 

접벡터 T(tangent) : 텍스처 공간의 u벡터를 물체 공간 기준 벡터로 변환한 벡터

종법선 B(bitangent) : 텍스처 공간의 v벡터 물체 공간 기준 벡터로 변환한 벡터

법선 벡터 N(normal) : T와 B의 외적

 

삼각형의 3개 정점의 u, v 좌표와 물체 공간 기준 좌표( posL )를 사용하여 T벡터와 B벡터를 얻을 수 있다.

ㄴ 삼각형별 접공간을 유도할 수 있다. (= T, B, N을 구할 수 있다.)

 

19.4 정점 접공간

삼각형별 접공간을 사용하여 법선 매핑을 하면 삼각형 내부의 3개 점이 같은 법선을 사용하기 때문에 조명 계산이 삼각형 단위로 (삼각형 내부는 같은 색) 진행된다.

 

정점의 접공간을 계산하는 방법은 정점을 공유하는 메시의 모든 삼각형의 접벡터와 종법선의 평균을 사용하여 정점의 접벡터(T)와 종법선(B)을 구하는 것이다.

 

평균을 내서 구한 TBN 기저벡터는 이후 변환 계산을 위해 정규 직교화하여 정점 자료에 저장한다. 보통 그람-슈미트 정규직교화 방법을 사용한다.

 

19.6 법선 매핑 셰이더 코드

법선 맵의 법선 벡터(접공간)에 TBN 기저(세계 변환과 보간이 일어난 접벡터와 법선 벡터로 만든) 를 이용한 변환 행렬을 곱하여 Bumped normal을 얻는다. 이 법선은 이후 법선 벡터가 관여하는 모든 계산 (조명, 입방체 매핑)에 쓰인다.

 

정점 셰이더에서 법선 벡터와 접벡터에 대한 세계변환이 일어난다.

래스터화 단계에서 특성에 대한 보간이 일어난 뒤 픽셀 셰이더에 전달된다.

픽셀 셰이더에서는 법선 맵에서 추출한 표본을 픽셀 셰이더의 입력으로 받은 법선 벡터와 접벡터를 사용하여 세계 좌표계로 변환한다.

 

normal 맵의 알파 채널에는 픽셀 수준의 광택 계수가 들어 있다.

연습문제

2.

Crazubump 프로그램으로 생성한 normal map
예제 프로그램 타일에 텍스처를 적용한 모습

 

 

연습문제 파일

https://github.com/lemonyun/Directx12_study/tree/main/19

 

GitHub - lemonyun/Directx12_study: 2022/06/10

2022/06/10. Contribute to lemonyun/Directx12_study development by creating an account on GitHub.

github.com

 

 

'읽은 책 > DirectX 12를 이용한 3D 게임 프로그래밍 입문' 카테고리의 다른 글

21. 주변광 차폐  (0) 2022.07.10
20. 그림자 매핑  (0) 2022.07.08
18. 입방체 매핑  (0) 2022.07.02
17. 3차원 물체의 선택  (0) 2022.07.01
16. 인스턴싱과 절두체 선별  (0) 2022.06.30

 

입방체 맵(cube map)은 여섯장의 텍스처로 된 배열이다.

18.1 입방체 매핑

0 ~ 5번 인덱스가 면 +X -X +Y -Y +Z -Z 순서대로 대응된다.

 

2차원 텍스처는 (u, v)로 지정했는데 3차원 텍스처는 조회벡터를 사용한다.

조회벡터는 원점에서 조회벡터 방향으로 나아가는 반직선이 입방체의 한 면과 교차하는 지점이다.

 

18.2 환경 매핑

수평 수직 시야각이 90도인 카메라를 장면의 한 물체의 중심에 두고 6방향으로 각각 장면을 렌더링해서 입방체 맵에 담는다. -> 반영 반사를 묘사할 수 있다.

 

Terragen 이라는 프로그램을 사용하면 3차원 세계에서 장면을 만들고, 그 장면을 미리 여섯 개의 입방체 면 이미지들로 렌더링 할 수 있다.

 

DirectX의 Texassemble 도구를 사용하면 이미지 6장으로 입방체 맵 DDS를 만들 수 있다.

 

18.2.1 입방체 맵의 적재와 적용

SRV 만들 때 ViewDimension = D3D12_SRV_DIMENSION_TEXTURECUBE; 으로 설정해줘야 한다. 나머지는 일반 2D 텍스처 방식과 동일하다.

 

18.3 하늘에 텍스처 입히기

하늘 구체의 중심을 카메라 위치에 두면 된다.

하늘 구체를 위한 셰이더 함수는 일반 물체를 위한 셰이더 함수와 다르다.

1. 구를 구성하는 정점을 벡터값으로 사용한다.

2. world space에서 동차 좌표계로 변환한 이후 z값을 w값으로 설정한다. (하늘 구가 항상 먼 평면에 있도록)

3. 픽셀 셰이더에서 2차원 텍스처 좌표가 아닌 조회벡터를 사용하여 샘플을 추출한다.

4. 렌더링 설정 - 후면 선별을 비활성화 해야 한다. 

5. 깊이 비교 함수를 LESS_EQUAL로 설정해야 한다. (깊이 버퍼는 1로 초기화 되므로)

 

18.4 물체의 주변 환경 반사

환경 맵을 이용해서 반영 반사를 묘사하는 방법

환경 맵의 모든 텍셀을 각각 하나의 광원으로 간주한다.

반사 벡터만으로 반영 반사를 계산하는 것은 평평한 표면에서는 동작하지 않는다. (작은 공간에 대한 환경 맵의 경우)

해결 방법

환경 맵과 같은 크기의 축 정렬 경계상자 정보를 셰이더로 보낸다.(상수 버퍼로)

world에서 실제로 빛의 반직선을 계산하여 반직선과 경계상자의 교점을 구하여 원점에서 표면의 정점까지의 벡터와 표면의 정점에서 경계상자의 교점 벡터를 더한 벡터값을 조회벡터로 사용한다.

 

18.5 동적 입방체 맵

동적 입방체 맵을 생성하려는 물체의 중심에 카메라를 놓고 6개의 좌표축 방향으로 매 프레임마다 장면을 입방체 맵의 각 면에 렌더링한다.

 

입방체 맵을 동적으로 렌더링 하는 것은 비용이 크다. 가급적 적게 사용하는 것이 좋다.

동적 입방체 매핑에서는 픽셀 처리량을 줄이기 위해 보통 256x256 같은 저해상도 입방체 맵을 사용한다.

 

1. 입방체 맵 자원은 자원 생성시 D3D12_RESOURCE_FLAT_ALLOW_RENDER_TARGET 플래그를 지정해야 한다.

2. 입방체 맵 자원은 자원 생성시 DepthOrArraySize 속성을 6으로 설정해야 한다.

3. RTV 힙의 크기를 6개 더 늘려야 한다.

4. RTV 서술자의 ViewDimension 속성을 D3D12_RTV_DIMENSION_TEXTURE2DARRAY로 설정한다.

5. DSV 힙의 크기를 1개 더 늘려야 한다. (256x256 크기)

6. 생성한 입방체 맵을 셰이더 입력으로 묶어야 하므로 SRV도 하나 더 필요하다.

7. SRV 서술자의 ViewDimension 속성을 D3D12_SRV_DIMENSION_TEXTURECUBE로 설정한다.

8. 256x256 크기의 viewport와 ScissorRect 정의가 필요하다.

9. 카메라 객체를 6개 만든다. 

10. 프레임 자원당 패스별 상수 버퍼를 6개 더 만든다.

 

연습문제

1. 

구체의 FresnelR0 값을 (1, 0 0) 에 가깝게 줬을 때 모습
거칠기 계수를 늘리면 반사된 모습이 흐려진다. (좌 : 0.1 우: 0.4)

2.

texassemble 도구를 사용하여 cubemap.dds를 만들어 냄
입방체 맵을 적용한 결과
입방체 맵을 만들기 위해 사용한 이미지 파일

3.

굴절 벡터를 계산해 주는 hlsl의 refract함수를 사용한다. 세번째 매개변수는 두 매질의 굴절률의 비율(eta)을 넣는다.

반사 벡터 대신에 이 굴절 벡터를 텍스처 조회 벡터로 사용한다.

 

왼쪽부터  순서대로 eta값 1.0, 0.95, 0.9 

연습문제 파일

https://github.com/lemonyun/Directx12_study/tree/main/18

 

GitHub - lemonyun/Directx12_study: 2022/06/10

2022/06/10. Contribute to lemonyun/Directx12_study development by creating an account on GitHub.

github.com

 

화면에 한 점을 클릭하면 시야 절두체 가까운 면의 정점 하나가 대응되고, 시점 위치와 그 정점을 잇는 반직선을 계산한 뒤에 각 물체와 반직선을 교차 판정하고, 여러 개의 물체가 반직선과 겹친다면 가장 가까운 물체를 선택하면 된다.

 

17.1 화면에서 투영 창으로의 변환

뷰포트 변환은 래스터화 단계에서 일어난다.

원점 (0, 0, 0)에서 시야 공간의 평면 z = 1에 투영된 정점으로 나아가는 반직선을 계산 할 수 있다.

 

17.2 세계 공간과 국소 공간의 선택 반직선

17.3 반직선 대 메시 교차 판정

1. 반직선이 메시의 경계 상자와 교차하는지 먼저 확인한다.

BoundingBox와 BoundingSphere 구조체에 Intersects 함수를 사용한다.

ㄴ 교차 여부를 반환하고, 출력 매개변수로 반직선의 원점에서 교차 지점까지의 거리를 반환한다.

 

2. 교차하는 경우에는 반직선과 메시 내의 삼각형 교차 판정을 진행한다.

ㄴTriangleTests::Intersects 함수를 사용한다.

ㄴ 메시에 존재하는 모든 정점이 이루는 삼각형에 교차 판정을 수행해가며 가장 짧은 거리를 반환하는 삼각형 정보를 저장한다.

 

17.4 선택 예제 프로그램

물체의 RenderItem과 하이라이팅 RenderItem이 따로 존재한다.

두 RenderItem은 같은 기하구조 (정점 리스트)를 사용하는데  하이라이팅 RenderItem의 indexcount는 3이다 (삼각형 하나만 그린다)

하이라이팅 RenderItem의 StartIndexLocation은 RenderItem초기화 시에 결정 되는 것이 아니라 커서 우클릭 위치(반직선)와 물체 renderitem의 boundingbox가 교차하여 RenderItem을 구성하는 정점들이 이루는 모든 삼각형과 교차 판정을 진행하다가 교차하는 경우에 결정된다. (교차점과의 거리가 더 짧은 삼각형이 있으면 새로 갱신)

 

RenderItem에 Visible 속성이 추가 되었는데 이는 하이라이팅 renderitem만을 위한 속성이다. 물체 renderitem에는 속성을 지정할 필요가 없다. 

 

반직선이 boundingbox와는 교차하는데 모든 삼각형과 교차하지 않는 경우는 하이라이팅 renderitem의 Visible 속성을 true로 갱신하지 않는다.

 

RenderItem들에 대한 DrawIndexedInstance 함수를 호출하기 전에 Visible인지 확인하는데 Visible이 false이면 함수가 호출되지 않는다.(물체 RenderItem의 Visible 속성은 항상 true)

 

연습문제 

1.

BoundingBox를 BoundingSphere로 바꿈

bounds.Radius = XMVectorGetX(XMVector3Length(0.5f*(vMax - vMin))); // BoudingSphere 구조체에는 반지름을 채워줘야 함

 

3. 

8진 트리는 3차원 공간을 재귀적으로 분할하는데 사용된다. 

트리의 노드 한개는 BoundingBox를 가진다.

 

노드 한개에 포함된 객체(물체)는 BoundingBox 안에 완전히 있어야 한다. (가정)

ㄴ 느슨한 옥트리 ( 노드의 경계 입체 자체를 조정 )

 

노드를 적절한 수로 분할한다면 여러개의 물체의 BoundingBox가 한 개의 노드에 들어가게 된다.

선택 함수를 수행할 때 모든 물체의 BoudingBox와 반직선 간의 교차 검증을 하지 않고 모든 노드들의 BoundingBox와 교차 검증을 먼저 실행하면 판정 횟수가 줄어들 여지가 있다.

 

연습문제 파일

https://github.com/lemonyun/Directx12_study/tree/main/17

 

GitHub - lemonyun/Directx12_study: 2022/06/10

2022/06/10. Contribute to lemonyun/Directx12_study development by creating an account on GitHub.

github.com

 

 

 

 

인스턴싱 : 한 장면에서 같은 물체를 여러 번 그리는 것, 성능을 크게 향상할 수 있다.

절두체 선별 : 시야 절두체 바깥에 있는 일단의 삼각형들을 간단한 판정으로 골라내서 기각하는 기법

 

16.1 하드웨어 인스턴싱

한 장면에서 같은 물체를 여러번 그리되 위치나 방향, 축적, 재질, 텍스처 등을 각자 다르게 해서 한 번에 그리는 것

 

DrawIndexedInstanced 함수의 두번째 매개변수로 원래 1을 사용했었는데 여기에 10을 지정하면 인스턴스를 10번 그린다는 뜻이 된다.

 

인스턴스별로 다른 자료를 가져야 겹쳐 그려지지 않기 때문에, 인스턴스 버퍼를 파이프라인에 묶어야 한다.

셰이더에서 SV_InstanceID라는 시스템 값 의미소로 인스턴스 색인을 알 수 있다.

이 방식을 사용하면 물체별 상수 버퍼를 사용하지 않아도 된다. 인스턴스 버퍼에 넣으면 되기 때문이다.

 

정점 자료(VertexOut)에 재질 색인(인스턴스 버퍼에서 얻어온)을 기록하게 되는데, 정점 자료가 래스터화 단계에서 보간될 때 색인 값이 보간되면 안되기 때문에 색인 특성에 nointerpolation을 지정해야 한다.

 

인스턴스 버퍼는 렌더 항목마다 존재 해야 하며 FrameResource 클래스에서 렌더 항목별로 UploadBuffer로 관리한다.(버퍼를 매 프레임 갱신하기 위해서) (상수 버퍼로 사용할 용도는 아니라서 버퍼 크기를 256 배수에 맞출 필요는 없음)

 

16.2 경계입체와 절두체

DirectXMath의 DirectXCollision.h에 있는 충돌 라이브러리를 사용한다.

 

16.2.2 경계상자

BoundingBox 구조체로 표현

AABB(axis-aligned bounding box: 축 정렬 경계상자)

1. vmin과 vmax로 표현 가능

2. 중심 c와 한계벡터 e로 표현 가능

 

BoundingOrientedBox 구조체로 표현

OBB (oriented bounding box: 유향 경계상자)

ㄴ 메시의 국소 공간에서 계산한 메시의 AABB를 세계 공간으로 변환했을 때 생기는 특정 방향을 가리키는 경계 상자

 

16.2.3 경계구

BoundingSphere 구조체로 표현

중심은 AABB의 중심을 쓰고 반지름은 중심과 임의의 정점 사이의 최대 거리를 사용함

16.2.4 절두체

절두체는 DirectXMath 라이브러리의 BoundingFrustum 구조체로 서술된다.

절두체는 6개의 평면으로 되어있지만, 시야 공간에서 절두체 평면을 정의하는 것은 복잡하지 않다

 

절두체 구조체의 멤버

원점, 회전 사원수

양의 X 기울기, 음의 X 기울기, 양의 Y 기울기, 음의 Y 기울기

가까운 평면 거리, 먼 평면 거리

 

NDC 공간의 꼭짓점을 투영 행렬의 역행렬로 변환하면 시야 공간에서의 꼭짓점이 나온다.

이 변환된 꼭짓점들로 

 

16.2.4.2 절두체 대 구 교차 판정

절두체의 6평면에 대해 구가 평면의 음의 반공간에 있으면 절두체에서 벗어나 있다는 뜻이다.

구의 중심 좌표 c와 법선 벡터 n을 내적한 값이 -r보다 크면 양의 공간 (절두체 내부) 에 있는 것이고

-r 보다 작으면 절두체와 교차하지 않는 것이다.

16.2.4.3 절두체 대 AABB 교차 판정

AABB와 절두체가 같은 좌표계에 있어야 판정을 할 수 있다.

법선 벡터와 가장 정렬되는 대각선 벡터를 이루는 꼭짓점 P와 Q를 구한 뒤 판정에 사용한다.

 

16.3 절두체 선별

절단 단계는 렌더링 파이프라인 단계에서 후반부에 있기 때문에 절단 되기 전까지 시야 밖에 있는 기하구조들에 대해서도 셰이더 함수가 실행되므로 미리 CPU에서 기하구조를 선별하는 것이 좋다.

 

교차 판정은 좌표계가 같아야 하므로 시야 절두체를 각 인스턴스의 local space로 변환하여 비교하는 방식과 AABB를 world space로 변환하고 시야 절두체도 world space로 변환해서 교차 판정을 수행할 수도 있다.

 

좌표계 변환을 위한 함수는 BoundingFrustum 구조체의 Transform함수를 사용하고 입력 매개변수로 변환 행렬을 넣으면 출력 매개변수로 변환된 BoundingFrustum을 보낸다.

 

인스턴스 버퍼를 update 할 때 각 인스턴스가 절두체 내에 있는 경우에만 버퍼에 인스턴스 자료를 담는다.

절두체 내에 있는 인스턴스 개수를 세어서 renderitem에 저장해두고 DrawIndexedInstanced 함수를 호출할 때 입력으로 넣어주면 실제로 보이는 인스턴스들만 렌더링 파이프라인으로 들어간다.

 

 

동적 색인화는 Direct3D 12에만 존재하는 새로운 기법이다.

Texture2DArray와는 달리 크기와 형식이 서로 다른 텍스처들을 담을 수 있다.

 

15.2 Camera 클래스

right 벡터, lookat 벡터, up 벡터

카메라 위치 벡터

시야 행렬, 투영 행렬

수직 시야각, 종횡비, 가까운 평면 거리, 먼 평면 거리

ㄴ 멤버 변수로 모두 저장하고 있음

 

setLens() // 투영 행렬 갱신

UpdateViewMatrix() // 시야 행렬 갱신

ㄴ 멤버 변수 값들을 사용하여 행렬 변수에 계산된 값을 저장

 

함수 이름

yaw = 좌우 둘러보기 (y축 회전)pitch = 위아래 끄덕 (카메라 right축 회전)roll = 갸웃갸웃 (카메라 lookat축 회전)

 

15.5 동적 색인화

1. 상수 버퍼의 한 요소를 색인으로 사용할 수 있다.

2. SV_PrimitiveID와 같은 시스템 ID를 색인으로 사용할 수 있다.

3. 셰이더 프로그램 안에서 수행한 어떤 계산의 결과를 색인으로 사용할 수 있다.

4. 텍스처에서 추출한 표본의 값을 색인으로 사용할 수 있다.

5. 정점 구조체의 한 성분을 색인으로 사용할 수 있다.

 

예제에서의 사용법

모든 재질 자료를 담은 재질 구조체의 배열 (구조적 버퍼 자원) 

텍스처 배열 (서술자 테이블)

객체 별 상수 버퍼에 재질 인덱스 정보만 넘기면 셰이더 파일에서 텍스처와 재질에 접근할 수 있다. (1번 사용법) 

 

연습문제

2.

Q와 E키로 회전하도록 만들었다.

mLook ( 카메라 기준 Z 축 )에 대한 회전행렬을 만들고

mRight 벡터와 mUp 벡터에 회전 행렬을 곱해 값을 갱신한다.

연습문제 파일

https://github.com/lemonyun/Directx12_study/tree/main/15

 

GitHub - lemonyun/Directx12_study: 2022/06/10

2022/06/10. Contribute to lemonyun/Directx12_study development by creating an account on GitHub.

github.com

 

 

테셀레이션을 사용하는 이유

1. GPU상의 동적 LOD

 

2. 효율적인 물리 및 애니메이션 계산

ㄴ 물리와 애니메이션을 저다각형 메시에 대해 수행하고, 그 저다각형 메시를 테셀레이션해서 고다각형 버전을 만들면 물리 및 애니메이션 계산을 낮은 세부도에서 수행함으로써 계산량을 줄일 수 있다.

 

3. 메모리 절약

ㄴ 메모리에는 저다각형 버전을 담아두고 GPU 테셀레이션으로 즉석에서 고다각형 버전을 생성하는 방식을 사용하면 메모리를 절약할 수 있다.

 

테셀레이션 단계들은 정점 셰이더 단계와 기하 셰이더 단계 사이에 위치할 수 있으며, 생략 가능하다.

1. 덮개 셰이더 단계

2. 테셀레이터 단계

3. 영역 셰이더 단계

 

14.1 테셀레이션 기본도형 위상구조

IASetPrimitiveTopology 메서드로 삼각형이 아닌 제어점들로 이루어진 패치를 입력으로 설정

D3D_PRIMITIVE_TOPOLOGY_N_CONTROL_POINT_PATCHLIST (N은 1부터 32 까지 있음)

제어점이 5개 이상인 패치는 보통 곡면을 구현하기 위해 사용된다.

 

14.2 덮개 셰이더

두 종류의 셰이더로 구성됨

1. 상수 덮개 셰이더

struct PatchTess
{
    float EdgeTess[4] :  SV_TessFactor;
    float InsideTess[2] : SV_InsideTessFactor;
}

사각형 패치에 대한 테셀레이션의 경우 6개의 테셀레이션 개수를 지정할 수 있다.

변 테셀레이션 계수 4개와 내부 테셀레이션 계수 2개

 

모든 테셀레이션 계수가 0이면 그 패치는 폐기된다.

 

테셀레이션 정도를 결정하는데 흔히 쓰이는 측정치

1. 카메라와의 거리

2. 화면 영역 포괄도 (물체가 화면의 픽셀을 몇개나 덮는지)

3. 방향 (시점에 따라 물체의 윤곽선으로 보이는 삼각형들은 다른 삼각형들보다 세분하면 자연스럽다.)

4. 표면 거칠기 (표면 거칠기가 높은 세부사항이 많은 표면은 테셀레이션 정도를 높일 필요가 있다.)

 

2. 제어점 덮개 셰이더

제어점을 받아서 제어점을 출력하는데, 제어점 개수를 증강할 수 있다.

struct HullOut
{
	float3 PosL : POSITION;
};

[domain("quad")] // 패치의 종류 tri, quad, isoline 중 하나
[partitioning("integer")] // 테셀레이션 단위가 정수 // 분수는 fractional_even 또는 fractional_odd
[outputtopology("triangle_cw")] // 세분으로 만들어지는 삼각형의 정점 감김 순서
[outputcontrolpoints(4)] // 하나의 입력 패치에 대해 출력할 제어점 개수 (= 덮개 셰이더의 실행 횟수)
[patchconstantfunc("ConstantHS")] // 상수 덮개 셰이더 함수의 이름
[maxtessfactor(64.0f)] // 셰이더가 사용할 테셀레이션 계수의 최댓값 (하드웨어는 이 정보로 최적화)
HullOut HS(InputPatch<VertexOut, 4> p,
	uint i : SV_OutputControlPointID,
        uint patchId : SV_PrimitiveID)
{
	HullOut hout;
    hout.PosL = p[i].PosL;
    return hout;
}

14.3 테셀레이터 단계

상수 덮개 셰이더가 출력한  테셀레이션 계수들에 기초해서 패치들을 테셀레이션하고 정점들을 생성해 출력한다.

 

14.4 영역 셰이더

테셀레이터 단계에서 출력된 정점마다 한 번씩 호출됨, 동차 절단 공간으로 변환하는 역할

세 가지 종류의 입력을 받는다.

1. 테셀레이션된 정점 위치의 매개변수화된 좌표 (u, v)

2. 제어점 덮개 셰이더가 출력한 제어점

3. 상수 덮개 셰이더가 출력한 테셀레이션 계수들

struct DomainOut
{
	float4 PosH : SV_POSITION;
};

[domain("quad")]
DomainOut DS(PatchTess patchTess, // 상수 덮개 셰이더가 출력한 테셀레이션 계수들
		float2 uv : SV_DomainLocation, // 테셀레이터 단계에서 얻은 정점의 매개변수화된 좌표
           	const OutputPatch<HullOut, 4> quad) // 제어점 덮개 셰이더가 출력한 제어점 패치
{
	DomainOut dout;
    
    float3 v1 = lerp(quad[0].PosL, quad[1].PosL, uv.x);
    float3 v2 = lerp(quad[2].posL, quad[3].PosL, uv.x);
    float3 p = lerp(v1, v2, uv.y);
    
    float4 posW = mul(float4(p, 1.0f), gWorld); //
    dout.PosH = mul(posW, gViewProj); 			// 동차 좌표계로 변환
    
    return dout;
}

14.6 삼차 베지에 사각형 패치

다수의 제어점을 이용해서 곡면을 생성하는 방법

삼차 베지어 매개변수 공식
삼차 베지에 곡선
삼차 베지에 곡선의 도함수 (곡선의 접선 벡터를 계산할 때 유용)

14.6.2. 삼차 베지에 곡면

영역 셰이더에서 계산이 일어남

 

테셀레이터 단계에서 만들어진 정점의 좌표 uv의 0과 1사이의 v.x 와 uv.y 값을 삼차 베지에 공식의 t에 집어 넣음

테셀레이션되어 생성된 정점들 각각의 위치 (정점 한 개) 를 계산하는데 16개의 제어점이 모두 쓰인다.

 

1.

사각형 패치 대신 삼각형 패치를 테셀레이션 한 결과
삼각형 패치를 입력으로 받는 영역 셰이더 함수
정점 버퍼에 넣을 정점 배열도 수정한다.

5.

2차 베지에 곡면에 대한 매개변수 방정식 p(u, v)

7. 8.

제어점이 아홉 개인 이차 베지에 곡면

베지에 곡면 예제를 제어점이 아홉 개인 이차 베지에 곡면을 사용하도록 만들고, 곡면에 색조와 조명을 적용했다.

기존 3차 베지에 곡면 구현을 위해 16개의 제어점이 사용되던 것을 9개로 줄였다.

베지에 곡면 관련 함수를 수정하였다.

픽셀 셰이더에서 조명을 계산하기 위해 법선 벡터를 u, v에 대한 편미분 계수를 외적함으로써 구했고, uv 값은 텍스처 좌표값에 대응되도록 하였다.

 

연습문제 파일

https://github.com/lemonyun/Directx12_study/tree/main/14

 

GitHub - lemonyun/Directx12_study: 2022/06/10

2022/06/10. Contribute to lemonyun/Directx12_study development by creating an account on GitHub.

github.com

 

 

GPU는 임의 메모리 접근을 염두에 두고 설계된 CPU와 대조적으로 하나의 저장 장소 또는 연속된 저장 장소들에서 대량의 메모리를 읽어서 처리하는 작업에 최적화되어 있다.

 

계산 셰이더는 렌더링 파이프라인 옆에 따로 존재하며, GPU 자원의 자료를 직접 읽어 들이거나 GPU 자원에 직접 자료를 기록할 수 있다.

 

페르미 아키텍처는 SM을 16개 사용

SM(Streaming Multiprocessor) = 다중 처리기

ㄴ SP 32개를 사용

 

SP(Streaming Processor) = CUDA 코어

ㄴ 코어 하나당 스레드하나 담당

 

GPGPU 프로그래밍 (general purpose GPU) 

ㄴ GPU를 비그래픽 분야에 응용하는 것

ㄴ GPU가 계산한 결과를 CPU에서 읽어야 하는 경우가 많다.

ㄴ 자료 병렬성이 있는 알고리즘을 사용해야 한다.

 

13.1 스레드와 스레드 그룹

다중 처리기당 적어도 두 개의 스레드 그룹을 두는 것이 바람직

각 스레드 그룹에는 그룹의 모든 스레드가 접근할 수 있는 공유 메모리가 주어짐

다중 처리기는 하나의 워프(32개 스레드)를 SIMD32 단위로 처리함.

스레드 그룹의 크기는 워프 크기(32)의 배수인 것이 바람직하다. (AMD의 경우 웨이브프런트(64)의 배수)

 

13.2 계산 셰이더

스레드 그룹을 격자 형태로 분배 (cmdlist->Dispatch)

스레드 그룹 하나의 스레드 개수 구성 (계산 셰이더)

계산 셰이더는 D3D12_COMPUTE_PIPELINE_STATE_DESC 구조체를 서술

 

텍스처자원은 cmdlist->SetGraphicsRootDescriptorTable

UAV자원은 cmdlist->SetComputRootDescriptorTable

 

계산 셰이더는 각 스레드에서 실행된다.

 

13.3 자료 입력 자원과 출력 자원

SRV는 읽기 전용 서술자이다.

출력 자원을 계산 셰이더에 묶으려면

UAV 자원을 ALLOW_UNORDERED_ACCESS 플래그를 지정해서 만들고 D3D12_UNORDERED_ACCESS_VIEW_DESC 구조체를 서술해 UAV 서술자를 만들어야 한다. 

 

출력 자원은 RW 접두사가 붙으며 출력 원소의 형식과 차원을 < > 으로 지정해야한다.

ㄴ 예시 : RWTexture2D<int2> gOutput;

 

 

텍스처 필터링을 거친 표본을 사용할 경우 문제점

1. Sample 메서드를 사용할 수 없어서 SampleLevel 메서드를 사용하여 텍스처 필터링을 통해서 텍스처의 표본을 추출해야 한다.

2. 정수 색인이 아닌 [0, 1] 구간으로 정규화된 텍스처 좌표를 사용해야 한다. [ x / 텍스처 너비, y / 텍스처 높이 ] 를 사용해야 한다.

 

13.3.4 구조적 버퍼 자원

SRV 버퍼 자원은 정점 버퍼 자원을 기본 힙에 만들어 올리는 것과 동일한 방법으로 하면 된다.

UAV 버퍼 자원은 생성할때 자원 상태와 플래그를 UNORDERED_ACCESS로 해줘야 한다

 

struct Data

{

float3 v1;

float2 v2;

}

 

(.hlsl)

StructuredBuffer<Data> gInputA : register(t0);

StructuredBuffer<Data> gInputB : register(t1);

StructuredBuffer<Data> gOutput : register(u0);

 

(응용 프로그램)

slotRootParameter[0].InitAsShaderResourceView(0);

slotRootParameter[1].InitAsShaderResourceView(1);

slotRootParameter[2].InitAsUnorderedAccessView(0);

 

루트 서술자들을 받는 루트 서명은 서술자 힙을 거치지 않고 자원의 가상 주소를 루트 인수로서 직접 전달할 수 있는데 이 방법은 텍스처가 아니라 버퍼 자원에 대한 SRV와 UAV에만 가능하다.

 

13.3.5 계산 셰이더의 결과를 시스템 메모리에 복사

계산 셰이더에서 UAV 버퍼 자원에 결과값을 저장하고 메모리에 복사하기 위해서는 힙 속성이 READBACK인 자원(UAV 버퍼와 자료 형식과 크기가 동일) 을 만든 뒤에 cmdlist->CopyResource 메서드를 이용해 자원을 READBACK 버퍼에 복사한다. 이후 Map 함수로 자료를 매핑하여 CPU에서 읽을 수 있도록 한다.

 

13.4 스레드 식별 시스템 값

그룹 ID (SV_GroupID) : 그룹 ID

그룹 스레드 ID (SV_GroupThreadID) : 그룹 내에서 각 스레드의 ID

배분 스레드 ID : SV_DispatchThreadID

3가지 식별 값 모두 int3의 자료형을 가진다.

 

13.5 추가 버퍼와 소비 버퍼

자료를 계산하는 순서가 중요하지 않은 경우 추가 버퍼와 소비 버퍼라고 불리는 종류의 구조적 버퍼를 사용하는 것이 좋다.

 

ConsumeStructuredBuffer<Particle> gInput;

AppendStructuredBuffer<Particle> gOutput;

 

Particle  p = gInput.Consume();

 

gOutput.Append(p);

 

13.6 공유 메모리와 동기화

groupshared float gCache[256]; // 스레드 그룹마다 하나씩 존재

공유 메모리의 흔한 용도는 텍스처 값을 저장하는 것이다. 동일한 텍스처를 여러 번 추출하는 것은 느리기 때문에 

 

스레드 그룹 내 각 스레드가 gCache (공유 메모리)에 값을 저장을 완료하기 전에 gCache 내용을 사용하려 하면 안되기 때문에, GroupMemoryBarrierWithGroupSync() 함수를 사용해 스레드가 모두 공유 메모리에 값을 기록하기 전까지 대기하도록 만들어야 한다.

 

13.7.2 텍스처 대상 렌더링 기법

원래 후면버퍼를 화면에 출력하는 방법

후면버퍼 = 텍스처

텍스처에 대한 rtv 서술자를 출력 병합기 단계에 묶고(OMSetRenderTargets) Present 함수 호출하여 화면에 후면 버퍼 용 출력

 

또 다른 텍스처를 생성하고 그에 대한 rtv 를 만들어서 똑같이 진행해도 가능함 (화면 밖 텍스처 대상 렌더링 방법)

텍스처 대상 렌더링 기법의 용도

1. 그림자 매핑

2. 화면 공간 주변광 차폐 (SSAO)

3. 입방체 맵을 이용한 동적 반사

 

연습문제

1. 2. 3.

1번 2번 문제는 해결했으나 3번 문제는 해결하지 못했다.

계산 셰이더와 출력 결과값 파일
UAV로 사용할 버퍼 자원을 만들고 입력 자료로 사용할 자료를 임시 업로드 버퍼를 사용하여 복사했다. (정점 버퍼 올리는 방법과 동일)
DX11의 UAV FLAG
DX12의 UAV FLAG

DX11에서는 버퍼에 대한 UAV를 만들 때 D3D11_BUFFER_UAV_FLAG_APPEND를 지정하면 해당 버퍼를 셰이더에서 추가 버퍼 혹은 소비 버퍼로 인식하게 만들 수 있는 거 같은데 책과 인터넷을 찾아봐도 DX12에서 추가 소비 버퍼의 사용 예제가 보이질 않는다. FLAG_NONE을 대체하여 사용해봤는데 계산 셰이더가 제대로 작동하지 않는다. 책에서도 이에 대한 설명이 부실하여 일단은 넘어가도록 해야겠다.

D3D12_BUFFER_UAV_FLAG_APPEND는 없다..?

UAV_FLAG_NONE으로 설정하고 셰이더 파일에서 추가 소비 버퍼를 사용하는 대신에 구조적 버퍼(RWStructuredBuffer)로 바꿔주기만 해도 정상적으로 결과가 나온다.

 

5.

예제가 이미 만들어져 있어서 성능 비교만 했다. 격자 크기 512x512 시뮬레이션 대신에 256x256으로 진행하였다.(512로 하면 CPU 버전에서 파도가 렌더링되지 않음)

CPU에서 파도를 처리하는 경우
계산 셰이더(GPU) 에서 파도를 처리하는 경우

GPU에서 계산을 수행하는 것이 CPU에서 수행하는 것보다 처리가 확연하게 빠름을 알 수 있었다.

CPU를 사용한 프로그램은 괜찮았는데 계산 셰이더를 사용한 프로그램을 돌리니 GPU에서 평소에 듣지 못하던 소음이 발생했다.

 

동작 과정:

Disturb가 발생할 때 mCurrsolUav 서술자가 가리키는 버퍼에 계산 셰이더를 이용해서 파도를 일으키면 (계산 셰이더1)

Update마다 mCurrsolUav와 mPrevSolUav를 이용해 mNextSolUav에 다음 정점 위치를 계산하여 기록한다 (계산 셰이더2)

Update가 끝나면 srv, uav, 자원을 서로바꿔준다. (prev를 next로 curr을 prev로 next를 curr로)

 

연습문제 파일

https://github.com/lemonyun/Directx12_study/tree/main/13

 

GitHub - lemonyun/Directx12_study: 2022/06/10

2022/06/10. Contribute to lemonyun/Directx12_study development by creating an account on GitHub.

github.com

'읽은 책 > DirectX 12를 이용한 3D 게임 프로그래밍 입문' 카테고리의 다른 글

15. 1인칭 카메라 구축과 동적 색인화  (1) 2022.06.29
14. 테셀레이션 단계들  (0) 2022.06.29
12. 기하 셰이더  (0) 2022.06.24
11. 스텐실 적용  (0) 2022.06.22
10. 혼합  (0) 2022.06.22

 

기하 셰이더는 기본도형을 입력받는다. 기본도형 마다 기하 셰이더가 실행된다.

기하 셰이더는 기하구조를 새로 생성하거나 폐기할 수 있다.

기하 셰이더에서 나오는 정점 위치들은 반드시 동차 절단 공간으로 변환된 것이어야 한다.

12.1 기하 셰이더 프로그래밍

기하셰이더 한번의 호출로 출력할 최대 정점의 개수를 함수 정의 앞에 붙여야 한다.

[maxvertexCount(N)]

ㄴ N이 지나치게 크면 성능상의 문제가 생길 수 있다.

 

기하 셰이더의 입력 매개변수

ㄴ 입력되는 기본도형의 종류를 명시해야 한다. (point, line, triangle, lineadj, triangleadj)

기하 셰이더의 출력 매개변수

ㄴ inout 붙여야 한다.

ㄴ 기하 셰이더가 출력하는 기본도형의 정점들을 담는 스트림 형식의 객체를 사용한다. 

ㄴ 스트림 형식에 따라 기하 셰이더에서 나온 정점들이 만드는 기본도형의 종류가 달라진다. (PointStream, LineStream, TriangleStream)

 

TriangleStream 같은 경우 삼각형 띠를 출력하는데, 삼각형 목록을 출력하고 싶으면 RestartStrip을 사용하여 흉내낼 수 있다.

 

12.2 빌보드 기법

멀리 있는 물체를 3차원으로 렌더링 하는 대신 2차원 텍스처로 렌더링하고 카메라를 바라보도록 만드는 방법

 

 

기하 셰이더의 입력으로 들어오는 부호 없는 정수 매개변수의 의미소 형식 SV_PrimitiveID

ㄴ 입력 조립기 단계에서 기본도형마다 ID를 부여한다. ( 0, 1, 2 ... )

ㄴ 기하 셰이더나 픽셀 셰이더에서 사용할 수 있다.

 

12.3 텍스처 배열

텍스처 배열은 하나의 자원이다. 자원 서술 구조체에 DepthOrArraySize라는 필드가 있는데 원하는 배열 크기를 여기에 적으면 된다.

 

일반 텍스처 (Texture2D)

gDiffuseMap.Sample(gsamAnisotropicWrap, pin.TexC) * gDiffuseAlbedo;

 

텍스처 배열 (Texture2DArray)

float3 uvw = float3(pin.TexC, pin.PrimID%3);
float4 diffuseAlbedo = gTreeMapArray.Sample(gsamAnisotropicWrap, uvw) * gDiffuseAlbedo;

 

한번의 텍스처 배열 설정과 한번의 그리기 호출로 여러 기본도형에 서로 다른 텍스처를 그릴 수 있다. 

 

12.3.3 텍스처 배열 적재

밉맵 수준이 하나인 dds들을 합쳐서 텍스처 배열을 담은 dds 파일 하나를 만든다. (texassemble 도구)

그 후에 밉맵 생성, 픽셀 형식 변경 할 수 있다. (texconv 도구)

 

12.3.4 텍스처 부분 자원

텍스처 배열의 부분자원에 부여된 색인의 순서는 이렇게 된다.

12.4 알파 포괄도 변환

혼합 설정에서 AlphaToCoverage을 켜게 되면 MSAA 포괄도 계산시에 다각형 기준의 부분픽셀 중심의 위치만 고려하는 것이 아니라 알파 채널도 고려하게 된다.

일반적으로 불규칙한 형태를 알파 채널로 오려내느 식으로 활용하는 텍스처에 대해 사용하는 것이 바람직하다.

기본적으로 MSAA가 활성화 되어있어야 한다.

 

연습문제

1.

7장 예제 프로그램을 변형하여 진행했다.

GeometryGenerator에 xz평면에 만들어지는 원 기하구조를 생성하는 함수를 만들었다.
Default.hlsl의 정점셰이더와 픽셀셰이더로 그린 원의 기하구조
circle.hlsl의 기하 셰이더 함수

기본도형으로 line을 입력받아 2개의 정점을 linestream(선 띠) 형태로 출력하도록 만들었다.

출력되는 정점의 첫 번째 정점은 입력받은 line이 이루는 두 정점의 가운데 점이고

두번째 정점은 그 점에 y 값을 + 4.0f 해준 값이다.

결과

2.

시점과의 거리에 따른 세부 수준(LOD) 변화 구현

기하구조의 중심의 위치는 원점

시점과 기하구조의 거리에 따라 다른 스트림을 출력하도록 기하 셰이더 내부에서 다른 함수들을 사용한다.

4.

메시의 정점 법선 시각화
모든 renderitem의 정점 법선을 시각화 한다.
정점 법선 시각화를 위한 pso
정점 법선 기하 셰이더

정점을 기본도형으로 입력받아 정점 두개로 이루어진 line (법선)을 출력하는 기하 셰이더이다.

 

연습문제 파일

https://github.com/lemonyun/Directx12_study/tree/main/12

 

GitHub - lemonyun/Directx12_study: 2022/06/10

2022/06/10. Contribute to lemonyun/Directx12_study development by creating an account on GitHub.

github.com

 

'읽은 책 > DirectX 12를 이용한 3D 게임 프로그래밍 입문' 카테고리의 다른 글

14. 테셀레이션 단계들  (0) 2022.06.29
13. 계산 셰이더  (0) 2022.06.28
11. 스텐실 적용  (0) 2022.06.22
10. 혼합  (0) 2022.06.22
9. 텍스처 적용  (0) 2022.06.21

+ Recent posts