저번에 이어서 오늘의 주제는

저 오른쪽 위에 보이는 풀숲을 그리는 것이다.
지금까지 만든 지형은 단순히 땅만 있는 상태인데, 실제 지형은 그렇게 단순하지 않기에 여러 디테일을 추가해줄 필요가 있다. 이런 디테일의 대표적인 것 중 하나가 풀숲이다.
풀숲 이외에도 나무나 돌맹이 등도 포함될 수 있으나 이번에는 풀숲만을 추가해볼 것이다.(위 스크린 샷에는 돌멩이도 보이긴 하는데, 이는 오브젝트가 아니고 텍스쳐에 포함된 녀석이다.)
그러면 어떻게 풀숲을 구성하느냐가 이제 관건인데, 상용 엔진에 경우 디테일과 관련된 여러 툴을 제공하여 이 툴을 이용해 터레인 위에 마우스 클릭 앤 드래그를 하면 원하는 위치에 풀이나 나무, 돌 등을 그릴 수 있다. 하지만 당연히 풀숲 몇개 그리기 위해 이런 툴을 만들기엔 작업량도 많고, 프로젝트의 목적에도 맞지 않으니... 터레인을 만들 때와 마찬가지로 유니티에서 먼저 이러한 디테일을 구성한 뒤 구성 정보를 익스포트하여 사용할 계획이다.
그래서 먼저 유니티로 디테일을 구성해봤다.

현재 디테일 세팅이 빌보드로 되어 있기 때문에 공중에서 비스듬하게 바라보다보니 풀숲이 누워있는 것처럼 보인다. 다만 여기서 누워있는지 아닌지는 중요한 문제가 아니므로 패스.
일단 이 디테일들의 정보를 어떻게 관리하고 있는지 파악하는 것이 우선이다.
우선 유니티 TerrainData 라는 클래스를 보면 멤버 함수 중에 GetDetailLayer() 라는 함수를 찾아볼 수 있다. 이 함수는 xBase, yBase, width, height, layer 의 5개의 인수를 받는다. Get이라는 이름대로 이 함수는 n번째 layer에서 x와 y 를 기준으로 width와 height 만큼의 디테일 정보를 int[,] 형식으로 반환한다.
고로 이 함수를 이용하면 전체 터레인 영역에 분포한 디테일 정보를 2차원 배열의 형태로 얻어낼 수 있다. 단, 오직 분포에 대한 정보만 있고, 어떤 객체나 텍스쳐가 렌더링되어야 하는지는 포함되어있지 않다.
그럼 이제 2차원 배열을 얻었으니 이를 2D 텍스쳐로 저장하면 디테일 맵을 뽑아낼 수 있지 않을까...? 이를 토대도 코드를 써보면

이렇게 된다.
여기서 DetailPrototype은 위에서 말한 분포가 아닌 각 레이어에 어떤 것들이 그려져야하는 지에 대한 정보라 보면 된다. 이 프로토타입들의 Length를 이용해 레이어의 갯수를 파악하고 모든 디테일 레이어를 익스포트한다.
그래서 GetDetailLayer()를 이용해 데이터를 얻어내고 이를 저장하기 위한 텍스쳐를 하나 만든 뒤 각 텍스쳐의 픽셀에 값을 Set한다. 이 부분에서 사실 오류가 있는데 잠깐 넘어가고, 이렇게 얻어낸 텍스쳐의 결과를 보면

이렇게 나오는데... 약간 문제가 있다.
지금 코드에서는 var map에 있는 값들을 그대로 SetPixel()를 하고 있다. map의 자료형은 int[,]이기 때문에 디테일이 있는 부분의 값은 항상 1보단 큰 값이 위치하게 된다. 그래서 이를 그대로 쓰면 안되고 map[x,y] / 255 를 이용해 rgb 형식에 맞게 정규화 시켜줘야할 필요가 있다.
그런데 그렇게 되면 다시 읽을 때도 실수 -> 정수로 값변환도 해야할 것이고, 다이렉트x12 환경에서 텍스쳐를 로드하기 위한 번거로운 작업도 해야될 것이다. 그러니 차라리 생각을 바꿔서 텍스쳐로 저장하고 읽어들이는 게 아니라 바이너리로 정수 그대로 적고 읽어 들이면 더욱 간편해질 것이다. 그래서 코드를 좀 더 추가해보면

위에 코드에서 바로 이어지는 코드로 텍스트 및 바이너리 라이터 객체를 생성하고 이를 적는 코드다. 실제 사용은 바이너리로 하지만 제대로 값이 쓰여지는 지 확인하기 위해 텍스트 파일로도 저장한다. 생성된 텍스트 파일을 열어보게 되면,

이렇게 0과 1이 섞인 값들이 쓰여져 있는 것을 볼 수 있다. 여기선 1밖에 보이지 않지만 풀숲이 많이 몰려있는 부분은 2나 3 같은 값도 쓰여진다.
이렇게 하면 유니티로 부터 디테일 분포도를 얻어오는 건 성공했다고 볼 수 있다. 그 외 해당 디테일 정보에 따라 어떤 객체가 그려져야 하는지는 이미 내가 세팅할 때 어떤 텍스쳐가 어떤 색상으로 그려져야 하는지를 설정하면서 알고 있기에 익스포트를 할 필요는 없고 코드를 작성할 때 적절히 넣어주는 것으로 대체할 수 있다.

여기서 설정한 색상이나 텍스쳐 종류 등을 코드를 작성하면서 넣어주면 될 것인데, 노이즈라 적힌 부분이나 패딩 같은 정보는 지금은 무시한다. 유니티에서 설정된 것들을 모두 똑같이 구현하는 것은 실질적으로 어렵고, 그럴듯하게만 만드는 데에는 이러한 정보는 필요 없는 부분이다.
그래서 이렇게 얻어낸 디테일 정보를 통해 디테일를 그리기 위한 새로운 클래스를 만들어 보자.

선언부를 간략하게 설명하면 이전에 만들어둔 CBilboardObject 클래스를 상속받아 만들되 렌더링을 위한 버텍스 버퍼뷰를 비롯한 여러 버퍼들을 스스로 가지고 있도록 구성했다. CBilboardObject 의 경우 하나의 오브젝트가 하나의 CSpriteMesh 라는 메쉬 클래스를 소유할 수 있고, 해당 메쉬 클래스 내부에 이러한 버퍼들을 보유하고 있었다.
하지만 디테일 오브젝트의 경우 굉장히 많이 렌더링될 가능성이 높고, 이를 위해 여러개의 빌보드 메쉬를 만들어 가지는 것보단 디테일 오브젝트의 렌더링에서 하나의 버퍼만 이용하여 드로우 콜을 하고, 이때 프리미티브토플로지는 점리스트로 하여 넘겨준 뒤, 기하셰이더에서 정사각형의 메쉬를 만들어 픽셀 셰이더로 넘겨주게 함으로써 드로우 콜과 버퍼 Set 횟수를 줄이는 방향으로 구성했다.
이제 각 함수들을 정의하고, 추가로 셰이더 코드도 짜야하는데 이 부분은 또 다음에 이어서..
'Rampage' 카테고리의 다른 글
Screen Space Light Shaft - 화면 공간 라이트 섀프트 (1) (0) | 2023.08.10 |
---|---|
Detail Object Map - 지형 위에 풀숲 (2) (0) | 2023.07.29 |
Terrain - 지형 (2) (1) | 2023.07.07 |
Terrain - 지형 (1) (0) | 2023.01.28 |
Rampage - 소개 및 개요 (2) (0) | 2023.01.21 |