-
DirectX 11을 이용한 3D게임 프로그래밍 입문 연습문제 8Graphics/DX11 물방울책 연습문제 풀이 2021. 4. 24. 14:40
문제 1
나무 상자 예제의 텍스처 좌표들과 좌표 지정 모드, 필터링 옵션들을 여러 가지로 변경하면서 실험해 보라.
해결
텍셀과 픽셀의 개념은 비슷하면서도 완전히 다른 용도로 쓰인다. 게임을 하다 보면 오브젝트의 텍스트 좌표가 항상 픽셀과 1:1대 응이 되는 건 아니다. 예를 들어 오브젝트를 축소하거나 확대한다면 텍스처가 뭉개지거나 늘리게 될 것이다. 텍스처에 필터링을 씌워서 텍스처가 변질되는 것을 어느 정도 막을 수 있는데 대표적으로 크게 3가지 점 필터링, 선형 필터링, 비등방 필터링으로 나뉜다.
점 필터링은 두 텍셀사이의 텍스처 좌표의 값을 상수 보간으로 구하는 방식이다. 가장 싼 비용이지만 그만큼 품질이 좋진 않다. 위 사진에서 나머지 필터링 방식과 비교해도 가장 좋지 않다는 걸 알 수 있다.
선형 필터링은 상수보간 대신 선형 보간로 텍스처 좌표 값을 구한다. 점 필터링과 비교했을 때 보다 매끄럽게 보여 품질이 좋지만 그만큼 비용이 비싸다.
비등방 필터링은 법선벡터와 카메라 시선 벡터가 수직에 가까울 때 발생하는 왜곡현상을 완화시켜주는 필터링이다. 점 필터링과 선형 필터링의 예시 사진을 보았을 때 상자 윗면의 품질이 비교적 좋지 않은 것을 확인할 수 있다. 필터링을 비등 방식으로 적용한다면 왜곡 현상을 완화시킬 수 있어 선형 필터링보다 품질이 더 높지만 당연히 그만큼 더 비용을 비싸게 잡아먹는 단점이 있다.
본래 예제소스를 실행한다면 크레이트 텍스처가 타일처럼 배치가 되지 않을 것이다. 그 이유는 mTexTransform값이 본래 단위행렬이며 이로 인해 (u, v)=[0,1]^2 이기 때문이다. 타일 형식처럼 배치를 하고 싶다면 직접 Vertex.Tex를 건드려도 되지만 보다 간단한 방법이 있다. mTexTransform은 SRT처럼 좌표변환 행렬을 저장하는 변수이기 때문에 변하게 하고자 하는 행렬을 적절히 저장하면 된다. 단 텍스처는 2차원이기 때문에 z값은 항상 0 임을 알고 있어야 한다. 만약 비례 행렬이 xyz를 5배 늘려주는 행렬이라면 타일 또한 한 변당 5배로 늘어날 것이다.
Border는 (u, v)가 0~1 값을 벗어났을 때 지정된 색깔이 출력된다.
Clamp는 (u,v)가 0~1값을 벗어났을 때 가장 가까운 (u, v)의 텍스처 좌표로 대체된다.
Mirror는 Wrap과 비슷하지만 옆 타일의 텍스처가 뒤집어진 모양으로 출력된다.
좌표 지정 모드를 지정하지 않으면 기본값이 WRAP이다.
문제 2
DirectX 텍스처 도구를 이용하면 각 밉맵 수준을 직접 지정할 수 있다(File ᅳ> Open Onto This Surface). 그림 8.19에 나온 것처럼 밉맵 수준들의 이미지 내 문구나 색상이 모두 다른(렌더링 결과에서 각 밉맵 수준을 식별할 수 있도록) 밉맵 사슬을 가진 DDS 파일을 생성하고 그 파일로 텍스처를 생성해서 입방체에 적용하도록 나무 상자 예제를 수정하라. 예제를 실행한 후 상자를 줌인,줌아웃해서 밉맵 수준의 변화를 실제로 확인 해 볼 것. 또한 점 밉맵 필터링과 선형 밉맵 필터링의 차이도 확인해 보라.
해결
프로젝트 안에 mipmap.dds가 있어서 바로 적용했다.
미리 축소된 이미지를 준비함으로써 텍스처가 적용된 오브젝트가 축소돼도 렌더링 속도를 높일 수 있는 기법이 밉매핑이다. 텍스처 이미지가 얼마나 축소됐냐에 따라 DirectX가 밉맵레벨을 적절하게 판단해준다. dds는 확장자이자 direct draw surface의 약자이며 dds파일 안에 밉맵이 내장되어있다.
이러한 파일을 만드는 법은 윈도 검색창에 directx texture tool이라고 검색하면 뜰 것이다. 우선 그 프로그램을 실행하도록 하자.
이 프로그램에 dds파일을 드래그 앤 드롭하면 화면에 나타난다.
page up, down키를 누르면 각 밉맵레벨에 설정된 이미지들을 볼 수 있다. 각 크기는 순서대로 256,128,64....이다. 만약 텍스처 크기가 40x40이라면 64x64와 32x32 텍스처 좌표를 보간해서 눈에 나타날 것이다. 이처럼 dds안에 밉맵이 내장되어있으며 텍스처 크기는 대부분 2의 배수이다. 단축키를 통해 각 밉맵레벨마다 이미지를 확인할 수도 있다.
잘 이용하면 툴을 이용해 자신이 원하는 dds파일도 만들 수 있다. 왼쪽은 점 필터링, 오른쪽은 선형 필터링을 적용했을 때 나타난 결과물이다. 확실히 점 필터링은 밉맵레벨 간의 이미지 경계가 뚜렷하다. 반면 선형 필터링은 이미지가 자연스럽게 넘어가기 때문에 보다 좋은 퀄리티를 보장할 수 있다.
혹시나 해서 비등방 필터링을 적용해봤다. 역시 비싼 값 하는 필터답게 모든 면이 밉맵레벨 1로 나타나는 것을 볼 수 있다. 캡처는 따로 안 해놨지만 y축을 기준으로 각도를 극히 기울여서 옆면이 보일랑 말랑 할 때 그때서야 밉맵레벨 2가 보인다.
문제 3
나무 상자의 여섯 면에 불덩이 모습이 나타나도록 나무 상자 예제를 수정하라.
해결
프로젝트 안에 두 파일이 있다. 이 두 개의 dds파일을 이용해 텍스처를 혼합해서 불덩이 같은 모습을 나타내도록 한다.
우선 ID3 D11 ShaderResourceView 변수를 배열형으로 바꿔준다. 이전까지는 텍스처 하나만을 이용해 입체에 그림을 그렸지만 이번에는 2개를 사용하기 때문이다.
ReleaseCOM은 이중 포인터를 매개변수로 넘길 수 없기 때문에 원소마다 Release함수를 호출해준다.
텍스처 파일 경로를 통해 파일을 불러와서 좀 전에 언급한 배열에 값을 넣도록 한다. 하나의 함수에서 2개 이상의 텍스처를 한 번에 불러올 수 없기 때문에 텍스처가 2개이므로 함수도 2번 호출하도록 한다.
이 소스코드는 Effects.h에 있는 소스코드 중 일부분이다. 기존 텍스처 1개의 정보를 HLSL로 보내기 위해서는 SetResource함수를 사용하였으나 이 함수는 텍스처 하나만 보낼 수 있는 즉 배열을 인자로 넘길 수 없기 때문에 이를 가능케하는 SetResourceArray함수를 사용하도록 했다. 이로서 텍스처 배열을 HLSL로 값을 넘길 수 있게 되었다. 참고로 원래 헤더 파일에서 기능을 구현하면 안 되는 걸로 아는데 예제 소스가 그렇게 구현되어있으니 이를 따라 했다. 아래부터는 fx소스코드이다.
각 텍스처의 샘플 결과물을 곱하여 두 텍스처를 합친 결과물을 볼 수 있다. 책에서는 곱하라고 나와있지만 사칙연산을 모두 이용해본 뒤 결과물을 보았다.
문제 4
연습문제 3을 이어서, 불덩이 텍스처를 시간의 함수로서 회전해서 각 면에 입히도록 예제를 수정하라.
해결
문제를 읽고서 고민을 좀 했다. 회전을 하는 건 좋은데 무슨 축을 기준으로 회전을 하라는 건지.
x, y, z 축을 하나씩 골라서 답을 찾아보는 방법도 있지만 문제를 읽었을 때 그건 정답이 아니라고 생각했다.
만약 회전이 가운데를 중심으로 빙글빙글 도는 거라면 아마도 할 수 없을 거라고 단정 지어버렸기 때문이다.
텍스처는 2차원 좌표(u, v)로 위치를 잡는데 원점이 왼쪽 위 꼭짓점이기 때문에 u 축으로 회전하든 v 축으로 회전하든 원하는 애니메이션을 보기는 어려울 것 같다.
그렇다면 그 원점을 가운데로 이동하면 어떨까 했는데 그렇게 하면 타일링이 발생해서 한 면에 온전한 텍스처가 나오지는 않을 것이다. 그래서 가장 비슷하게나마.. z 축으로 회전하게 구현하였다.
프레임은 1초에 몇 번이나 장면이 업데이트되는지 나타내는 함수이고 dt는 그 프레임과 프레임 사이의 시간이다.
고로 프레임 수 * dt = 1이다. 만약 회전 주기를 2초로 하고 싶다면 dt/2로 해주면 된다.
알아보니까 회전과 관련된 용어 중 쿼터니언이라는 게 나오는데 사원수 파트 때 나오는 개념인 것 같다. 그걸 이용하면 이 문제를 해결할지도 모르겠다.
문제 5
4초간 재생되는 불 애니메이션의 프레임 이미지 120장을 이용하여 각 면에 재생되도록 수정하라.
해결
이 예제에서는 매 업데이트마다 deltaTime값을 제공한다. deltaTime(줄여서 dt)는 프레임과 프레임 사이의 시간을 가리키며 이를 이용해 애니메이션 효과를 낼 것이다. 불 애니메이션의 재생시간은 4초이며 총프레임은 120장이므로 이 애니메이션의 dt는 4/120인 0.03333...이다. 매 프레임마다 dt값을 더하여 애니메이션 dt값보다 커질 때마다 텍스처가 바뀌도록 수정하려고 한다.
우선 몇 가지 주의할 점이 있다. 애니메이션 파일이 dds가 아닌 bmp인데 현재 directx 11 sdk는 CreateShaderResourceViewFromFile함수를 제공하고 있지 않다. 고로 추가적으로 라이브러리를 외부에서 가져와야 한다. 본인도 이거 때문에 방법 찾느라 시간을 너무 많이 잡아먹었다. 라이브러리 적용 방법은 이 링크를 참고하길 바란다. lucidmaj7.tistory.com/173
먼저 변수 및 함수를 간단히 살펴보겠다.
ScratchImage는 위 링크를 통해 가져온 외부 라이브러리 DirectXTex에 있는 클래스이다. 이 배열을 이용해 120개의 텍스처 정보를 담을 것이다.
maxAnimFrame은 총 프레임 개수, 즉 애니메이션 파일 개수라고 보면 된다. 예제에서는 120장이 있으니 생성자에서 120으로 초기화했다.
AnimationUpdate는 업데이트 함수마다 같이 호출되는 애니메이션 전용 메서드이다. 이 메서드에서 애니메이션 프레임을 계산 및 체크하며 적절히 애니메이션을 재생하도록 기능을 추가할 것이다.
images를 포인터로 선언하였기 때문에 배열로써 동적 할당해준다. 그다음은 반복문인데 애니메이션 파일 이름이 1부터 시작해서 i 또한 1부터 시작하도록 하였고 적절히 문자열 함수를 사용하여 파일 이름을 완성시켰다. LoadFromWICFile메서드를 호출하여 파일 경로를 통해 bmp 파일을 가져와 images 배열에 집어넣는다. 여기서 첫 번째 매개변수가 const wchar_t*이기 때문에 const char*과 혼동되지 말아야한다. 마지막으로 CreateShaderResourceView메서드를 호출한다. 이메서드또한 DirectXTex에서 가져온거기 때문에 자세한 설명은 못하지만 아마 책에서 설명하는 CreateShaderResourceViewFromFile 메서드와 비슷한 기능일 것이다. 첫번째 매개변수에 directx 장치를 가리키는 포인터를 넘기고 중간은 이미지 어쩌고.. 등을 넣고, 마지막은 이미지를 적용할 텍스쳐 주소 값을 넘기면 된다. 문제 4번에서는 mTextures함수가 이중 포인터였는데 여기서는 다시 1차원 포인터로 바꿨다.
위 메서드는 업데이트 메서드와 같이 호출되는 메서드이다. 위에서 설명했듯 매개변수는 각각 애니메이션 재생 길이, 프레임 개수, 델타 타임으로 받는다. secondPerFrame은 애니메이션 기준 프레임과 프레임 사이의 시간인데 매 프레임마다 dt를 더해서 그 값보다 같거나 커지면 애니메이션 다음 프레임이 나오도록 구현했다. 조건문 안 소스코드가 그 내용이다.
문제 7
제7장의 조명된 두개골 예제("LitSkull")의 지면과 기둥, 구에 텍스처를 입혀 보라.
해결
7장의 LitSkull예제는 조명을 다루는 예제이기 때문에 텍스처에 대한 소스코드가 거의 없다. 8장에서 배운 내용을 토대로 소스코드를 적어보자.
위 스크립트는 각각 Vertex.h와 Vertex.cpp의 일부분이다. 이 프로젝트는 해골을 제외하면 GeometryGenerator를 통해 자동으로 정점과 텍스쳐 좌표를 제공해 준다. 그리고 도형을 생성할때 그 값을 받아야 한다. 멤버를 추가했으니 인풋레이어도 수정했다. TEXCOORD는 2차원이기 때문에 포맷방식을 DXGI_FORMAT_R32G32_FLOAT으로 지정하였다.
위 스크립트는 Effects.h와 Effects.cpp의 일부분이다. Effects는 c++에서 효과 객체를 다룰 수 있게 해준다. 이를 통해 hlsl과 소통을 할 수 있다. 하지만 텍스처에 대한 기능이 추가되어있지 않기 때문에 위와 같이 변수와 함수를 추가해줬다.
Basic.fx이다. 외부에서 SetTexTransform메서드를 사용하면 이 변수가 갱신된다. VertexIn의 Tex와 gTexTransform(float4x4)를 곱한다. 그러면 텍스처 국소 좌표가 세계 공간 좌표로 변환된다. Texture는 2차원이므로 xy값만 가져온다.
위 소스는 LitSkullDemo.cpp이다. Tex를 추가했으니 이제 값을 할당해줘야 한다. 위에서 설명했듯 도형을 생성하면 그 객체에 Normal, Position, TexC, TangentU가 있다. TexC의 값을 Tex에 할당해준다.
그리고 텍스처를 불러온다. 다행히도 텍스처가 dds이기 때문에 외부 라이브러리를 불러오는 고생은 안 해도 된다.
각 도형을 그리는 영역에서 텍스처와 TexTransform의 값을 적절히 할당한다. 도형을 그릴 때마다 텍스처와 변환 행렬을 할당해줘야 할 필요는 없다. 하지만 도형을 그리기 전에 이미 할당을 한 상태라면 그 값에 의해 도형 및 텍스처가 그려지니 만약 다른 변환을 원한다면 그리기 전에 새로운 값을 경신해야 할 것이다.
여담으로 이유는 잘 모르지만 해골은 텍스쳐가 잘 입혀지질 않는다. 입혀진다고 해도 괴상한 모습으로 나올 것이다. 마지막으로 포스팅한 지 1달이나 지났는데 외부 라이브러리를 가져오는데 애먹어 시간을 굉장히 많이 잡아먹었다. 그리고 문제 6번은 책에 답이 나와있기도 하고 개인적으로 어려운 문제라고 판단해서 따로 적진 않았다. 다음엔 9장 혼합 연습문제 풀이를 가져오겠다.
'Graphics > DX11 물방울책 연습문제 풀이' 카테고리의 다른 글
DirectX 11을 이용한 3D게임 프로그래밍 입문 연습문제 10 (1) 2021.05.26 DirectX 11을 이용한 3D게임 프로그래밍 입문 연습문제 9 (0) 2021.04.28 DirectX 11을 이용한 3D게임 프로그래밍 입문 연습문제 7 (0) 2021.04.03 DirectX 11을 이용한 3D게임 프로그래밍 입문 연습문제 6 - 12 (0) 2021.03.24 DirectX 11을 이용한 3D게임 프로그래밍 입문 연습문제 6 - 10 (0) 2021.03.23