<commit_before>---
layout: post
author: "<NAME>"
comments: true
categories:
  - unity
  - shader
  - rendering
  
---

__이 글은 Unity 5.6.1f 버젼에서 작성되었습니다. 다른 버젼에서는 에러가 날 수 있으니 참고 바랍니다.__

[Using Texture2DArray in Unity]({{ site.baseurl }}{% post_url 2017-06-04-using-texture2darray-in-unity %}) 에 이어 _DrawCall_ 을 줄이기 위한 방법에 대해서 소개하려한다. GPU Instancing 이라는 방법인데 _TextureArray_ 와 같이 응용해서 사용하면 획기적으로 _DrawCall_ 을 줄일 수 있다.   

일반적으로 알려진 _GPU Instancing_ 에 대해서 말하자면 컴퓨터의 RAM 에만 저장하던 데이터들을 GPU 메모리에 복사해놓고 GPGPU 나 쉐이더를 실행할 때 빠르게 데이터에 접근하는 것을 GPU Instancing 이라 한다. 만약 _GPU Instancing_ 을 사용하지 않으면 매번 _DrawCall_ 에 데이터를 넣어줘야하기 때문에 수많은 _DrawCall_ 이 걸리게 되고 이는 CPU 의 시간을 뺏어먹게 되어 영 좋지 않은 일이 된다. 보통은 같은 동작을 하는 오브젝트들을 최적화할 때 쓰인다. 사용하게 되면 _DrawCall_ 이 _O(__오브젝트 갯수__)_ 로 되던것이 O(1) 의 갯수로 줄어든다. 그래서 _TextureArray_ 와 같이 사용하게 되면 _DrawCall_ 이 _O(__오브젝트 갯수__ * __텍스쳐 갯수__)_ 로 계산되던게 _O(__1__)_ 로 바뀌어 버리니 CPU 시간을 엄청나게 많이벌 수 있다. 다만 GPU 메모리를 많이 잡아먹기 때문에 신경써서 데이터를 구성하지 않으면 무슨일이 일어날지 모른다.

<!-- more -->

기술을 써보기 전에 우선 구현 사항부터 생각해야 한다. 필자는 Unity 에서 지원하는 __SkinnedMeshRenderer__ 가 _DrawCall_ 배칭을 해주지 않아 간단한 스키닝을 직접 구현하였다. __SkinnedMeshRenderer__ 가 많은 기능을 지원하긴 하지만 __SkinnedMeshRenderer__ 컴포넌트의 갯수가 절대적으로 많아지고 매터리얼이 늘어나게 되면 어쩔 수 없이 원하는 기능을 붙여 직접 구현해야 한다. [InstancedSkinning](<EMAIL>:hrmrzizon/InstancedSkinningExmaple.git)에서 참고할 수 있다.

해야할 것은 두가지다. 쉐이더에서 데이터를 선언 후 직접 사용하는 코드를 짜주어야 하고, 스크립트에서는 필요한 데이터를 모아서 넣어주기만 하면 된다. 말로는 간단하지만 신경써주어야 할것이 많다. 필자 역시 간단하다고 생각하여 시작했으나 꽤 많은 삽질 끝에 성공했다.

_GPU Instancing_ 의 핵심은 GPU 메모리에 어떤 데이터들을 어떻게 옮겨놓고 그 데이터들을 어떻게 사용하느냐가 제일 핵심이다. 스크립트에서는 __MaterialPropertyBlock__ 인스턴스를 통해 데이터를 한꺼번에 세팅하고 _Graphics.DrawMeshInstanced_ 메소드를 호출해 그린다. 보통은 매 프레임별로 _Graphics.DrawMeshInstanced_ 호출하기 때문에 적당히 코딩이 되어있다면 필요할때마다 __MaterialPropertyBlock__ 인스턴스에 데이터를 갱신해주기만 하면 된다. __MaterialPropertyBlock__ 은 쉐이더에 들어가는 정보들을 취급하는 데이터 뭉치(chunk)다. __Material__ 은 쉐이더 정보와 필요한 데이터를 가지고 있는 인스턴스다. 쉐이더 정보를 가지고 있기 때문에 매터리얼의 갯수가 많으면 많을수록 _DrawCall_ 의 갯수가 늘어난다. 하지만 __MaterialPropertyBlock__ 은 __Material__ 과는 다르게 정보만 가지고 있는 것이기 때문에 _DrawCall_ 의 갯수가 늘어나지 않는다. __MaterialPropertyBlock__ 에 관한 자세한 사용법은 [Unity Reference : MaterialPropertyBlock](https://docs.unity3d.com/kr/current/ScriptReference/MaterialPropertyBlock.html) 을 참고하라.

아 그러면 쉐이더는 어디서 정의하냐고? _Graphics.DrawMeshInstanced_ 메소드는 __Material__ 과 __MaterialPropertyBlock__ 둘다 필요하다. 적당히 데이터를 분리해서 취급하면 된다. 아래 그리는 코드를 살펴보자. [InstancedSkinning - CharacterSet](https://github.com/hrmrzizon/InstancedSkinningExmaple/blob/master/Assets/2%20-%20InstancedSkinning/CharacterSet.cs) 에서 간추려서 가져왔다.

``` csharp
Dictionary<CharacterData, DrawData> drawDataDict;

void Update()
{
    var enumer = drawDataDict.GetEnumerator();
<commit_msg>Fix external link<commit_after>---
layout: post
author: "<NAME>"
comments: true
categories:
  - unity
  - shader
  - rendering

---

__이 글은 Unity 5.6.1f 버젼에서 작성되었습니다. 다른 버젼에서는 에러가 날 수 있으니 참고 바랍니다.__

[Using Texture2DArray in Unity]({{ site.baseurl }}{% post_url 2017-06-04-using-texture2darray-in-unity %}) 에 이어 _DrawCall_ 을 줄이기 위한 방법에 대해서 소개하려한다. GPU Instancing 이라는 방법인데 _TextureArray_ 와 같이 응용해서 사용하면 획기적으로 _DrawCall_ 을 줄일 수 있다.   

일반적으로 알려진 _GPU Instancing_ 에 대해서 말하자면 컴퓨터의 RAM 에만 저장하던 데이터들을 GPU 메모리에 복사해놓고 GPGPU 나 쉐이더를 실행할 때 빠르게 데이터에 접근하는 것을 GPU Instancing 이라 한다. 만약 _GPU Instancing_ 을 사용하지 않으면 매번 _DrawCall_ 에 데이터를 넣어줘야하기 때문에 수많은 _DrawCall_ 이 걸리게 되고 이는 CPU 의 시간을 뺏어먹게 되어 영 좋지 않은 일이 된다. 보통은 같은 동작을 하는 오브젝트들을 최적화할 때 쓰인다. 사용하게 되면 _DrawCall_ 이 _O(__오브젝트 갯수__)_ 로 되던것이 O(1) 의 갯수로 줄어든다. 그래서 _TextureArray_ 와 같이 사용하게 되면 _DrawCall_ 이 _O(__오브젝트 갯수__ * __텍스쳐 갯수__)_ 로 계산되던게 _O(__1__)_ 로 바뀌어 버리니 CPU 시간을 엄청나게 많이벌 수 있다. 다만 GPU 메모리를 많이 잡아먹기 때문에 신경써서 데이터를 구성하지 않으면 무슨일이 일어날지 모른다.

<!-- more -->

기술을 써보기 전에 우선 구현 사항부터 생각해야 한다. 필자는 Unity 에서 지원하는 __SkinnedMeshRenderer__ 가 _DrawCall_ 배칭을 해주지 않아 간단한 스키닝을 직접 구현하였다. __SkinnedMeshRenderer__ 가 많은 기능을 지원하긴 하지만 __SkinnedMeshRenderer__ 컴포넌트의 갯수가 절대적으로 많아지고 매터리얼이 늘어나게 되면 어쩔 수 없이 원하는 기능을 붙여 직접 구현해야 한다. [InstancedSkinning](github.com/hrmrzizon/InstancedSkinningExmaple.git)에서 참고할 수 있다.

해야할 것은 두가지다. 쉐이더에서 데이터를 선언 후 직접 사용하는 코드를 짜주어야 하고, 스크립트에서는 필요한 데이터를 모아서 넣어주기만 하면 된다. 말로는 간단하지만 신경써주어야 할것이 많다. 필자 역시 간단하다고 생각하여 시작했으나 꽤 많은 삽질 끝에 성공했다.

_GPU Instancing_ 의 핵심은 GPU 메모리에 어떤 데이터들을 어떻게 옮겨놓고 그 데이터들을 어떻게 사용하느냐가 제일 핵심이다. 스크립트에서는 __MaterialPropertyBlock__ 인스턴스를 통해 데이터를 한꺼번에 세팅하고 _Graphics.DrawMeshInstanced_ 메소드를 호출해 그린다. 보통은 매 프레임별로 _Graphics.DrawMeshInstanced_ 호출하기 때문에 적당히 코딩이 되어있다면 필요할때마다 __MaterialPropertyBlock__ 인스턴스에 데이터를 갱신해주기만 하면 된다. __MaterialPropertyBlock__ 은 쉐이더에 들어가는 정보들을 취급하는 데이터 뭉치(chunk)다. __Material__ 은 쉐이더 정보와 필요한 데이터를 가지고 있는 인스턴스다. 쉐이더 정보를 가지고 있기 때문에 매터리얼의 갯수가 많으면 많을수록 _DrawCall_ 의 갯수가 늘어난다. 하지만 __MaterialPropertyBlock__ 은 __Material__ 과는 다르게 정보만 가지고 있는 것이기 때문에 _DrawCall_ 의 갯수가 늘어나지 않는다. __MaterialPropertyBlock__ 에 관한 자세한 사용법은 [Unity Reference : MaterialPropertyBlock](https://docs.unity3d.com/kr/current/ScriptReference/MaterialPropertyBlock.html) 을 참고하라.

아 그러면 쉐이더는 어디서 정의하냐고? _Graphics.DrawMeshInstanced_ 메소드는 __Material__ 과 __MaterialPropertyBlock__ 둘다 필요하다. 적당히 데이터를 분리해서 취급하면 된다. 아래 그리는 코드를 살펴보자. [InstancedSkinning - CharacterSet](https://github.com/hrmrzizon/InstancedSkinningExmaple/blob/master/Assets/2%20-%20InstancedSkinning/CharacterSet.cs) 에서 간추려서 가져왔다.

``` csharp
Dictionary<CharacterData, DrawData> drawDataDict;

void Update()
{
    var enumer = drawDataDict.GetEnumerator();

{'fraction_non_alphanumeric': 0.05766685573832483, 'fraction_numerical': 0.007941009642654566, 'mean_word_length': 4.2015732546706, 'pattern_counts': {'":': 0, '<': 10, '<?xml version=': 0, '>': 10, 'https://': 4, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5994461', 'n_tokens_mistral': 3740, 'n_tokens_neox': 4571, 'n_words': 776}