이번엔 인스턴싱에 대해 이야기 해보고자 한다. instancing에 대한 정의 및 정리는 다른 분들이 잘 해주신 것들이 많기에 괜히 내가 어설프게 따로 정리하지는 않는다. 다만 내가 사용 한 조금 다른 방식을 소개하고 조언을 구하고자 한다. 우선 인스턴싱에 대하여 잘 모르시는 분은 다음 링크들을 살펴보면 되겠다.
: 이 글 내에도 더 많은 인스턴싱 뿐 아닌 Batching 관련 용한 링크들이 있다.
: XNA 기준으로 작성이 되어 있지만 함수 프로퍼티만 다를 뿐, 개념 및 설명은 정말 잘 되어 있다.
최근의 대중화된 그래픽카드들은 웬만하면 Shader Model 3.0이상을 지원하므로, 우리 엔진도 secone Vertex Buffer를 사용하는 Hardware Instancing를 이용한다.
그러기 위해서는 매번 second Vertex Buffer에 대하여 lock/unlock를 수행해야 한다. 나는 buffer lock없이도 오브젝트들을 transform 시키고 컬링에서 걸러질 수 있을 지 고민하다 셰이더 상수 배열을 이용하기로 했다. 위 링크에 나온 방식에서 특별히 크게 다른 것도 아니고, 쉽게 생각 할 수 있는 방식이라 이미 누군가 사용하다 버린(내가 생각했던 것 보다 효울이 그렇게 좋지는 않았기 때문에) 것일 지도 모르겠다.
- second Vertex Buffer의 자료형은 오로지 float하나만으로 이루어져 있다.
- 한번에 수용 가능한 인스턴스의 갯수만큼의 크기 n의 크기로 버퍼를 생성하고, 0,1,2~(n-1)로 채운다. 이 숫자들은 변경될 일이 없으며, 상수 배열의 인덱스로 쓰인다.
- 셰이더에서는 인스턴스의 트랜스폼을 결정 할 매트릭스 배열 m[n]을 선언한다.
- 오브젝트의 위치 변화가 있을떄 혹은 매 프레임마다 인스턴스들의 world matrix들을 m에 셋팅해준다.
- VS에서는 second stream의 내용을 m[]의 인덱스 첨자로 사용하여 world matrix를 결정하고 transform한다.
이렇게 되면 Vertex Buffer의 실시간 Lock/Unlock비용에서 해방될 수 있다.
하지만 단점으로는,
- 1. 상수 갯수의 제한이 있다보니 한번에 100단위의 갯수를 그려내지 못한다. 이것 저것 따지다보면 기껏 해봐야 3,40개 정도가 수용 가능하다. 하지만, 실제 게임에서 랜더링 하다보면 같은 메쉬를 그 이상 그려낼 일이 많이 없다. 100단위 혹은 1000단위로 그려내는 것은 테스트 컷 외에는 발생하지 않으므로, 이 단점이 큰제약이 되지는 않는다.
- 2. matrix array를 셰이더로 넘겨주는 것이 생각보다는 무겁다. VB lock보다는 낫긴 하지만 생각보다 드라마틱한 효과는 보지 못했다.
- 3. 실제 그리려는 인스턴스의 갯수와는 상관 없이 m[n]의 크기에 따라 SetMatrixarray()를 통한 상수 설정 시간이 결정된다. SetMatrixarray(,,1)을 하든 SetMatrixarray(,,40)을 하든 상관 없이 n에 따라 비례한다. 이를 극복할 수 있는 방법이 있으면 조언을 부탁드린다.
만약 우리 게임이 작은 우주선이 2,300개씩 나오는 RTS라면 이 방식이 쓰이지 못할 것이다. 하지만 우리 엔진에는 꽤 적합한 방식이라 생각하고 있다. 작업 양이 그리 크지도 않으므로 이런 방식으로도 한번 시도해보시길 바란다. 작업시 문제점과 개선점도 공유해주시고말이다~ 굽신~굽신~