Unity3D iOS Porting Tips

2011. 8. 23. 17:28게임 개발/OpenGL

사진에 나온건 the Apple mothership ... 애플에서 만드는 사옥이라고 알고 있습니다. 우주선이 따로 없네요
Gallery : http://www.engadget.com/photos/the-apple-mothership/ 

암튼 각설하시구... 아래 내용은 좀.. 필요할듯 싶어서.. 긁어왔습니다.

원문 출처 : http://roperacket.tumblr.com/post/2544450508/unity-ios-porting
번역본 출처 : http://unity3dkorea.com/bbs/board.php?bo_table=m03_9&wr_id=148


This presentation was given in the 2010 Gaming Unconference, 31 Dec 2010. I hope you find it useful.
December 31, 2010 at 1:51pm 에 작성한 글이니까.. 거의 3년이 다 되어 가지만.. 도움이 될것으로 생각되어 정리 해놓습니다.

 

The following is an incomplete, unsorted bullet list of tips I picked up while porting my Unity3D game ‘Rope Racket’ to iOS (iPhone/iPad).
I will try to keep this list updated as needed, so check back soon

아래의 글은 유니티3D로 만들었던 제 게임 'Rope Racket' iOS(iPhone/iPad)로 이식하면서 얻은 팁들에 대해 대략적으로 정리해놓은, 미완성된 리스트입니다.
아래 리스트를 필요하면 그때그때 업데이트할 예정이니, 종종 들러서 확인해보시기 바랍니다.

 
Physics(물리)

Unity3D uses the highly optimized PhysX physics engine. Normally all operations involving physics should be taken care of automatically by the engine. Here are some points to consider:

Colliders – remove unneeded colliders and combine colliders into one. E.g. an enemy object can typically use one collider.
Overlapping colliders can produce as many as 3 solving iterations per each frame so pay special attention when forcing position on an object with a collider.
Use simpler colliders – sphere is simpler than cube, which is simpler than mesh collider.
Joints chain reaction – a movement on one end of the joint always yields movement on the other end. Using several interconnected joints can produce a chain reaction of consequent movements, and should be used with care.

 

유니티3D는 매우 잘 최적화된 PhysX 물리엔진을 사용합니다.
일반적으로 물리에 관련된 모든 기능들은 이 엔진에 의해 자동적으로 처리됩니다.
하지만 iOS에 적용할 경우 고려해 볼 만한 점이 좀 있습니다 :
충돌자Colliders : 불필요한 충돌자들을 없애고, 하나로 묶으세요.  Ex) 적 오브젝트는 한개의 충돌자만 쓰게끔
 •겹쳐진 충돌자들Overlapping colliders은 매 프레임당 (많으면)3번까지의 반복 처리를 유발할 수 있으므로, 충돌자가 붙은 오브젝트의 위치를 강제로 배치할 경우 특별히 유념하시기 바랍니다.
(씬 에디터 상에서의 수동 배치나, transform값 수정에 의한 배치 등 직접 position값을 주는 경우를 말함 - 역주)
 •좀 더 간단한 형태의 충돌자를 사용 : 메쉬mesh 충돌자보다는 입방체cube, 입방체 보다 구체sphere 충돌자가 더 간단합니다.
 •조인트Joints의 연쇄 반응에 주의 : 조인트 사용시, 한쪽 끝의 움직임은 항상 반대쪽 끝까지의 연쇄적인 움직임을 유발합니다. 서로 연결된 몇개의 조인트를 사용하게 되면 필연적으로 연쇄 반응하는 움직임을 양산하므로, 사용에 주의하시기 바랍니다.

 
General Optimization (일반 최적화)

Code optimization can significantly improve performance. In most cases, inefficient programming is the main blame for unoptimized performance.

Some operations do not need to be called with every frame. E.g. button states, check distances, AI decisions etc. Most actions can be called 10 times per sec without anyone noticing.
Get functions are a neat concept but can be expensive. Declaring a variable as public is crude, but is faster when external access is needed.
transform is also a get function. Cache it in a variable when used repetitively.
To avoid cpu spikes call garbage collector at scheduled intervals with System.GC.Collect();
Precalculate arithmetic tables where possible, or at least cache values when they don’t change (sin/cos/atan/sqrt).

코드 최적화는 성능을 두드러지게 향상시킬 수 있습니다.
대부분의 경우, 최적화가 안돼서 성능이 떨어지는 주된 원인은 프로그래밍이 (애초에) 부적절하게된 점에서 발생합니다.

몇가지 작업들은 굳이 매 프레임마다 돌릴 필요가 없는 경우가 있습니다.
Ex) 버튼의 상태 체크, 거리 체크, AI 전략 판단 등등 이런 동작들 대부분은 초당 10번 정도만 하게끔 바꿔도 아무도 모를 겁니다.
 Get...()류의 함수들은 개념적으로는 좋습니다만, 성능적으로 (처리 비용이) 비쌀 경우가 있습니다.
public 변수 정의로 값을 빼놓는 방법은 지저분해 보이긴 합니다만, 외부에서 값을 억세스할 경우 더 빠릅니다.
 transform도 알고 보면 Get...()류의 함수를 쓰고 있습니다반복해서 사용할 경우 변수 값에다 따로 빼놓으세요.
 종종 CPU 사용량이 비정상적으로 튀는(CPU spikes) 현상을 방지하려면, System.GC.Collect()를 사용, 적절히 정해진 간격으로 가비지 컬렉터를 호출하세요.
 sin/cos/atan/sqrt등의 수학 함수는 가능한 한 미리 계산된 테이블값을 사용하거나, 최소한 자주 변하지 않는 값들을 찾아 변수에 담아놓고 불러다 사용하세요.

 
Graphics (그래픽)

iOS GPU is very different from your PC’s. Some well known caveats:

Triangle count – I’ve read a lot about some hard limits like 7k but my experience shows that it doesn’t change that much in comparison to other factors. Try to keep below 10k per scene if possible.
Memory optimization – change the target maximum texture size per iOS to the smallest possible size without losing details. I found the 256x256 or less is quite sufficient for most objects. You can also change it per each target platform so it looks detailed on PC, and saves on memory on iOS.
Complex and multi-pass shaders are slow on iPhone’s GPU. Toon and cutout shading are examples of shaders that should be avoided if possible.
Lighting is very expensive. In general, every light is another drawing pass for all affected objects. Use one bright directional light. Use other lights, especially spot and point, with caution. Note that additional lights have two wrongs – they take an additional draw pass per each lit item, and initial light-on calculation takes a few milliseconds of stalled application.
Changing vertex position, normals or tangents is expensive and should not be done repetitively. Changing UV (pixel position within texture) is fast, however.
OnGUI is called every frame, plus every event. Furthermore, it does not share materials between subsequent calls to texture or text, even when the same resource is used (e.g. 2 calls for text and its shadow). Avoid if you can.

iOS에서 사용되는 GPU PC에서 사용되는 것과는 많이 다릅니다.
잘 알려진 제약 사항들 몇 가지를 살펴보자면 :

  폴리곤 수
7,000개가 HW 한계라던지(3GS 기준인 듯 - 역주) 같은 글들을 많이 읽어봤습니다만,
경험적으로는 폴리곤 수가 다른 요인에 비해 특별히 더 중요한 영향을 끼친다고 생각되진 않습니다.
한 씬에서 10,000개 이하로 유지하기 위해 가능한 한 노력해보세요.
  메모리 최적화
iOS 플랫폼에서 사용할 최대 텍스쳐 사이즈값Maximum Texture Size for iOS지정을, 디테일을 잃어버리지 않는 한도에서 최대한 줄여보시기 바랍니다.
(해당 Texture Inspector 메뉴 하단에서각 빌드 플랫폼별로 Override for... 체크박스를 켜고 최대 텍스쳐 사이즈 및 포맷 지정 가능. 자세한 사항은 http://unity3d.com/support/documentation/Manual/Textures.html  위 링크에서 Per-Platform Overrides 항목 및 플랫폼별 사용 가능한 텍스쳐 포맷 설명을 참조 - 역주)
제 경우는 대부분의 오브젝트에서 최대 256x256, 또는 그 이하로도 충분했었습니다.또한 이것을 각각의 대상 플랫폼별로도 지정하실 수가 있는데 (위의 역주 참고)PC에선 디테일하게, iOS에선 메모리 사용을 줄이는 식으로 지정할 수도 있습니다.
 iPhone GPU상에서 복잡하고 여러번의 패스multi-pass를 거쳐야 하는 셰이더를 쓰면 느려집니다. 예를 들어 Toon 계열 셰이더라든지, Transparent/Cutout 계열 셰이더는 가능한 한 사용을 피해야 합니다.
 라이팅은 (처리 비용이) 매우 비쌉니다. 일반적으로 조명이 추가될때마다 (영향받는 모든 오브젝트들에 대한그리기 패스drawing pass 또한 추가가 됩니다.밝은 지향성 조명directional light 하나만 사용하세요
다른 조명, 특히 점point 광원이나 스포트라이트를 사용할 땐 주의하시기 바랍니다.조명을 추가하는 데엔 두 가지 문제점이 있음을 기억하세요 -그 조명을 받는 모든 물체에 대해 렌더링 작업이 늘어난다는 점과,그 조명이 켜지는 시점에서  조명 초기화를 위해 몇밀리초를 더 잡아먹게 된다는 점.
 정점vertex의 위치/노말/탄젠트 값을 바꾸는 것은 (처리 비용이) 비싸며, 반복적으로 해선 안됩니다.반면, UV(텍스쳐 내에서의 픽셀 위치) 값을 바꾸는 것은 빠릅니다.
 OnGUI()는 매 프레임마다, 추가적으로  매 이벤트마다 호출됩니다. 게다가, 연속되는 OnGUI() 내에서 같은 리소스, 매터리얼을 반복 사용하더라도
이를 공유해서 최적화해준다던가 하지 않습니다.(예를 들어, 텍스트와 그 그림자를 그리면 그대로 2번의 그리기 호출2 calls이 발생) 그러니 가능한 한 사용을 피하세요.

Draw Calls(그리기 호출)

A Draw Call is one or more objects, sharing a single material on a single camera, in a single shader pass, being placed on screen.
My experience proves that the most important element to optimize is to reduce the number of draw calls, even in high-end mobile device such as iPad. Apparently the pipeline between the CPU and GPU is not as efficient as in non-mobile devices.

Make sure Dynamic Batching is checked.
When checked, the engine groups all objects by their shared material, up to a maximum number of vertices per batch. Make sure that member sharedMaterial is used instead of material, whenever applicable.
Furthermore, all batched objects must share queue pass. Each camera has multiple drawing passes. If a few materials share the same pass, there is no guarantee that all objects using the same material will be batched. Therefore you can help Unity by placing a material in a particular pass by changing its pass order.
For example, shader directive Tags {“Queue” = “Transparent-1” }
will make sure that this shader is drawn right after Transparent pass. So setting Transparent-1 separates this new material from normal Transparent shaded materials, and batches all objects using this material into one pass.
Note: This trick also helps when trying to sort depths and fix Z issues.
Most importantly: Use one material (one texture) for as many objects as you can. For example, use an icon atlas and change UV position according to the desired image.
Alternative solution: load a font and export its texture. Place all your images on the exported font texture instead of the original letters. Then, use Text Mesh and change the letters to display the proper icon.


그리기 호출Draw Call (카메라를 통해 한 프레임의 화면을 만들어내는 동안) 하나의 셰이더 패스, 하나의 매터리얼로 오브젝트를 한 번 그리는 작업의 단위를 말합니다.
(같은 셰이더/같은 매터리얼을 갖는 여러 개의 오브젝트라도 한번의 draw call 작업에서 그릴 수 있음 - 역주)

iPad같은 하이엔드급 모바일 장치에서조차, 이 그리기 호출Draw Call 수를 줄이는 일이
제 경험상 최적화에서 가장 중요한 요소라고 생각이 됩니다. 확실히 모바일 장비는 비-모바일 장비non-mobile devices에 비해 CPU GPU간의 파이프라인이 그리 효율적이지가 않습니다.

동적 배칭Dynamic Batching 기능이 켜져있는가 확인하세요 (File 메뉴 > Build Settings... 메뉴 > Player Settings 버튼 >
 Inspector창에서 Other Settings 항목 > Rendering 항목 밑에 Dynamic Batching 체크박스 선택.
 자세한 설명은 http://unity3d.com/support/documentation/Manual/iphone-DrawCall-Batching.html  위 링크에서 Dynamic Batching 부분을 참조 - 역주)
이것이 켜져있으면, 게임 엔진은 모든 오브젝트를 공유된 매터리얼 단위로 묶습니다.
 이 경우, (스크립트에서 건드릴 땐) material 멤버 대신 sharedMaterial 멤버를 사용하도록 해야 합니다.
나아가, 배치 처리될 모든 오브젝트all batched object는 같은 셰이더 큐 패스queue pass를 공유해야 합니다.
각각의 카메라는 여러 개의 그리기 패스drawing pass를 가지고 있습니다. 몇몇 매터리얼이 같은 그리기 패스를 공유하고 있다 하더라도, 같은 매터리얼을 사용하는 모든 오브젝트가 한번에 배치 처리된다는 보장은 없습니다.
그러므로, 매터리얼의 셰이더 패스 순서pass order를 직접 지정해줌으로써 그 매터리얼이 특정 그리기 패스 안에서 동작하도록 강제할 수 있습니다.
예를 들자면, 셰이더 명령어 Tags {"Queue" = "Transparent-1" } 을 사용하면 해당 셰이더가 Transparent pass 직후(*1*)에 동작하도록 강제할 수 있습니다. 이렇게 Transparent-1로 셋팅하게 되면, 이 새로운 매터리얼은 기존의 Transparent 셰이더와 분리되며,
이 새로운 셰이더를 사용하는 모든 오브젝트를 묶어 한 패스 안에서 배치batch 처리하게 됩니다. : 이 방법은 깊이값 소팅이나 Z값 관련 문제를 해결하는데도 도움이 됩니다.
(이 항목의 내용은 제가 셰이더를 공부 안해서 제대로 번역이 되었는지 확신이 없네요 - 역주)
가장 중요한 사항: 가능한 한 많은 오브젝트를 한 매터리얼(한 장의 텍스쳐)로 묶어야 합니다.예를 들어, 한장의 아이콘 맵icon atlas을 만들고 필요한 상황에 따라 UV위치값을 바꿔가며 쓰는 방법이 있습니다.
 대안책: 폰트를 하나 읽어들이고 이 텍스쳐를 export합니다찍고싶은 이미지를 조각내서 이 export된 폰트 텍스쳐 위치에 갖다 붙입니다.
그리고, Text Mesh를 사용해서 이것들을 가져다 적절한 아이콘으로 사용합니다.


 
More Tips

Here are some handy non-optimization tips:

Instead of OnGUI or GUITexture, display all GUI in a new camera.
◦Create a new camera for GUI: isometric, ignores all other layers, Depth 1 (to draw after main camera) and Don’t Clear.
◦Use Text Mesh and Mesh Renderer to draw text. Make sure that the GameObject is set to the GUI camera layer. This is a very fast and versatile to use dynamic, localized text in Unity. Text can be changed, rotated, scaled and a user-defined material can be used instead of standard font.
◦Similarly, logos and images can be displayed on boxes. The triangle cost is minimal and on this isometric camera, the side walls are culled.
When transparent image is used, either use a shader with Culling On so the back wall is removed, or place the box very close to the camera so the front wall is behind the camera and only the back wall is shown.
White border around textures is caused when a zero-alpha color partly bleeds into a visible pixel. In this case, the RGB part of that supposedly transparent pixel is taken into effect. To fix it in Photoshop:
◦Select all zero-alpha pixels
◦Paint around the edges of the images with the edge color (the select region will protect the image)
◦Create Layar Mask -> Hide selected area.
The result is an image where the RGB part of zero-alpha pixels matches the edges of the texture and bled pixels look nice.

More tips to follow soon.

최적화와는 관계없지만 몇가지 편리한 팁들이 더 있습니다 :
 
OnGUI() GUITexture를 사용하는 대신, 별도의 카메라로 모든 GUI를 그려주기
◦GUI를 위한 별도의 카메라를 만듭니다
: 평행isometric 카메라로 지정하고, 별도로 만든 GUI 오브젝트 레이어 이외의 모든 다른 레이어를 끄고,
  (메인 카메라보다 나중에 찍도록) Depth값을 1로 주고, Clear flag Don't Clear로 맞추세요.
 ◦텍스트를 찍기 위해선 Text Mesh Mesh Renderer를 사용합니다.
만들어진 GUI GameObject들은 별도로 만든 GUI 오브젝트 레이어 쪽으로 넣어줍니다.
이렇게 하면 유니티에서 현지화된 텍스트를 사용할때 매우 빠르고 융통성있게끔 만들 수 있습니다.
이런 덱스트는 변경 가능하고, 회전, 확대/축소할 수도 있으며 기본 폰트대신 사용자 정의된 매터리얼로 대치할수도 있습니다.
비슷한 형태로, 로고나 이미지는 박스를 이용해 찍을 수 있습니다.
평행isometric 카메라 상에서 상각 폴리곤을 찍는 처리비용은 거의 미미하며, 불필요한 옆면 등은 알아서 무시됩니다.
투명값을 가진 이미지를 사용할때는, 뒷면이 없어지게끔 컬링이 적용된 셰이더를 사용하거나 박스 자체를 카메라에 아주 가깝게 가져와서, 박스 앞면은 카메라 평면 뒤로 보내고 박스 뒷면만 보이게 합니다.

 (투명값을 갖는 텍스쳐의 경우) 텍스쳐 테두리의 흰색 띠가 보일 경우는 알파값 0를 가진 픽셀 색이 주변으로 일부 번지기 때문에 발생합니다.
이 경우, 테두리의 주변 RGB값들이 영향을 미치고 있는 것인데, 포토샵에서 이를 수정하려면 :
알파값 0 인 배경 픽셀들을 전부 선택한다
이미지 테두리를 따라 원하는 경계선 색을 다 칠해준다 (이미지 안쪽은 선택 영역이 보호해준다)
◦Layar Mask를 만들어, 방금 칠해준 선택 영역을 hide 시킨다
 
결과적으로 알파값이 0인 픽셀들도 원하는 테두리 색을 가지게 되어 보기 좋게 섞여진다.

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
*1*
원문에는 will make sure that this shader is drawn right after Transparent pass. 라고 되어 있어서
원 저자의 의도대로 'Transparent pass 직후' 라고 번역이 되었습니다만...
http://unity3d.com/support/documentation/Components/SL-SubshaderTags.html
을 보시면, 각 렌더 패스마다 내부적으로 1000단위의 숫자가 매겨져 있고, Transparent의 값이 3000이니
Transparent-1 패스의 경우는 2999가 되어, 실제론 'Transparent pass 직전'이 맞습니다.

하지만 원문 내용은 Transparent pass 앞이냐 뒤냐가 중요한게 아니고, 저런 식으로 태그를 부여해서 원하는 drawing 작업을 분리된 하나의 drawing pass로 통합하는 방법을 보여주는 것이니
 
틀린 내용이라 하더라도 원 저자의 문맥대로 일단 번역하고 별도로 각주를 남깁니다.
* 원문에 이렇게 애매모호(?)한 부분이 두어 군데 더 보입니다만, 마찬가지로 패스... *
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

END