본문 바로가기

Starling

동적 TextureAtlas

draw call 횟수를 줄인다는 것은

결국 gpu에 업로드하는 텍스쳐 수를 줄이는 것이다.



QuadBatch를 사용하여 줄일 수도 있지만 이것은 제약이 꽤 있어 범용적으로 쓰긴 힘들다.

-----------------------------------------------------------------------------------------

QuadBatch

Packagestarling.display
Classpublic class QuadBatch
InheritanceQuadBatch Inheritance DisplayObject Inheritance EventDispatcher Inheritance Object

-----------------------------------------------------------------------------------------




가장 좋은 방법은 스프라이트시트를 이용하는 것이다.

많은 이미지를 한장에 넣고 좌표정보를 이용하여 원하는 영역을 보여주면된다.

이를 위해서 Starling 에서는 TextureAtlas 를 이용하게 된다.



하지만 렌더링할 화면의 모든 것들이 한장의 스프라이트시트에 포함되면 좋겠지만

그러긴 힘든것이 


1.일단 2048x2048의 크기 제한이 있고

2.스프라이트시트를 표현할 화면수만큼 만드는 것은 사실 불가능하고

3.비슷한 유형의 것들(배경,캐릭터,무기등등) 분류하여 스프라이트시트를 만드는것이 훨씬 더 효율적이기 때문이다.


하나의 화면을 그릴 땐 어쩔 수 없이 배경시트에서 산과 들판을 가져오고,

유닛시트에서 a유닛,b유닛을 가져오는 식으로 화면을 구성하게 되는데

필연적으로 draw call 이 늘어나게 된다.


이 때 만약 가져온 텍스쳐들을 gpu에 업로드하기전 다시 하나의 스프라이트시트를 동적으로 만들어

사용할 수 있다면 draw call을 줄일 수 있다.




아래 예제의 IMG불러오기 버튼을 눌러 이미지 파일들을 선택하게 되면

선택된 파일들은 텍스쳐로 바뀌어 드래그가 가능한 형태로 화면에 랜덤하게 뿌려지고

터치가 지원되거나 혹은 Z키를 누른다면 ZOOM 과 ROTATE 기능도 어설프지만 사용할 수 있다.

확실히 RenderTexture 나 QuadBatch 를 사용한 것은 아니란 걸 알 수 있다.

하지만 draw call은 어지간해선 올라가지 않는다.



(이미지를 추가하다 보면 draw call 은 결국 올라가긴 올라간다)





이것이 동적 TextureAtlas 를 이용하는 방법이다.

이를 위해선 일단 여러장의 텍스쳐들을 한장의 텍스쳐로 만들기 위한 기법이 필요한데

이는 우야꼬님의 블로그 포스팅에서 유용한 알고리즘 링크를 찾을 수 있다. (바로가기)



c++ pseudocode 로 된 알고리즘과 그를 이용한 자바스크립트 코드는 아래와 같다.





어디선 본듯한 표는 왠지 허프만 알고리즘을 생각나게 하지만

개인적으론 그것과 연관시켜 생각하는 것보단 오히려 Composite 패턴으로 생각하는쪽이 편한 것 같다.;


두 코드에서 핵심은 동일하다.(당연하겠지만)


0.노드는 자신의 공간을 필수적으로 가지며,필요한 경우 분할된 2개의 자식노드를 가질 수 있다.

1.자식노드가 존재한다면 자식노드를 우선적으로 고려한다. (child[0] 과 child[1] , left 와 right)

2.빈공간이 없거나 , 대상공간이 내부공간보다 크다면 null 리턴

3.공간과 대상영역이 일치하면 해당 노드 리턴,

4.위 조건에 부합되지 않는다면 2개의 자식노드를 만들고 여분의 높이와 너비 중 큰공간을 분할하는 방식으로

 공간을 자식에게 배분.

5.그리고 적은영역을 가진 자식노드를 우선으로 재귀호출.



이젠 ActionScript 로 연습용 예제를 만들어 보자.

이해는 했으나 안보고 짜기엔 너무 잘짜여 있어 적극적으로 참고하며 (배껴서) 짜기로 한다 ㅋㅋ




Rect 클래스


ActinoScript 에는 영역에 대한 정보 처리하기 위하여 Rectangle 클래스를 제공하므로 이를 상속하자.

fits_in 메서드와 same_size_as 메서드는

부모클래스인 Rectangle 의  containsRect 메서드, equals 메서드와 비슷해보이지만

좌표를 무시하고 크기 자체만을 고려하여 검사한다. 





Node 클래스


트리를 구성할 클래스. 자바스크립트 코드를 그대로 씀.





메인클래스


클래스 하나에 막 쑤셔 넣다보니 지금보니 반복된 코드도 있고

매번 rect객체를 만들 필요도 없어보이는데 귀찮으니 넘어가고;-_-ㅋ; 

어쨌든 좀 길긴 하지만 별건 없다. 


메인노드를 만들고 화면을 구성한다.

매번 일정크기의 (5~50) 랜덤한 Shape를 생성한 뒤 400,540의 크기의 영역에서 적절한 위치가

어딘지 알아내어 BitmapData에 그린다.


만약 생성된 Shape의 크기만큼의 여분공간이 남아 있지 않다면  Packing 은 이루어지지 않고

실패한 영역은 따로 계산해 둔다.


아래는 위의 코드로 만들어진 예제 파일이다.






이는 간단한 연습예제일 뿐이지만 여기에 필요한 모든 것이 다 들어 있다.

하지만 동적 TextureAtlas 를 만들려면 처리해야 하거나 조금 생각해봐야 할 것이 몇가지 있는데


1.예제에서는 고정된 크기의 영역을 (400x540) 미리 만들고 랜덤한 크기의 shape를 집어 넣었지만

실제론 필요한 다수의 shape나 texture 들이 먼저 준비되거나 파악된 후, 그에맞는 적절한 크기의 영역이 만들어져야 한다.

하지만 텍스쳐의 크기는 어차피 자유롭지 않으므로 (1,2,4,8,16,32,64,128,256,512,1024,2048) 합쳐질 텍스쳐들의 크기가

어느정도되는지 파악하여 적당크기를 선택하는 것으로 우선은 해결조짐이 보인다.

필요한 순간에만 사용하고 dispose 할거라면 사실 신경안쓰고 2048x2048에 맞춰도 상관없을 것 같기도하고

임의적으로 상황에 맞게 자신이 선택할 수 있도록 하는 방법도 있다.


2.위 알고리즘은 다수의 텍스쳐를 포함할 가장 효율적인 한장의 크기를 구한다기보다는

정해진 한장의 크기에 어떻게 하면 많은 것을 구겨넣을 수 있을까? 에 대한 방법이다.

같은크기라도 200x200과 100x400 과  무엇을 먼저 배치할 것인지에 대한 순서도

포함될 텍스쳐수에 영향이 있을지 없을지도 고려해봐야 한다.


3.2048x2048 을 넘을 경우 또다른 영역을 만들어야 한다.

이는 packing 에 실패한 텍스쳐들의 목록이나 크기를 가지고 다시 반복작업을 하거나 한번이라도 실패한다면

또다른 node와 공간, 그려질 영역을 만들어야 한다는걸 의미한다.

이 때 중요한것은 공간이 모자라 새로운 영역을 만들었더라도 이후 추가될 텍스쳐가 이전의 영역에 포함 될

여지가 있다면 이전의 영역에 추가되어야 한다.  


4.실제 TextureAtlas 는 Texture 타입 객체하나와 XML을 필요로 하며 유용한 메서드가 준비되어있다.

이를 xml을 구성한 뒤 상속 혹은 합성하여 사용할 것인지 , 

아니면 단순히 RenderTexture에 draw 하며  Texture 클래스의 fromTexture 메서드를 사용할 것인지도

고민해볼만한 문제이다. 장단점이 있어 보인다.


 fromTexture(texture:Texture, region:Rectangle = null, frame:Rectangle = null):Texture

[static] Creates a texture that contains a region (in pixels) of another texture.





사실 위 예제만 이해했다면 이후 자신만의 커스텀 모듈은 각자 만들기 나름이다.

개개인의 성향과 습관, 실력에 따라 코드나 구조가 바뀌게 될테지만

필요한 텍스쳐를 가능한한 하나의 텍스쳐로 합쳐 그것을 사용하는 것.

이것만 지키면 분명 draw call 은 줄어 든다.