그래픽스/DirectX12

[Directx12][9장]텍스처

우향우@ 2023. 3. 21. 21:39

개요

지금까지는 정점별 색상이나 오브젝트의 재질과 조명에 의해서 물체 표면의 색상을 정했다. 이제는 이미지파일을 물체 표면에 적용시키는 방법을 이야기 할 것이다.

우선 텍스처라는 자원에 대해 이야기하자면 기존에도 사용했던 후면버퍼나 깊이버퍼도 다 텍스처이다. 텍스처는 원소들의 n차원 배열(행렬)이다. 대부분의 경우 이미지를 담는 용도로 사용하지만 깊이버퍼처럼 다른 용도로도 많이 사용된다. 이러한 텍스처와 버퍼의 차이점은 텍스처는 밉맵 수준, 필터링, 다중표본화등의 gpu와 연관된 추가기능들이 가능하지만 특정 종류의 타입들만 원소로 사용할 수 있다. 그에 비해 버퍼는 그러한 기능들이 없는 대신 임의의 자료를 담을 수 있다. 아무튼 이번 장에서는 이미지를 담고 있는 텍스처를 이용하여 물체 표면에 그 이미지를 적용시키는 것을 이야기 할 것이다.

추가로 렌더대상 역시 텍스처인데 렌더대상으로 사용한 텍스처를 셰이더 자원으로 사용하거나 반대로도 가능하다. 다만 한번의 렌더링 파이프라인에 동시에 하는 것은 안되고 각각 다른 렌더링 파이프라인에서 다른 것으로 사용이 가능하다.

텍스처 좌표

물체의 표면에 텍스처의 이미지를 적용시키려면 각 정점에 텍스처 좌표라는 특성을 추가해야한다. 텍스처 좌표는 해당 정점이 텍스처상의 어느 위치에 해당하는지를 나타내는 좌표이다. 이때 (0,0)이 텍스처 좌측상단을 나타내며 (1,1)이 우측 하단을 나타낸다. 하지만 0,1의 범위를 벗어난 값도 유의미한 값을 가지기에 사용 할 수 있다.(이후 좌표지정모드에서 설명)

어느한 표면 삼각형이 가지는 세 정점의 텍스처 좌표들은 래스터화 단계에서 삼각보간되어 삼각형의 한 점의 텍스처 좌표를 나타내게 된다. 이때 각 정점이 텍스처 상의 좌표를 잘 표시하고 있다면 삼각형을 이루는 각 점(또는 픽셀)은 텍스처에서의 적절한 좌표를 가지게 될 것이다.

이런 텍스처 좌표를 잘 활용하면 여러 물체에서 사용하는 이미지를 하나의 텍스처에 넣고(텍스처 대지도) 각 물체에서 필요한 좌표부분만 정점에 기록하여 사용하는 방법도 가능하다.

 

DDS

DDS는 PNG, BMP같이 이미지를 나타내는 확장자이다. DDS는 DirectDraw Surface format의 약자로 이름 그대로 3차원 그래픽에 특화된 이미지 형식이다. DDS 텍스처는 다음과 같은 기능들을 제공한다.

  1. 밉맵
  2. GPU가 직접 해제할 수 있는 압축 형식
  3. 텍스처 배열
  4. 입방체 맵
  5. 입체 텍스처

또한 DDS는 다양한 픽셀 형식, 즉 원소 타입을 제공한다. 또한 BC1 ~ BC7에 해당하는 다양한 타입의 압축 형식도 제공한다. 각 압축 형식은 몇가지 색상 채널인 경우, 알파성분이 몇 비트인 경우 사용할 수 있다 같은 제한과 성능이 다 다르다. 이러한 타입들로 DDS를 사용하면 GPU메모리에도 압축형식으로 저장했다가 사용할 때 즉석에서 GPU가 압축 해제를 할 수 있다는 장점이 있다.

PNG나 BMP같은 일반적인 이미지 파일을 DDS로 변환하는 방법은 photoshop의 플러그인 같은 이미지 편집기의 기능을 사용하거나 마이크로 소프트에서 제공하는 texconv라는 명령줄 도구를 사용하면 된다. 각 기능들은 이미지 크기, 픽셀 형식 변경, 밉맵 생성등의 다양한 기능을 제공한다.

텍스처 생성 및 사용

이미지 텍스처를 사용하려면 우선 DDS파일을 불러와 텍스처 리소스를 생성해야한다. 이번 예제에서는 마이크로 소프트의 DDSTextureLoader라는 라이브러리를 서적의 저자가 수정하여 DX12버전으로 만든 DDSTextureLoader.h/.cpp의 기능을 사용한다. 이 라이브러리의 CreateDDSTextureFromFile12함수를 사용하면 기존 데이터를 가진 디폴트 버퍼를 만드는 함수와 비슷하게 디바이스, 명령 목록, dds파일명, 리소스 comptr,업로드 리소스 comptr을 넘겨주면 해당 dds파일의 데이터를 가져와 업로드 리소스를 만들고 업로드 리소스 comptr에 담아준다. 그리고 디폴트 리소스를 만들어 리소스 comptr에 넣고 업로드 리소스를 그 리소스로 옮기는 명령을 명령 목록에 담아주는 함수이다.

그 다음에는 기존에 서술자 테이블을 위해 자원들을 준비했듯이 텍스처 자원을 위한 힙을 만든다. 이는 SRV힙으로 shader resource view 힙이다. 기존과 마찬가지로 CreateDescriptorHeap에 SRV힙 타입으로 만들면 된다.

그 후 SRV desc를 작성하여 SRV생성을 준비한다. SRV desc는 해당 텍스처 자원의 포맷, 차원, 차원타입 별 union 구조체들을 채운다. 우리가 사용할 union 구조체는 2d_SRV로 이 구조체는 텍스처의 밉맵 범위, float 밉맵 하한,고급 기법용 특성들을 가진다. SRV desc를 채웠으면 자원 포인터, SRV desc, 힙 핸들을 이용해 SRV를 생성 및 힙 핸들에 등록한다.

이후 등록된 텍스처들은 힙핸들을 통해 서술자 테이블에 매개변수로 등록해 파이프라인에서 사용 할 수 있다.

필터

텍스처맵 필터

이미지 텍스처를 물체 표면에 씌울 때 텍스처의 각 텍셀 값은 면적을 가진 사각형의 색상값으로 생각하면 안된다. 텍셀 값들은 정확한 한 좌표의 한 점에서의 색상값으로 생각해야한다. 그럼 그러한 좌표를 벗어난 텍스처 좌표가 추출되면 어떻게 될까. 바로 그 주변의 텍셀들을 보간해서 사용한다. 이러한 보간법에는 상수보간(점 필터링)과 선형보간(선형 필터링) 두 가지가 있다. 상수 보간은 가장 가까운 텍셀의 값을 추출하는 것이다. 선형 보간은 겹선형보간을 통해 주변 4개의 텍셀로 부터 선형 보간을 하여 색상을 추출하는 방식이다. 이런 식으로 텍셀들로 부터 특정 좌표의 색상을 추출하는 것을 텍스처 표본 추출이라고 한다.

위의 두 보간법들은 텍스처의 텍셀 갯수와 해당 표면이 렌더링 되는 화면 상의 픽셀 갯수가 비슷하다면 큰 고민 대상이 아닐 것이다. 하지만 물체에 카메라가 가깝게 다가가 텍셀의 수가 픽셀의 수보다 작아진지는 확대 현상이 발생하면 보간법에 따라 각 픽셀의 색상이 크게 달라지며 미시적인 느낌도 달라질 것이다. 상수 보간의 경우 특정 범위의 픽셀들이 같은 색상으로 추출되어 픽셀진 느낌이 들것이며 선형보간은 근처의 4 텍셀의 가중치에 따라 색상이 분포되어 좀 더 부드러운 느낌이 날 것이다. 보통 선형 보간이 더 나은 경우가 많으며 비용도 선형 보간이 크다. 하지만 높은 해상도의 텍스처를 쓰는 것이 미시적 관점에서는 가장 좋다.

밉맵 필터

확대의 반대 개념으로는 축소가 있다. 축소는 물체와 카메라가 너무 멀어져 표면 텍스처의 텍셀이 픽셀수보다 많아 진 경우이다. 이러한 상황에 그냥 텍스처맵 상수보간이나 선형보간만을 사용한다면 수많은 텍셀중 한 텍셀(또는 4텍셀)이 수많은 텍셀 색을 바로 대표해버리는 현상이 발생한다. 그러면 텍스처가 뭉개져 보이는 현상이 나타난다. 이를 방지하기위해 밉맵이라는 개념을 사용한다. 밉맵은 평균 하향표본화를 통해 4분에 1크기의 텍스처들을 여러 레벨에 걸쳐 미리만들어두는 기법이다. 이러한 밉맵들을 텍스처 생성 또는 초기화 시점에 미리 만들어두고 축소가 발생하면 가장 적당한 레벨의 밉맵의 텍스처로 색상을 추출하면된다. 그러면 원래 해상도 기준에서 해당 범위의 여러 텍셀들의 평균값에 의해 색이 추출되게 되어 훨씬 나은 결과가 나오게 된다.

이때 밉맵을 선택하는 방식도 두가지로 나뉘게 된다. 첫번째는 밉맵의 점 필터링 방식으로 가장 가까운 밉맵의 텍스처에서 색상을 추출하는 방식이다. 두번째는 밉맵의 선형 필터링 방식으로 가장 가까운 두 밉맵의 텍스처에서 각각 색상을 추출한 뒤 가까운 정도를 가중치로 선형 보간을 한 색상을 최종 색상으로 추출하는 방식이다. 이러한 선형 필터링에서는 밉맵 레벨이 float로 표현될 수 있다.

비등방 필터링

비등방 필터링은 텍스처를 적용한 표면의 법선 벡터와 시선 벡터에 각도가 클때 (시야에서 비스듬 할 때) 발생하는 텍스처 왜곡을 제거하는 필터링이다. 비등방 필터링은 선형 필터링 등에 비해 성능이 좋지만 비용도 그만큼 비싸다.

텍스처 좌표 지정 모드

텍스처 좌표 지정 모드는 정점 또는 픽셀에서 지정한 텍스처 좌표가 (0,1)구간을 벗어날때 어떻게 해석할지를 정하는 모드이다. 순환, 테두리 색, 한정, 반사 네가지가 있다.

순환: 이미지가 정수 경계마다 반복

테두리 색상: 벗어난 좌표는 따로 지정한 고정색으로 추출

한정: 벗어난 좌표에서 가장 가까운 (0,1) 구간의 좌표로 추출한 색상을 추출

반사: 순환과 비슷하나 경계마다 뒤집어짐

 

표본 추출기 객체

표본 추출기는 텍스처로부터 표본추출을 어떤 텍스처맵,밉맵 필터링과 좌표지정모드, 밉맵 추가 범위 제한들을 어떻게 설정하여 추출할지에 대한 정보를 담은 객체이다. 표본추출기는 두가지 방식으로 생성 및 쉐이더에 전달 할 수 있다. 첫번째는 표본 추출기 힙 및 뷰를 생성하여 서술자 테이블 매개변수로 넘겨주는 방식이고 다른 하나는 루트 서명에서 정적 표본추출기로 생성 및 전달하는 방식이다.

전자는 셈플러 타입의 힙을 기존과 같이 생성하고 표본 추출기 정보를 담은 sampler desc를 채워 createSampler함수에 힙 핸들과 sampler desc를 넘겨주어 표본 추출기 생성 및 핸들에 등록을 해주면된다. 이후 해당 힙 핸들을 루트 서술자에 매개변수로 넘겨주면 된다.

후자는 static sampler desc를 필요한 샘플러 갯수만큼 만들고 배열로 만든 뒤 루트서명의 생성자 함수로 루트서명 생성시 sampler size 매개변수와 sampler 배열을 넘겨주며 생성하면 해당 루스서명에는 desc들에서 지정한 정보로 dsce들에 지정한 레지스터들에 정적 샘플러들이 자동으로 전달되게 된다. 정적 샘플러의 경우 전자의 힙 샘플러와 달리 테두리 색 방식에 설정할 수 있는 테두리 색이 한정적이며 2032개까지만 정의 할 수 있다는 특징이 있다. 하지만 보통 2032개면 충분하고도 남는 양이다.

이렇게 생성된 표본 추출기는 이후 쉐이더에서 텍스처에서 표본추출을 할때 어떻게 추출할지를 지정해주는 역할을 하게된다.

셰이더에서 표본 추출

셰이더에 다음과 같이 텍스처와 표본 추출기가 매개변수로 넘겨졌다고 하자.

Texture2d gTextureMap : register(t0);

SamplerState gSamPler0 : register(s0);

SamplerState gSamPler1 : register(s1);

이때 Texture2D::Sample 메서드로 텍스처에서 특정 좌표의 표본 추출을 할 수 있다.

float4 result = gTextureMap.Sample(gSamPler0, float2(u,v) )

텍스처와 조명

텍스처 표본추출과 조명을 함께 사용하려면 어떻게 해야할까. 텍스처에 들어있는 색상값들을 색상이 아닌 분산반사율이라고 생각하면된다. 재질 특성중 분산 반사율이 해당 표면의 기본적인 색상을 나타내는 느낌이었다는 것을 생각하자. 표면점의 텍스처 좌표로 부터 표본 추출을 하고 그 값을 분산 반사율로 설정해서 조명 계산을 수행해주면 텍스처 이미지위에 조명이 적용되게 할 수 있다. 추가로 재질 상수버퍼의 분산반사율은 그대로 두고 표본 추출한 반사율에 색상 곱하기를 해주는 식으로 사용하면 같은 텍스처를 사용하더라도 재질의 분산반사율에 따라 전체적인 색상 변화를 줄 수 있다.

텍스처 변환

텍스처 예제에서는 오브젝트 상수버퍼와 재질 상수버퍼에 텍스처 트랜스폼이라는 4x4행렬이 추가되있다. 이들은 둘 다 정점 셰이더에서 각 정점들의 텍스처 좌표를 (u,v,0,1)로 확장한 뒤 곱하고 다시 2차원을 줄여 변환하는 역할을 수행한다. 오브젝트 행렬을 먼저 곱하고 재질 행렬을 곱한다. 이렇게 하면 오브젝트 ,재질 별로 전체적인 텍스처 좌표를 변환 할 수 있다. 대표적으로는 비례변환으로 [0,1] 구간의 좌표들을 [0,4] 구간으로 바꾸어 1x1에서 4x4 타일링으로 바꾸는 응용등이있다. 또한 매 프레임 변환 행렬을 바꾸어 프레임 텍스처 애니메이션을 구현 할 수 도 있다.

참고서적

DirectX 12를 이용한 3D 게임 프로그래밍 입문

'그래픽스 > DirectX12' 카테고리의 다른 글

[Directx12][10장]혼합  (0) 2023.03.21
[Directx12][~9장][실습]Block Game  (0) 2023.03.21
[Directx12][8장][2]HLSL 구조체 채우기  (0) 2023.03.21
[Directx12][8장][1]조명  (1) 2023.03.21
[Directx12][7장]파이프라이닝2  (0) 2023.03.21