기존의 텍스처 매핑 방법은 정점 자료에 법선 정보를 넣어 삼각형 내에서 3개 정점에 대한 법선을 보간하는 방법으로 법선을 지정했는데, 법선 매핑을 사용하면 더 세밀하게 법선을 지정할 수 있다고 한다.
법선을 높은 해상도(더 세밀한 방법)에서 지정하면 조명의 세부도가 높아지지만, 메시 기하구조 자체의 세부도까지 높아지지는 않는다.
법선 맵을 만드는 도구
1. NVIDIA의 Photoshop용 법선 맵 필터
2. CrazyBump
19.2 법선 맵
법선 맵은 하나의 텍스처이다. RGB 를 8비트씩 24비트에 저장하는 텍스처라고 가정하면 RGB에 x y z (법선 벡터) 를 넣는다. 대체로 법선 벡터는 z축과 유사한 방향이기 때문에 z 값이 x y 값보다 높아 법선 맵 텍스처를 색상 이미지로 보게 되면 푸르스름한 모습이 된다.
법선 맵을 압축 텍스처 형식에 저장하고 싶다면 BC7(DXGI_FORMAT_BC7_UNORM) (압축된 텍스처는 렌더링 파이프라인의 셰이더 단계에 대한 입력으로만 사용할 수 있다.)을 사용하는 것이 최상의 품질을 보인다.
화면에 한 점을 클릭하면 시야 절두체 가까운 면의 정점 하나가 대응되고, 시점 위치와 그 정점을 잇는 반직선을 계산한 뒤에 각 물체와 반직선을 교차 판정하고, 여러 개의 물체가 반직선과 겹친다면 가장 가까운 물체를 선택하면 된다.
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)
루트 서술자들을 받는 루트 서명은 서술자 힙을 거치지 않고 자원의 가상 주소를 루트 인수로서 직접 전달할 수 있는데 이 방법은 텍스처가 아니라 버퍼 자원에 대한 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번 문제는 해결하지 못했다.
DX11에서는 버퍼에 대한 UAV를 만들 때 D3D11_BUFFER_UAV_FLAG_APPEND를 지정하면 해당 버퍼를 셰이더에서 추가 버퍼 혹은 소비 버퍼로 인식하게 만들 수 있는 거 같은데 책과 인터넷을 찾아봐도 DX12에서 추가 소비 버퍼의 사용 예제가 보이질 않는다. FLAG_NONE을 대체하여 사용해봤는데 계산 셰이더가 제대로 작동하지 않는다. 책에서도 이에 대한 설명이 부실하여 일단은 넘어가도록 해야겠다.
UAV_FLAG_NONE으로 설정하고 셰이더 파일에서 추가 소비 버퍼를 사용하는 대신에 구조적 버퍼(RWStructuredBuffer)로 바꿔주기만 해도 정상적으로 결과가 나온다.
5.
예제가 이미 만들어져 있어서 성능 비교만 했다. 격자 크기 512x512 시뮬레이션 대신에 256x256으로 진행하였다.(512로 하면 CPU 버전에서 파도가 렌더링되지 않음)
GPU에서 계산을 수행하는 것이 CPU에서 수행하는 것보다 처리가 확연하게 빠름을 알 수 있었다.
CPU를 사용한 프로그램은 괜찮았는데 계산 셰이더를 사용한 프로그램을 돌리니 GPU에서 평소에 듣지 못하던 소음이 발생했다.
동작 과정:
Disturb가 발생할 때 mCurrsolUav 서술자가 가리키는 버퍼에 계산 셰이더를 이용해서 파도를 일으키면 (계산 셰이더1)
Update마다 mCurrsolUav와 mPrevSolUav를 이용해 mNextSolUav에 다음 정점 위치를 계산하여 기록한다 (계산 셰이더2)
Update가 끝나면 srv, uav, 자원을 서로바꿔준다. (prev를 next로 curr을 prev로 next를 curr로)