-
Translate Gamedev Structured Buffer Vs Constant Buffer
GameDev : structured buffer vs constant buffer의 답변 해석 글이다.
-
Structured Buffer Vs Constant Buffer
CG 로 쉐이더 코딩을 하기 위해 여러 소스와 웹페이지를 뒤지던 도중 재미있는 글을 발견했다. HLSL 에서 사용하는 StructuredBuffer 와 Constant Buffer 의 차이에 대한 글이였다. Unity 메뉴얼을 따라가면서 몇번 보긴했지만 무슨 차이인지도 모르는 것들이였다. 하지만 알고나니 GPU Instancing 에 대한 기본적인 상식이기에 글을 쓴다. 우선 두가지를 먼저 간단하게 알아보고 두 개념의 차이에 대해서 알아보자.
-
Using Gpu Instancing In Unity
이 글은 Unity 5.6.1f 버젼에서 작성되었습니다. 다른 버젼에서는 에러가 날 수 있으니 참고 바랍니다.
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 메모리를 많이 잡아먹기 때문에 신경써서 데이터를 구성하지 않으면 무슨일이 일어날지 모른다.
-
Using Texture2darray In Unity
Unity 에서 렌더링에 관련된 최적화를 할때는 TextureArray 를 사용할 수 밖에 없다. 이는 Unity 에서 DrawCall 을 줄이기 위해 써먹는 Batching 이라는 개념 때문인데 단순하게 말하면 그리는 새로운 매터리얼과 메쉬의 종류가 많으면 많을 수록 DrawCall 을 많이 하게 된다. 하지만 이 DrawCall 의 비용은 싼편이 아니기 때문에 CPU 의 성능을 꽤나 잡아먹게 된다. 그래서 Unity 는 자동으로 Batching 을 해주게 된다. 같은 메터리얼을 쓰면 자동으로 묶어주고, 같은 메쉬를 쓰면 또 자동으로 묶어준다. 결국 Batching 이 DrawCall 의 횟수와 같은 개념이 되는 것이다.
그래서 Batching 의 횟수를 줄이기 위해 매터리얼을 줄이는 방법에 대한 것이 TextureArray 다. 이것보다 일반적으로 알려진 기법은 TexutreAtlas 인데, 이 방법은 상당히 단순하다. 그냥 텍스쳐 한장에 모든 그림을 때려박고 UV 를 수정해주는 작업을 할때 쓰인다. 보통은 UI 이미지에서 스프라이트를 설정할 때 쓰이며, Unity 는 UGUI 기능에 Sprite 들을 합쳐서 TextureAtlas 로 만들어주는 기능이 있다. 하지만 3D 오브젝트의 UV 에서는 말이 조금 달라진다. UV 좌표는 0과 1사이의 값으로 이루어지는데 텍스쳐 여러장과 세팅되어 있던 UV 좌표들을 한장으로 통합해 다시 세팅하려면 굉장히 귀찮아진다. 그리고 합쳐지기전의 텍스쳐의 갯수가 합쳐진 후에 추가된다면 그것또한 굉장히 귀찮아질 것이다. 결국 생산성의 문제가 된다. 그래서 다른 방법을 쓸 수 있는데, 이 방법이 바로 TextureArray 다.
TextureArray 의 개념은 단순하게 텍스쳐를 배열로 묶은 것으로, 인덱스만 있으면 그냥 하나하나 참조하여 사용가능하다. 즉 UV 의 2차원 좌표와 함께 인덱스 한개만 더 있으면 된다. 그리고 TextureArray 의 장점은 TextureAtlas 마냥 합쳐주고 UV 를 수정할 일이 없고, 메쉬별로 인덱스를 따로 설정해주는 작업만 해주면 상당히 편하게 할 수 있다. 또한 텍스쳐 갯수가 몇개가 되던간에 메터리얼을 한개로 유지할 수 있기 때문에 굉장히 편하다. 근데 Unity 에서 사용하려면 몇가지 단점이 있다. Asset 생성을 지원하지 않기 때문에 굉장히 불편하고, 보여주는 GUI 또한 Unity 내부에서 지원하지 않는다. 편하게 사용하기 위해선 에디터 코드를 직접 만져야 한다. 물론 직접 생성해주는 것도 상관없지만 생산성 자체만 놓고보면 그다지 좋은 편은 아니다. 또한 Shader 코드들도 직접 바꿔주어야 하기 때문에 이것저것 세팅해줘야 할것이 많다. 즉 사용하기에 비용이 많이 든다.
이제 직접 Unity 에서 적용시켜보자.
-
Simple Shader Programming
이전에 쓴 글(handling uvs and material)에서 쉐이더에 대한 언급을 한적이 있다. 간단하게 전체적인 의미와 역할에 대해서 설명했었다. 이 글에서는 조금 더 자세하게 알아보고 CG 를 이용해서 직접 다루는 방법에 대해서 알아보겠다.
3D 오브젝트는 GPU 에서 특정한 연산을 하여 화면상에 실제로 그려진다. 예전에는 그리는 방식이 정해져 있어 그 방식에 맞추어 데이터를 넣어주면 GPU 와 Graphics API 가 알아서 3D 오브젝트를 그렸었다. 하지만 기술은 점점 발전하여 프로그래머들이 직접 많은것을 제어할 수 있게 되었고 현재는 꽤 많은 것들이 가능하게 되었다. 그 발전속에서 나타난 것이 쉐이더다. 쉐이더는 3D 오브젝트를 그리는 방식을 적어놓은 코드라고 할 수 있다.
3D 오브젝트를 그리는 쉐이더 코드는 두가지로 나뉘는데, 하나는 vertex 를 처리하는 과정 또 하나는 pixel 자체를 처리하는 코드로 나뉜다. 이 두가지 과정을 잘 처리하면 게임에서 원하는 연출과 성능 두가지 토끼를 잡을 수 있다. 물론 잘하기 힘들다. 그래서 두 방법에서 프로그래머가 직접 코드를 짜서 넣으면서 게임의 그래픽을 원하는대로 커스터마이징이 가능하게 되었다. 이로써 꽤 많은 것을 실현 가능하게 되었었다. 하지만 이게 다가 아니였다.
쉐이더를 사용한 AAA급 3D 게임들과 함께 GPU 도 격렬하게 발전했다. 발전한 만큼 GPU 의 퍼포먼스는 점점 괴물이 되어가고 그 과정에서 vertex shader 와 pixel shader 를 단순하게 그리는 것에만 사용하는 것이 아니라 다른 계산이 필요한 곳에 써먹기 시작했고 편법을 사용한 많은 기술이 나왔었다. (vtf) 그렇게 프로그래머의 니즈를 파악한 GPU 제조사는 다른 기술을 개발한다. 이름하여 GPGPU 라는 이름의 기술인데 풀어 쓰면 “general purpose computing on graphics processing units” 이다. GPU 상의 범용 계산 이라는 뜻이다. 즉 위에서 언급한 병렬 계산이 가능한 것들을 편법을 쓰지말고 직접 이 기술을 사용해서 사용하라는 것이다. 이 GPGPU 기술이 나오면서 GPU 의 하드웨어적인 퍼포먼스에 따라 엄청 많은 것들을 가능하게 되었다. GPGPU 를 통해 불편했던 편법을 사용하던 기법들이 변형되어 쏟아져 나왔으며 새로운 기술 또한 엄청나게 쏟아져 나왔다. 그리고 그 기술들은 일반적으로 알려진 3D 그래픽이 차용된 AAA 급 게임들에 사용되어 일반 사용자들은 엄청난 그래픽을 자랑하는 게임들을 경험할 수 있게 되었다. 또한 최근에 AI 기술이 대두되면서 GPGPU 가 더욱더 각광받게 되었다.
이렇게 우리에게 다가오는 것은 꽤 많은 게임들의 발전인데, 다만 우리가 이 게임들의 기술에 접근하려면 꽤 많은 지식과 발상의 전환이 필요하다. 쉐이더만 하더라도 쉐이더 코드는 컴파일되어 GPU 에서 실행된다. CPU 에서 실행되는 일반적인 코드와 조금 다른 점은 CPU 에서 처리되는 것은 멀티스레딩을 하지 않는 이상 상당히 선형적인 코드를 짜게 되고 GPU 에서 돌아가는 쉐이더 코드를 짤 때는 병렬(parallel) 환경에서 돌아가게 짜야한다. 쉐이더 코드를 짤 때 첫번째로 겪게되는 어려움은 이것이다. 쉐이더까지 건드리게되면 경험이 어느정도 있는 상태일텐데, 개념을 조금 깨부수고 아예 병렬적으로 코드를 짜야하니 적응하는 것에 시간이 꽤나 소모된다.
Unity 에서 Shader 를 직접 만들어 사용하는 것에 대하여 알아보자.
-
Handling Rig And Skinning
앞서 오브젝트들을 그리는 방법에 대해서 알아보았다.(hnalding vertices, handling uvs) 폴리곤을 그리고 색을 칠하는 방법이었다. 하지만 이런 기능만 가지고 게임을 만들기에는 약간 부족하다. 보통 게임을 만들때 케릭터들의 부드러운 움직임을 표현해야 한다. 2D 게임은 보통 그림을 여러장을 그려서 움직이게 보이게 한다. 하지만 3D 게임에서의 부드러운 움직임은 2D 게임의 표현과는 다르게 표현한다. 일단 부드럽게 움직여야할 단위가 다르다. 메쉬의 정점들을 부드럽게 움직여야하기 때문에 2D 게임의 움직임과는 다른 무언가가 필요하다.
2D 게임에서 그림을 한꺼번에 움직이는 것처럼 단순하게 메쉬 전체를 부드럽게 움직여서 해결되면 좋겠지만 이 방법은 조금 문제가 있다. 관절같은 접합 부분에서 부드럽게 처리해야 하는 부분 즉 어떤 정점만 부드럽게 움직여야하는 문제가 있다. 그래서 고안된 방법은 특정한 위치를 설정해서 그 위치를 기준으로 정점들을 움직여주는 방법이다.
언급한 특정한 위치를 Bone : 뼈라고 한다. 뼈를 움직여서 정점들을 직접 움직이는 것이다. 그리고 뼈를 기준으로 움직이는 것을 Skinning 이라고 한다. 사람의 뼈가 움직이면 피부도 따라서 움직이듯이 피부를 직접 설정하는 것을 Skinning 이라고 하는 것이다. 그리고 Bone 의 위치도 상당히 중요하다. 자연스러운 움직임을 만들려면 만들어진 메쉬에 잘 맞게 위치를 설정해주어야 하기 때문이다. 위치 뿐만아니라 여러 움직이는 범위나 뼈의 계층 구조를 잘 설정해주어야 자연스러운 움직임을 나타낼 수 있다. 이러한 작업을 Rigging 이라 한다. 보통 3D 오브젝트를 만들고 Rigging 과 Skinning 을 하는 작업은 그래픽 아티스트가 직접 해주지만 우리는 이 과정을 이해해야 하기에 Unity 에서 직접 만들어 볼 것이다.
-
Handling Uv And Material In Unity
Handling vertices and indices 글에서 Unity 에서 정점과 인덱스를 사용해 물체를 그리는 방법에 대해서 알아보았다. 그런데 뭔가 설정해야 할것들이 빠진 것처럼 보인다. 실제로 그려지는 모습은 Unity 에서 아무것도 설정이 안되어 있을 때 나오는 분홍색으로 전부 칠해져 있다. 일반적으로 게임에서 나오는 3D 물체들은 전부 색이 칠해져 있거나 그림이 그려져 있다. 거기다가 빛을 받아서 반짝반짝이기도 할때도 있다. 이번 글에서는 3D 오브젝트에 색을 입히거나 그림을 입히는 방법에 대해서 알아보자.
-
Mesh Components In Unity
Unity 에서는 Mesh 를 활용하기 위해 몇가지의 컴포넌트를 지원한다. 간단하게 알아보자.
Mesh 를 가지고 있는 컴포넌트 : MeshFilter
이 컴포넌트는 Unity 컴퓨넌트가 아닌 Mesh 클래스의 인스턴스를 가지고 있는 목적으로 만들어진 클래스다. Mesh 의 인스턴스를 보관하고 외부에서 Mesh 인스턴스에 접근할 수도 있다. 다만 조금 유의해야할 사항은 사용법이다. MeshFilter 문서를 보면 사용할 수 있는 프로퍼티가 두개가 있는데 하나는 MeshFilter.sharedMesh 와 MeshFilter.mesh 두개가 있다. MeshFilter.sharedMesh 는 실제 가지고 있는 Mesh 인스턴스이고 MeshFilter.mesh 는 원래의 인스턴스를 복사해 새로 생성한 것을 반환하기 때문에 주의해야 한다.
Mesh 를 통해 그리는 컴포넌트 : MeshRenderer
MeshRenderer 컴포넌트는 Mesh 인스턴스와 등록된 Material 들을 통해 화면상에서 실제로 보여주는 역할을 하는 컴포넌트다. 같은 GameObject 안에 있는 MeshFilter 를 통해 Mesh 인스턴스에 접근한다. 또한 여러 옵션들을 통해 렌더링을 제어할 수 있다. 중요한 기능은 그림자를 받는 기능과 그림자를 생기게 하는 기능이다. 그 외에도 Unity 에서 지원하는 여러 옵션을 설정할 수 있다. 그리고 여러개의 Material 들을 가지고 있을 수 있는데 Mesh 의 submesh 별로 Material 을 매칭해주어야 알맞게 그릴 수 있다. 기본값은 한개이므로 특별히 세팅을 안했다면 한개씩만 넣어주면 된다.
SkinnedMeshRenderer
위에서 설명한 MeshRenderer 와 이름이 매우 비슷하다. 앞에 Skinned 라는 키워드만 붙어있다. 이름은 비슷하지만 Unity 안에서 처리되는 것은 조금 다르다. MeshRenderer 는 정점이 실시간으로 움직이지 않는 것들을 대상으로 그리는 컴포넌트다. 하지만 SkinnedMeshRenderer 는 다르다. 이 컴포넌트도 Mesh 를 그리기 위해 만들어진 컴포넌트지만 특정한 Bone 을 기준으로 위치를 전부 계산하고 그려야 한다.
특정한 Bone 을(Unity 에서는 Bone 한개마다 GameObject 하나로 나타낸다.) 기준으로 정점들을 움직이게 하게 해주는 작업을 Rigging 이라고 하는데 Rigging 이 적용된 것을 그릴려면 SkinnedMeshRenderer 컴포넌트를 붙여 주어야 한다. MeshRenderer 를 사용하면 Bone 을 움직여도 움직임이 적용이 안된채로 그려져서 말짱 꽝이 되버린다.
MeshCollider
충돌 감지를 Mesh 를 활용해서 하는 컴포넌트로 일반적으로는 안쓴다. 폴리곤의 갯수가 많으면 많을수록 체크에 병목이 생기기 때문이다. 상황에 따라 폴리곤이 적은 경우에는 써도 무방하다. 이 컴포넌트는 생성될 때 MeshFilter 컴포넌트가 존재하면 sharedMesh 를 통해 Mesh 인스턴스에 접근한다.
이렇게 Mesh 를 활용하는 여러가지 컴포넌트들에 대하여 알아보았다. 할말은 많지만 간단한 소개를 위해 쓰여졌기에 여기까지 하겠다.
참조
-
Handling Vertices And Indices In Unity
여태까지 많은 게임들은 Graphics API 를 사용하여 만들어졌다. 1992년에 OpenGL 의 첫버젼이 릴리즈 되었고 이어서 1995년에 DirectX 가 Windows Game SDK 안에 포함되어 릴리즈 되었다. 그 이후로 수많은 게임들이 이 두가지의 Graphics API 를 사용하여 개발되었다. 다만 Graphics API 를 직접 사용하려면 꽤 많은 배경지식과(선형대수학, Graphics 이론 등) 해당 Graphics API 에 대한 경험이 많이 필요했다. 즉 일반적인 프로그래머들이 접근하기 조금 어려운 분야였다. 하지만 이를 꽤 뚫어본 많은 사람들이 게임을 만들기 위한 소프트웨어 이른바 게임 엔진이라는 소프트웨어를 개발하면서 널리 퍼지게 되었고 요즘에는 많은 지식 없이 게임을 만들 수 있게 되었다.
하지만 프로그래머로써 성장하려면 한계단씩 내려가 보면서 원리를 깨우쳐야 한다. 특히 게임 클라이언트 프로그래머는 결국 Graphics API 를 활용한 프로그램을 짜는 것이기 때문에 지식이 없으면 없을수록 난항을 겪기 마련이다. 수학적인 지식이 부족하면 직접 계산하는 코드를 짤수가 없고, Graphics API 의 구성을 모른다면 최적화를 할때 하나하나 삽질해가며 바꿔보아야 한다.
이 게시물에서는 Graphics API 를 공부하면 처음 나오게는 지식들(정점, 폴리곤, UV)에 대해서 알아보고 Unity 에서 이 지식들을 시험해보겠다.
-
Unity Project Setting For Git
꽤 많은 사람들이 Git 을 사용한다. SVN 보다 더 널리 알려지고 유용하게 쓰이는 VCS 로써 굉장히 많이 쓰이는 시스템이다. Unity 를 사용할 때도 Git 을 이용해 버젼 관리를 할 수 있는데, 아무런 세팅없이 사용하기엔 조금 문제가 있다. 보통 대두되는 문제는 두가지다.
첫번째는 Git 을 쓰다보면 느끼게 되는데, Git 자체는 텍스트로 구성된 데이터를 취급하려고 만들어졌기 때문에 바이너리 데이터에 대한 솔루션이 없었다. 만약 큰 바이너리 파일이 존재하면 커밋마다 계속 스냅샷을 갱신하기 때문에 커밋에 쓰이는 데이터는 기하급수적으로 늘어나게 된다. 보통 텍스쳐나 영상을 가지고 있게 되면 위의 상황에 부딫친다. 두번째는 조금 귀찮은 경우다. Unity 는 자체적으로 여러 데이터들의 확장자를 지정하여 파일을 사용하는데 커밋을 병합(merge) 할 때 Unity 에서 지원하는 파일에 충돌이 생겨 직접 손봐주어야 할 때, 일정 형식에 맞추지 않으면 끔찍한 사태가 일어나게 된다. 문제가 대표적으로 생기는 파일은 씬(.scene) 파일이다.
여러가지 세팅을 해주어야 하니 차근차근 살펴보자.