'분류 전체보기'에 해당되는 글 81건

  1. 2020.03.14 블로그는 더 이상 업데이트 하지 않아요 ㅠ 1
  2. 2018.12.23 3D 메시 렌더링을 위한 버텍스 트랜스폼 (Vertex Transform)
  3. 2018.12.15 ASTC : 가성비 좋은 텍스처 압축 포맷
  4. 2018.12.15 LWRP(LightweightRenderPipeline) : 렌더링 성능을 위한 경량 파이프라인
  5. 2018.11.03 텍스쳐의 Read/Write Enabled 1
  6. 2018.10.30 유니티의 Mixed Lighting 모드 (라이트맵 모드)
  7. 2018.08.03 유니티 안드로이드 빌드 환경 설정 가이드 5
  8. 2018.07.10 유니티 최적화 관련 문서들 모음집 (Documents of optimization for Unity) 5
  9. 2017.09.30 Unity OVR Input 2
  10. 2017.08.16 나만 안써 진짜 사람들 메모리 프로파일러 다 쓰고 나만 안써
  11. 2016.10.14 유니티 Best Practices - Resources 폴더
  12. 2016.08.31 Debug.Log() 사용 시 주의점 1
  13. 2016.08.31 등짝을 보자 - 단편 수필? ㅋ
  14. 2016.08.31 유니티로 배우는 게임 수학 (한빛) 1
  15. 2016.08.31 The Game Graphics : 모델링과 텍스쳐링 #1 - 비엘북스
  16. 2016.08.31 The Game Graphics : 유니티 언리얼 VR - 비엘북스
  17. 2016.08.31 그래픽 최적화로 가...가버렷! (부제: 배치! 배칭을 보자!) , Batch! Let's take a look at Batching! (Unite Seoul 2016)
  18. 2016.08.31 유니티 그래픽 최적화, 어디까지 해봤니 (Unite Seoul 15, NDC15)
  19. 2016.08.31 스티브잡스처럼 프레젠테이션하기
  20. 2016.08.31 유니티의 라이팅이 안 이쁘다구요? (A to Z of Lighting) (CGC2014)
  21. 2016.08.31 쿠킹스타 제작 사례로 보는 COCOS 2D-X 소개 (KGC2013)
  22. 2016.08.31 디퍼드 렌더링 케이스 스터디 (KGC12)
  23. 2016.08.31 PC에서3D 입체 영상 게임 개발하기 (KGC11)
  24. 2016.08.31 optimize Modern GPU
  25. 2016.08.31 DOF
  26. 2016.08.31 SSAO
  27. 2016.08.31 Inferred lighting
  28. 2016.08.29 G-buffer Normal을 R16FG16F 포맷으로 사용 시 Z 부호 문제 1
  29. 2016.08.29 디퍼드 랜더 시스템 개략( Forward, Deferred, Light Prepass, Inferred)
  30. 2016.08.29 Compact Normal Storage for Small G-Buffers (from Aras)
336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.
Posted by ozlael
,
336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.

버텍스 트랜스폼 (Vertex Transform)


버텍스 데이터들은 GPU 메모리에 저장되어 있습니다.렌더링을 수행해야하는 시점이 되면 GPU는 메모리로부터 버텍스 정보들을 가져옵니다. 그리고 적절한 위치에 그려주기 위해서 위치 변환 즉 트랜스폼(transform)을 수행해주게 됩니다. (명사형으로 트랜스포메이션(Transformation)이라고 표현하기도 합니다.)


저장되어 있는 버텍스 데이터는 특정 위치가 반영된 데이터가 아닌 메시 형태만 반영되있는 데이터입니다. 하나의 메시가 여러 오브젝트 즉 인스턴스에서 사용되고 인스턴스마다 위치가 다릅니다. 또한 매번 위치가 바뀌기 때문에 원본 메시 데이터에 3D 공간상의 위치를 반영해놓을 수는 없고, 3D 공간의 특정 위치가 반영되지 않는 원점 기준의 메시 모델에 대한 위치 데이터만 가지고 있는 것입니다. 이러한 데이터를 로컬 스페이스(local space)에 존재한다고 표현합니다.





이를 3D 공간상의 특정 위치에 위치시켜줘야 합니다. 만일 오브젝트의 인스펙터에서 트랜스폼 컴포넌트의 position이 -4.5,0.7,3 으로 설정되어 있고 rotation y가 55으로 되어있다면 메시 데이터를 -4.5,0.7,3으로 위치시켜주고 y축으로 55도 회전시켜줘야합니다.

이러한 식으로 월드 스페이스(world space) 혹은 월드 좌표계(world coordinates)로 변환시켜주는 것을 월드 트랜스폼(world transform)이라고 표현합니다.

이 월드 트랜스폼 시에는 메시의 모든 버텍스의 위치와 노멀(Normal)(경우에 따라서 탄젠트(Tangent) 및 바이노멀(Binormal) 데이터 포함)을 월드 트랜스폼 시켜줍니다. 즉, 모든 버텍스의 위치와 노멀 정보가 로컬 스페이스에서 월드 스페이스로 변환되는 과정이 월드 트랜스폼인 것입니다.




월드 스페이스로 변환을 해준 뒤에는 카메라 스페이스(Camera Space)로 또 변환을 해줘야 합니다. 월드 트랜스폼을 거쳐서 모든 오브젝트의 버텍스들이 원래 있어야 할 위치로 위치하게 되었지만 , 이 오브젝트들은 카메라를 통해서 디스플레이가 됩니다. 카메라는 원점(x:0,y:0,z:0)에만 있는 것이 아니고 카메라 역시 고유의 위치와 방향을 가지고 있습니다. 그렇기 때문에 오브젝트의 버텍스들을 카메라에 상대적인 위치로 변환을 해줘야 합니다. 이러한 공간을 카메라 스페이스(Camera Space) 또는 뷰 스페이스(view space)라고 부르고, 뷰 스페이스로 변환하는 과정을 뷰 트랜스폼(view transform)이라고 합니다.




오브젝트들은 3차원 공간에 있지만 최종적으로 오브젝트가 렌더링되어 디스플레이 되는 공간은 2차원입니다. 3D 공간을 2D상의 위치로 매칭시켜주는 과정을 프로젝션(투영,projection)이라고 합니다. (빔프로젝터에서 빛을 쏘아서 스크린에 투영시키는 모습을 연상하면 됩니다.)


이러한 프로젝션 트랜스폼 과정에서는 원근법도 적용이 됩니다. 사람이 세상을 볼 때 멀리 있는 것은 작게 보이고 가까이 있는 것은 크게 보입니다. 또한 거리가 멀어질 수록 많은 사물들이 시야에 들어오게 됩니다. 카메라를 통해서 볼때도 이 역시 마찬가지며 유니티 카메라에서는 뷰 영역을 나타내는 프라미드 볼륨을 확인할 수 있습니다. 이를 뷰 프러스텀(view frustum)이라고 합니다.


이러한 투영을 퍼스펙티브 프로젝션(원근 투영, perspective projection)이라고 부릅니다.

cam_pers.png


또한, 원근법이 적용 된 퍼스펙티브 프로젝션 외에도 원근법이 제거 된 오쏘그래픽 프로젝션(orthographic projection, 직교 투영)도 존재합니다. 카메라와의 거리와는 무관하게 시각적으로 크기의 변화가 없이 보여집니다.

cam_ortho.png


카메라 투영에 관한 설정은 다음 비디오에서 자세하게 확인할 수 있습니다 : https://youtu.be/52g_3CX7avo?t=4m2s



유니티의 쉐이더를 작성할 시 UNITY_MATRIX_MVP 변수는 이러한 변환을 의미합니다. UnityShaderVariables.cginc 파일에 기술되어 있습니다.


더 많은 내용은 "유니티 그래픽스 최적화 스타트업"을 참고하세요








Posted by ozlael
,
336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.


ASTC(Adaptive Scalable Texture Compression)


ASTC(Adaptive Scalable Texture Compression)는 ARM에서 개발하고 2012년에 크로노스 그룹에서 OpenGL ES에 표준으로 지정되었습니다. ASTC는 PVRTC나 ETC와 마찬가지로 손실 블록 기반 텍스처 압축 알고리즘입니다.


ASTC는 용량과 품질에 대한 트레이드오프(Trade-off)가 가능합니다. ETC나 PVRTC는 bpp가 한정되어 있는 반면,  ASTC는 8bpp에서 0.86bpp까지 품질을 향상시킬 수 있습니다. 이러한 조절이 가능한 것은 가변 블록 크기를 사용하기 때문입니다. ETC와 PVRTC는 압축되는 블럭의 크기가 4x4로 고정되어 있습니다. 반면, ASTC는 압축되는 기준 블럭의 크기를 4x4에서 12x12 블럭으로 다양하게 선택을 할 수 있습니다. ASTC는 블럭 당 128비트를 사용합니다. 128비트를 4x4블럭에 할당하면 128/16=8bpp가 되는 것이고, 12x12 블럭에 할당하면 128/144=약0.89bpp가 되는 것입니다.

아래 이미지는 좌측으로부터 각각 8bpp, 3.56bpp, 2bpp의 결과를 보여주고 있습니다.

<이미지 출처 : 위키피디아 >


유니티에서도 텍스쳐 압축 포맷을 ASTC로 선택할 수 있고, 4x4에서 12x12까지 블록 크기를 선택할 수 있습니다.


텍스처의 중요도나 복잡도에 따라서 블럭의 크기를 개별적으로 선택할 수 있습니다. 예를 들어거 캐릭터의  텍스쳐같은 경우는 4x4를 선택하고,


파티클같이 이미지가 많이 뭉개져도 상관 없는 경우는 12x12를 선택함으로써 용량을 절약할 수 있습니다.


ASTC는 ETC보다 더 복잡한 인코딩 과정을 거칩니다. 그러다보니 압축 시간이 조금 더 걸리지만 결과적으로는 ETC보다는 ASTC가 일반적인 품질이 좋습니다.

< 이미지 출처 : developer.arm.com>


ASTC는 안드로이드폰과 iOS폰 모두에서 지원하기 때문에 같은 텍스처 압축 포맷을 사용함으로써  퀄리티와 용량에 대한 유지 보수를 한결 더 수월하게 할 수 있습니다.


물론, 문제는 지원 기기의 범위입니다.


안드로이드의 경우는 OpenGL 3.2 이상 혹은 OpenGL ES 3.1 + AEP(Android Extension Pack)이 지원되어야합니다. 삼성 갤럭시 S6 혹은 LG G5 이상의 기기들이 이에 해당합니다. 안드로이드의 경우에는 기종이 정말 다양하게 존재하기 때문에 반드시 주 타겟 기기에서 지원 여부를 직접 테스트해보아야합니다.


iOS는 A8 processor를 사용하기 시작하는 기종부터 사용이 가능합니다. iPhone 6, iPad mini 4가 이에 해당합니다.


따라서, 그 이하의 구형 기종도 지원해야하는 게임이라면 ASTC를 사용하기가 애매할 수도 있습니다. 하지만, 그렇지 않은 경우에는 ASTC를 사용하는 것이 품질과 용량을 모두 절약할 수 있는 좋은 방법이 될 수도 있습니다. 그러한 이유로 최근에 출시하는 많은 3D 게임에서 ASTC를 사용하기 시작하고 있습니다.


관련 참고 링크 :

https://developer.nvidia.com/astc-texture-compression-for-game-assets



더 많은 내용은 "유니티 그래픽스 최적화 스타트업"을 참고하세요

Posted by ozlael
,
336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.


SRP(Scriptable render pipeline)

유니티 2018부터 스크립터블 렌더 파이프라인(Scriptable render pipeline, 이하 SRP)이라는 기능을 제공합니다. SRP는 유니티의 렌더링 처리 방식을 C# 스크립트로 제어할 수 있는 기능입니다. 이를 통해서 라이팅 처리 방식을 포함한 전반적인 렌더링 루프를 직접 구성할 수 있습니다. 그렇게 함으로써 기존에 유니티에서 고정해놓은 방식대로만 렌더링하는 것이 아니라, 직접 렌더링 루프를 커스터마이징함으로써 원하는 방식으로 렌더링을 구성할 수 있습니다. 예를 들어서, 새로운 렌더링 기술을 직접 적용할 수도 있고, 특정 하드웨어에 맞게 최적화를 할 수도 있습니다.

유니티의 로우 레벨 렌더링 파트는 유니티가 C++로 구현해놓았습니다. 그리고, 이러한 기능들을 C# 스크립트로 사용하고 제어할 수 있도록 되어 있습니다. 유니티를 사용하는 개발자는 C# 스크립트로 자신만의 렌더 루프를 만들 수 있습니다. 하지만, SRP로 렌더 파이프라인을 직접 구성한다는 것은 많은 지식이 요구되고 많은 작업이 필요합니다. 대신, 유니티는 유니티에서는 SRP를 이용하여 HDRP(HD Render Pipeline)와 LWRP(Lightweight Render Pipeline)을 만들어두어 제공하고 있습니다. SRP로 직접 렌더 파이프라인을 구성하지 않고 HDRP나 LWRP를 이용하여 프로젝트를 진행할 수 있습니다.


HDRP(High Definition render pipeline)

HDRP는 하이엔드 PC나 콘솔등을 고려해서 고품질의 비주얼을 구현하는데 적합한 파이프라인입니다. 유니티의 빌트인에 구현되어있는 디퍼드나 포워드보다 발전되고 개선된 렌더링 기법이 적용되어있습니다. (주석 : Clustered tiled lighting, Fine pruned tiled lighting) 또한, Anisotropy, Subsurface 등 더 많은 재질 표현이 가능합니다.

하지만, HDRP는 기본적으로 요구되는 하드웨어 스펙들이 존재하기 때문에 성능이 낮은 플랫폼에서는 작동하지 않습니다. 즉, 모바일 게임 개발용으로는 적합하지가 않습니다.


LWRP(Lightweight Render Pipeline)

대신, 모바일을 위해서 LWRP라는 파이프라인이 제공됩니다. LWRP는 퍼포먼스에 최적화되어 있고, 모바일이나 VR이나 AR처럼 성능에 민감한 환경에서 높은 성능을 보여줍니다.

LWRP는 기존 파이프라인에 비하여 성능을 위한 특징들이 존재합니다. 이를 통해서 라이팅과 쉐이딩 관련하여 단점들을 해결하여 성능 제약이있는 플랫폼에서 최적화 된 실시간 렌더링 퍼포먼스를 제공합니다.


Multi light Single pass

그 중 가장 큰 특징은 실시간 라이팅들이 싱글패스로 처리된다는 점입니다. 앞서 설명하였듯이 포워드 렌더링에서는 실시간 라이트를 여러개를 처리하기 위해서는 그만큼 덧그리게 됨으로써 드로우콜이 늘어납니다. (주석:이를 멀티 라이트 멀티 패스(Multi light Multi pass)라고 표현됩니다.)

LWRP는 유니티에 기본으로 구현된 포워드 렌더링과는 달리, 여러개의 실시간 라이트를 한번의 패스로 처리하도록 만들어져 있습니다. (주석 : 즉, 멀티 라이트 싱글 패스(Multi light Single pass)입니다. ) 오브젝트를 렌더링 할 때 오브젝트에 영향받는 실시간 라이트들을 쉐이더에 전달해줍니다. 쉐이더에서는 한번에 여러 라이트들을 모두 처리해줌으로써 별도의 드로우콜이 발생하는 것을 방지합니다. 이러한 과정을 통해서 기존의 포워드 렌더링과 비교하여 드로우콜이 줄어들게 설계되어있습니다.


관련 참고 자료 링크 : 


더 많은 내용은 "유니티 그래픽스 최적화 스타트업"을 참고하세요

Posted by ozlael
,
336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.

텍스쳐의 Read/Write Enabled


텍스쳐의 임포트 셋팅 중 Read/Write Enabled 플래그도 메모리에 영향을 미칩니다. 이 플래그가 활성화되어 있다면 텍스쳐 데이터가 CPU 메모리에 상주하게 됩니다. 원래 텍스처 데이터는 GPU에서 사용할 수 있도록 GPU 메모리에 상주합니다. 하지만, 이 플래그가 활성화가 되면 추가적으로 CPU 메모리에도 상주하게 됨으로써 메모리 사용량이 두 배가 되는 것입니다.

이 플래그는 유저 스크립트에서 Texture.GetPixel 혹은 Texture.SetPixel 등의 코드를 통해 텍스쳐의 읽기 및 쓰기가 가능한지의 여부를 의미합니다. 만일 런타임이나 로딩타임동안 스크립트에서 텍스쳐의 데이터에 접근할 필요가 있다면 이 플래그가 활성화되어야 합니다.

하지만, 대부분의 경우에는 렌더링에 사용되는 텍스처가 스크립트에서 변경될 일이 없으므로 이 플래그를 비활성화 시켜야 메모리를 절약할 수 있습니다. 때문에, 기본적으로는 비활성화되어되어있습니다.


더 많은 내용은 "유니티 그래픽스 최적화 스타트업"을 참고하세요


Posted by ozlael
,
336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.

유니티의 씬의 모든 Mixed 라이트는 Mixed Lighting Mode에 의존합니다. Lighting Mode 를 설정하려면, Window Rendering > Lighting SettingsEdit > Window > Lighting을 선택하여 라이팅 창을 열면 Mixed Lighting 섹션이 있습니다. Baked Global Illumination 플래그는 베이킹된 GI 즉 라이트맵 사용 여부를 의미합니다. 라이트맵을 사용하려면 이 플래그가 켜져있으면 됩니다.


Mixed Lighting 의 Lighting Mode는 씬의 라이트들 중 모드가 Mixed로 되어있는 라이트들에 의해서 영향을 받습니다. Baked Indirect, Subtractive, Shadowmask 중 하나를 선택할 수 있습니다. 이 모드에 따라서 라이트맵들은 큰 차이를 보입니다.

(이 글에서는 포워드 렌더링 패스를 기준으로 설명합니다.)


Baked Indirect


이 모드로 설정된 경우는 GI만 라이트맵에 기록되고 그림자는 기록되지 않습니다. 그림자는 베이킹이 되지 않고 런타임동안 실시간으로 연산됩니다. 즉, 그림자는 실시간으로 처리하고 GI만 미리 연산해놓고 싶은 경우 이 모드를 사용합니다. 그림자가 매우 중요하게 다뤄져야할 경우 유용합니다. 하지만 그만큼 그림자 처리 비용이 들어가는 것을 염두해두어야 합니다. 그림자 뿐만 아니라 직접광(Direct lighting)도 리얼타임으로 처리됩니다. 따라서 런타임상에서 라이트의 컬러와 강도(intensity)도 변경 가능합니다. 다음 이미지는 스태틱으로 처리된 건물의 그림자와 케릭터의 그림자가 실시간으로 처리되고 있는 모습을 보여주고 있습니다. 건물 옆의 바위는 실시간 라이팅이 적용되어 스페큘러 하이라이트가 반영되어 있습니다.

< Assets : Lighting Optimisation Tutorial, Survival Shooter Tutorial, by Unity Technologies>


씬 뷰에서 Baked Lightmap 모드로 선택하여 확인해보면 AO등의 GI는 반영이 되어있지만 디렉셔널 라이트의 그림자는 반영이 되어 있지 않습니다.



Subtractive


Subtractive는 정통적인 라이트맵이라고 볼 수 있습니다. 라이트맵에 GI와 직접조명, 그림자 까지 모두 라이트맵에 베이킹됩니다. 이 모드는 스태틱 오브젝트에 대한 라이팅 연산이 라이트맵에 새겨지므로 스태틱 오브젝트에는 실시간 라이팅이 이루어지지 않고 오로지 라이트맵에해서만 라이팅이 이루어지기 때문에 스페큘러가 반영되지 않습니다.

라이트맵에 그려진 라이트는 단순히 텍스처에 그려진 이미지와 다름 없기때문에 실시간 그림자와 자연스럽게 반응하거나 하지는 않습니다. 아래 이미지는 베이킹된 건물의 그림자 위에 케릭터의 실시간 그림자가 그려지는 모습을 보여주고 있습니다. 앞의 Baked Indirect에서 캐릭터와 건물의 그림자가 자연스럽게 연결되는 모습과는 대조되는 현상입니다.

그래서 Subtractive 모드에서는 실시간 그림자의 컬러를 조절할 수 있습니다. Realtime Shadow Color를 변경하여서 그림자 컬러를 조절해줄 수 있습니다. 씬의 전반적인 그림자의 컬러와 어울리는 컬러로 선택해서 그림자의 이질감을 줄여줄 수 있습니다.


씬 뷰에서 드로우 모드를 Baked Lightmap로 확인한 모습입니다. 오브젝트의 라이팅과 그림자, AO까지 모두 라이트맵에 처리가 되어 있습니다.

Subtractive 모드에서는 이처럼 스태틱 오브젝트들의 라이트는 라이트맵에서 다 처리하므로 런타임 동안의 라이팅 오버헤드가 극히 적습니다. 그렇기 때문에 저사양 모바일 디바이스에서 사용하기 적합한 모드입니다.




Shadowmask


Shadowmask모드는 라이트맵을 베이킹 시 그림자 영역을 별도의 텍스쳐로 따로 저장합니다. 이러한 그림자 영역을 따로 저장해놓은 텍스처를 쉐도우마스크라도 부릅니다. 얼핏 보기에는 Baked GI와 비슷하게 보일 수 있습니다. 하지만, 런타임동안 그림자 캐스팅 연산 자체를 수행하지는 않습니다. 대신 쉐도우 마스크로부터 그림자 여부를 판단해서 실시간 그림자와 자연스럽게 합성해줍니다. 따라서, Subtractive 모드와는 달리 스태틱 오브젝트와 다이나믹 오브젝트의 그림자가 자연스럽게 연결됩니다.

다음 이미지에서 건물 그림자와 캐릭터 그림자가 자연스럽게 어울리고 있습니다.



Shadowmask모드는 라이트맵이 적용되는 오브젝트에도 스페큘라 하이라이트가 적용됩니다. 건물 옆바위를 보면 스페큘라 하이라이트가 맺히고 있음을 확인할 수 있습니다. 앞선 Subtractive 모드일 경우와 비교해보면 차이가 명확하게 드러납니다. 이 스페큘라 하이라이트는 리얼타임으로 픽셀쉐이더에서 연산되기 때문에 그 만큼의 추가적인 GPU 성능이 소모됩니다. 이는 BakedGI모드에서도 마찬가지지만, BakedGI와 다른점은 그림자 캐스팅 연산에 대한 비용이 상대적으로 절약된다는 점입니다. 스태틱 오브젝트의 그림자 영역은 미리 쉐도우마스크로 저장되어 있기 때문에 런타임동안 쉐도우맵을 이용한 연산을 수행하지 않습니다. ( 주석 : Distance Shadowmask는 경우에 따라 달라집니다.)동적인 오브젝트에 대해서만 쉐도우맵 연산을 함으로써 그림자 연산에 대한 비용을 절약할 수 있습니다.


씬 뷰에서서 드로우 모드를 Shadowmask로 설정하면 쉐도우마스크가 적용되는 부분만 따로 확인할 수 있습니다.


이처럼, 그림자 연산에 대한 성능 비용이 대폭 감소하기때문에 모바일 디바이스에서 사용하기 적합합니다. Subtractive 모드에서의 시각적인 품질이 아쉽다면 Shadowmask도 고려해볼만 합니다. 다만 물론 Subtractive 모드보다는 성능 비용이 더 필요합니다.  


또한, 쉐도우마스크 텍스처가 추가적으로 만들어지기 때문에 텍스처 메모리가 추가적으로 필요합니다. 라이트캡 텍스처 생성 결과물은 라이팅 윈도우의  Global Maps 탭에서 확인할 수 있습니다. 아래 이미지는 위 씬을 디렉셔널 모드를 Directional로 설정하고 라이팅 모드를 Shadowmask로 라이트맵을 베이킹한 결과물들입니다.

첫번째 행에 보여지는 것들이 본래의 라이트맵입니다. 두번째 행에 보여지는 것들이 디렉셔널 모드로 인해 추가된 텍스쳐입니다. 세번째 행에 보여지는 것들이 쉐도우마스크입니다. 원래는 4장인 라이트맵이 디렉셔널 모드와 쉐도우마스크로 인해서 3배가 되었습니다. 하단에 총 용량이 190.7MB임을 나타내고 있습니다. PC나 콘솔에서는 문제가 되지 않는 크기이지만 모바일 디바이스에서는 메모리 문제를 일으킬 수가 있는 수치입니다. 따라서 모바일을 타겟으로 할 시에는 이러한 점들을 염두해두어야 합니다.


Shadowmask는 Distance Shadowmask 모드로 확장될 수 있습니다. Edit > Project Settings > Quality 를 선택하여 퀄리티 셋팅 창에서 선택할 수 있습니다. Shadows 섹션의 Shadowmask Mode를 Distance Shadmowmask로 설정하면 디스턴스 쉐도우마스크 모드가 활성화 됩니다.


이 모드를 활성화 시켜주면 스태틱 오브젝트도 거리에 따라 쉐도우맵을 적용합니다. 가까운 거리는 쉐도우맵으로 실시간으로 그림자를 처리하고 먼 거리는 쉐도우매스크로 그림자를 처리하는 것입니다.

가까운 거리에 있는 다이나믹 오브젝트에게 스태틱 오브젝트로부터 캐스팅되는 그림자를 고품질로 드리우고 싶을 때 이 모드를 사용할 수 있습니다. 다음 이미지는 Distance Shadowmask 모드에서 건물의 리얼타임 그림자가 캐릭터에게 맺히는 모습을 보여주고 있습니다.


Shadow Distance에 설정된 값 이상의 거리는 Shadowmask를 적용해서 처리합니다. 아래 두 이미지는 그러한 예를 보여주고 있습니다.

카메라가 건물과 멀리 떨어져 있을 시에는 쉐도우맵의 영향이 아닌 쉐도우마스크로 처리가 되어서 그림자가 만들어집니다. 그림자의 경계가 쉐도우마스크의 해상도로 인해 블럭화되는 것을 볼 수 있습니다. 또한, 케릭터는 Shadow Distance보다 멀리 있어서 그림자가 생략됩니다.

카메라가 가까이 가면 쉐도우맵을 이용하는 실시간 그림자로 바뀝니다. 건물 그림자의 경계 품질이 바뀐 것을 확인할 수 있습니다.

이처럼, 스태틱 오브젝트도 실시간 그림자를 캐스팅하게 두고싶다면 디스턴스 쉐도우마스크를 이용하면 성능을 절약할 수 있습니다.


더 많은 내용은 "유니티 그래픽스 최적화 스타트업"을 참고하세요

Posted by ozlael
,
336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.

안드로이드 개발 환경 설정


-------- 주의 ---------

이 글의 내용은 더 이상 필요가 없는 설정입니다. 이제는 유니티 허브에서 모듈 추가 시 체크만 해주면 안드로이드 빌드 설정은 유니티가 알아서 해줍니다. 아래 3개의 체크 박스만 확인해주심 땡입니다요




유니티의 프로젝트를 안드로이드로 빌드하기 위해서는 우선 빌드 타겟을 안드로이드로 설정해줘야 합니다.


File > Build Settings 로 빌드셋팅창을 열고 Platform을 Android 로 선택 후 Switch Platform 버튼을 눌러서 빌드타겟을 안드로이드로 바꿉니다.


만일, 안드로이드 빌드용 모듈이 설치되어 있지 않다면 다음과 같이 No Android Module loaded 라는 메시지가 나옵니다. 메시지 바로 아래 있는 Open Download Page 버튼을 눌러 모듈을 다운로드받고 설치해줍니다. 모듈 설치시에는 유니티를 종료시켜줘야합니다. 설치 완료되고나면 유니티를 다시 켜고 프로젝트를 열어줍니다.


Switch Platform 버튼을 누르면 Asset Progress 프로그레스 팝업이 뜬 후 에셋들을 안드로이드 타겟으로 임포트 진행을 처리합니다.

완료되고나면 유니티 상단 타이틀바에 다음과같이 타겟이 안드로이드로 되어있음을 확인할 수 있습니다. ProfilerSampl1은 프로젝트명입니다. 각자 본인이 설정한 프로젝트명으로 표기됩니다.

빌드셋팅창에서 Player Settings 버튼을 누르면 Inspector창에 Player Settings가 뜹니다. Package Name을 변경해줍니다. Package Name은 Rendering 섹션의 하단에 있는 Identification 섹션에 있습니다. Package Name은 안드로이드 패키지의 이름이므로 원하는 이름으로 지으면 됩니다. 다만, com.제작자.프로젝트명 과 같은 형식으로 짓는 것이 일반적입니다.

이제 빌드를 하면 되는데, 유니티로 안드로이드 빌드를 해본 적이 없다면 빌드 전 안드로이드 개발 환경을 셋팅해줘야합니다. 우선, Preferences창을 엽니다. 윈도우즈는 File > Preferences, 맥은 Unity > Preferences. 윈도우 하단의 SDK와 JDK가 비어있는데, 여기에 경로 설정을 해줘야합니다.

우선, SDK는 Android SDK를 의미합니다. 안드로이드 개발을 위해서는 Android SDK가 필수적으로 설티되어 있어야합니다. SDK 항목 우측의 Download 버튼을 누르면 웹브라우저가 열리면서 다운로드 받을 수 있는 Android Studio 페이지로 이동합니다.

다운로드 버튼을 눌러 다운로드받고 설치를 합니다. 설치를 하고나서 Android Studio를 수행하면 다음과 같은 윈도우가 뜹니다. 여기서 Start a new Android Studio Project를 눌러서 새로운 프로젝트를 생성할 필요는 없습니다. 우리가 필요한 것은 단지 SDK가 설치된 위치만 알면 됩니다. 나머지는 유니티가 빌드할 때 알아서 해줍니다.

안드로이드 스튜디오의 새로운 프로젝트를 만드는 대신, 하단의 Configure 버튼을 눌러서 뜨는 리스트 중 SDK Manager 를 선택합니다.

그럼 SDK 매니저가 뜹니다. 이 중 상단의 Android SDK Location의 경로를 복사합니다.

다시 유니티의 Preferences 윈도우로 돌아가서 SDK 항목에 패스를 그대로 붙여넣으면 됩니다.


윈도우즈에서는 Android SDK 경로에 공백이 들어가 있으면 문제가 발생할 수 있습니다. 예를 들어서 사용자 이름에 공백이 들어가게 되면 전체 경로에 공백이 포함되어버리므로 문제가 발생할 여지가 있습니다. Android SDK Location 옆의 Edit 버튼을 눌러보면 다음과 같이 경고가 뜹니다.

이러한 경우에는 다른 경로에 공백이 없이 새로운 폴더를 만들어서 지정해주면 됩니다.


Android SDK를 완료했으면 이제 JDK를 설치해줘야합니다. JDK는 자바 SDK를 의미합니다. 안드로이는 JAVA를 기반으로 개발하게 되어있으므로 JDK가 설치되어 있어야합니다. 역시 JDK 옆의 Download 버튼을 누르면 웹페이지가 열리고 JDK를 다운로드 받을 수 있는 페이지로 이동합니다.

Download JDK 버튼을 눌르면 다음과 같은 선택 페이지로 들어갑니다.

Accept License Agreement를 체크하고, 본인의  PC에 맞는 OS용 설치 파일을 다운로드하여 설치합니다.


이제 유니티 Preferences 윈도우에서 JDK 경로를 입력해줘야합니다. 기본적으로 다음과 같은 폴더에 설치가 됩니다.

윈도우즈 : C:\Program Files\Java\jdk+버젼명

맥 : /Library/Java/JavaVirtualMachines/jdk+버젼명+.jdk/Contents/Home

탐색기나 파인더로 해당 폴더를 확인하신 다음에 JDK 항목에 패스를 기입해주면 됩니다.


SDK와 JDK 설정을 완료했으면 이제 빌드가 가능합니다. 하지만, 안드로이드도 IL2PP 백엔드로 빌드를 하려면  NDK도 추가로 설정을 해주어야 합니다. NDK는 Android Native Development Kit를 의미합니다. C와 C++ 같은 네이티브 코드 언어를 사용하여 앱의 일부를 구현하는 도구 모음입니다. IL2CPP 백엔드를 이용하면 최종적으로 유저 코드를 C++로 변경하기때문에 NDK가 필요합니다. NDK의 경우에는 Android Studio에서 설치가 가능합니다. 하지만 유니티에서는 특정 버전의 NDK를 요구하므로 Android Studio를 통해서 설치하면 버전이 맞지 않을 수도 있습니다. 따라서 SDK와 JDK와 마찬가지로 우측의 Download를 눌러서 다운로드 받습니다. NDK의 경우에는 인스톨 프로그램이 아니라 폴더 압축파일입니다. 윈도우즈의 경우에는 exe로 되어있어서 실행하면 바로 압축이 풀립니다. 맥에서는 bin파일로 되어있어서 터미널에서 커맨드라인으로 압축을 풀어줘야합니다. 우선 터미널을 열고 다운로드 받은 폴더로 이동합니다. 그 후 다음과같이 실행 권한을 추가해주고 압축을 풀어줍니다.

스크린샷 2017-05-19 오전 2.00.13.png

압축 푼 폴더를 적당한 위치로 옮겨주고 해당 경로를 Preference에 입력해줍니다.

스크린샷 2017-05-19 오전 2.11.02.png


Android SDK, JDK, NDK 모두 설치가 되었으면 이제 디바이스에 띄워볼 차례입니다. 우선, 안드로이드폰과 작업 PC를 USB로 연결해줍니다. Build Settings 윈도우로 돌아가서 Build And Run 버튼을 누르면, 빌드할 apk파일명을 물어봅니다. 원하는 이름으로 입력해주면 빌드가 진행됩니다.


만일, SDK 패스등이 제대로 설정되어 있지 않다면 빌드 시 다음과같은 실패 메시지가 뜹니다.

SDK 패스 설정이 잘 되어있음에도 불구하고 위와 같은 실패 메시지가 뜬다면, 유니티를 껏다 켜보고 OS 재부팅도 해보세요 :)


만일, 안드로이드 디바이스가 디버깅 환경 설정이 되어있지 않다면 다음과 같은 팝업창이 뜹니다.

안드로이드 디바이스는 기기를 개발용 디버깅이 가능한 개발자 모드로 설정을 해줘야 프로젝트를 빌드하여 띄울 수 있습니다. 개발자 모드 설정은 안드로이드OS 버전이나 기기마다 방법이 조금씩 상이합니다. 구글에서 본인의 테스트 기기에 대한 개발자 모드 방법을 검색하면 쉽게 방법을 찾을 수 있습니다.

개발자 옵션을 활성화시키고나면 개발자 옵션에 들어가서 USB 디버깅을 활성화시켜주면 됩니다.

또한, Windows OS의 경우에는 디바이스의 드라이버를 설치해줘야 합니다. 디바이스 벤더 사이트에서 해당 디바이스에대한 드라이버를 다운로드 받을 수 있습니다.

이제 다시 Build Settings 창에서 Build And Run 버튼을 누르면 빌드가 진행되고 빌드가 완료되면 디바이스에 apk가 자동으로 설치되고 게임이 수행됩니다. 디바이스에 맨 처음 연결하는 것이라면 디바이스에서 디버깅 연결할 지 여부를 묻는 창이 뜰 수도 있습니다. 이러한 경우에는 승인 전 까지는 PC에서 디바이스 연결이 실패하기때문에 승인 후 다시 빌드해줘야 할 수도 있습니다.


만일 타겟팅하고 있는 Android API Level(Edit>Project Settings>Player>Other Settings>Identification)이 설치가 되어있지 않은 경우에는 빌드시 Target SDK가 설치되어 있지 않다는 메시지가 뜰 수도 있습니다.


이러한 경우에는 Android Studio의 SDK Manager를 열어서 SDK Platforms 탭에서 해당 SDK를 체크해주고 Apply를 누르면 해상 SDK를 설치합니다.



더 많은 내용은 "유니티 그래픽스 최적화 스타트업"을 참고하세요


Posted by ozlael
,
336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.

문서들을 여기 저기 따로 공유하려느 번거로워서 유니티 최적화 관련 문서들을 모아두었습니다. 정리는 두서 없이 했고,다만 출처가 유니티 크루인지 아닌지로만 분류하였습니다. 추가적으로 발견하는 대로 지속적으로 갱신 할 예정입니다.

Update : 2018-07-10


From Unity


from Developers




Posted by ozlael
,

Unity OVR Input

Unity3D/Others 2017. 9. 30. 15:34
336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.

VR컨텐츠를 만들 때 일반적으로는 양대 HMD( 오큘러스, 바이브)를 모두 지원해야합니다. HMD 디바이스만 지원하는 것은 매우 쉽습니다. HDM의 트래킹을 지원하기 위해서 별도의 SDK나 에셋을 필요호 하지 않고 유니티의 기본 기능으로 충분하기 때문입니다. 하지만 문제는 컨트롤러의 트래킹과 입력을 지원하는 경우에 발생합니다. 오큘러스와 바이브 컨트롤러를 지원하기위해서는 각각의 SDK를 임포트해서 사용해야합니다. 예를 들어서, 오큘러스 터치를 지원하기 위해서는 오큘러스 디벨로퍼 센터에서 오큘러스 유틸리티를 받아서 임포트해야 합니다. 또한, 바이브 컨트롤러를 지원하기 위해서는 스팀VR  플러그인은 에셋스토어로부터 받아서 임포트해야합니다. 결과적으로는 컨트롤러 관련하여 각각 다른 코드와 프리팹들을 관히애하 합니다. 이는 프로젝트를 개발하고 유지보수하는데 있어서 매우 번거로운 상황입니다. 하지만, 만일 컨트롤러 시스템이 간단한 컨텐츠를 만든다면, 더 이상 이럴 필요가 없습니다. 유니티는 VR 트래킹 API를 제공하고있고, 이를 이용해서 컨트롤러의 위치를 트래킹 할 수 있습니다. 컨트롤러를 트래킹하기 위해서 추가적인 SDK를 유니티 외부로부터 임토프하지 않아도 됩니다. 유니티에서 제공하는 API만으로도 구현이 가능합니다. 버튼 및 트리거 입력은 유니티의 기본적인 인풋 시스템을 이용하면 됩니다.

If you want to make VR content, you have to support all of major HMDs.(Oculus and Vive at this moment :p) You may know supporting HMD devices only is very easy. You don't need any additional SDK or assets to track and support HMD deivces. Problem is supporting and tracking controllers. If you want to support both Oculus Touch and Vive Controller , you may import both SDKs. For Oculus Touch, You have to import Oculus Utilities for Unity rom Oculus Developer Center. And for Vive Controller, you have to import SteamVR Plugin from Unity Store. As a result, you have to use different prefabs and different codes to support both different controller systems. It is very annoying stuff for maintaining. Actually, you don't have to do it anymore, if your controll input system is simple. Unity provide VR tracking APIs to track posion of controllers. To track controllers, you don't have to import additional SDKs from out of Unity. You can do it as using API built inside unity. Plus, You can get input of triggers and buttons from controllers using basing Input system of Unity.


VRControllerTracking.cs :

transform.localPosition = InputTracking.GetLocalPosition (whichNode);

transform.localRotation = InputTracking.GetLocalRotation (whichNode);


컨트롤러 인풋을 얻어오기 위해서는 유니티 공식 매뉴얼을 참고하면 됩니다 : Input table for OVR controllers, Input API, Input Manager. 이는 일반적인 게임 컨트롤러와 동일합니다. 인풋 매핑 테이블을 참고하면 됩니다. 예를 들어서,  LeftHandTrigger를 얻어오고 싶으면, 조이스틱 버튼 14에 해당합니다.

To get input of controllers, You may refer official manual : Input table for OVR controllers, Input API, Input Manager. It is the same with general game controllers. Look at the input mapping table. For example, if you want to get LeftHandTrigger, it will say Joystick Button 14 in there.


LaserSwordControl.cs :

if (blade && Input.GetAxisRaw (inputNameToActiveBlade) > 0.1f) {


자세한 코드와 내용은 샘플 프로젝트 코드를 참고하세요 : https://github.com/ozlael/UnityDemo_InputForOVR

Posted by ozlael
,
336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.


게임을 개발함에 있어서 가장 중요한 것은 물론 재미입니다. 게임은 재미를 주는 것이 주 목적이니 당연한 이야기겠죠. 하지만, 게임을 아무리 재밌게 만들었다 하더라도 게임이 실행이 되야 재미를 느낄 수 있겠죠. 게임을 플레이하다가 게임이 자꾸 강제 종료되버린다면 말짱 도루묵이 되겠죠.

그렇기 때문에 게임 개발에 있어서 메모리 이슈도 중요합니다. 특히 모바일 환경에서는 더 심하죠. PC에서는 실제 물리적인 램이 부족하더라도, 디스크의 공간을 활용하여 모자란 메모리를 보조하는 시스템이 존재합니다. 때문에, 현대의 PC에서는 게임이 버벅일지언정 메모리 부족으로 게임이 강제종료되는 상황까지 가는 것이 흔치는 않습니다. 하지만, 스마트폰에는 그러한 기능이 존재하지 않습니다. 그말인 즉슨, 실제의 물리적인 메모리 공간을 넘어서 사용이 불가능하다는 것을 의미합니다. 그렇기때문에, 스마트폰 게임이 메모리를 많이 사용한다는 것은 게임이 뜻하지 않게 강제종료될 가능성이 그만큼 많아진다는 것을 의미합니다.  

스마트폰 게임을 개발할 때에는 항상 메모리에대해서 신경쓰면서 개발을 해야합니다. 하지만 메모리를 줄여야 하는 것은 알겠는데, 무엇을 어떻게 줄여야 할지 막막한 채로 작업을 할 수는 없겠죠. 그렇기 때문에  메모리도 프로파일링이 필요합니다.


유니티 5.3부터는 메모리 프로파일러라는 확장 툴을 이용하여 메모리를 확인할 수 있습니다. 이 툴을 이용하면 어떤 메모리가 얼만큼의 힙 메모리를 사용하고 있는 지를 직관적으로 쉽게 확인 할 수 있습니다.

다운로드 : https://bitbucket.org/Unity-Technologies/memoryprofiler/downloads/

아쉽게도 아직 유니티 기본 내장 툴은 아니고, 에디터 확장 기능으로 따로 다운로드받아서 사용해야합니다. 유니티도 오픈소스 형태로 개발하는 기능들이 많이 있는데요, 빗버킷에 올려두고 개발을 진행합니다. GUI 시스템도 마찬가지고 Post Processing Stack, Asset Bundle Manager 등등 많은 프로젝트들이 오픈소스 형태로 진행중입니다. 메모리 프로파일러 역시 이 중 하나라고 보시면 됩니다. 

유니티 5.3 이상부터 적용이 가능합니다. 다운로드 받은 프로젝트의 에디터 폴더를 현재 현재 개발중인 프로젝트의 에디터 폴더로 임포트 하면 됩니다. 스크립트 백엔드는 IL2CPP로 적용해줘야합니다. 안드로이드 역시 IL2CPP가 정식 지원되고 있기 때문에 안드로이드도 사용이 가능합니다. 다만 주의할 점은 5.3에서는 안드로이드 IL2CPP가 정식 지원이 아니라는 점입니다. 결론적으로는 메모리 프로파일러를 이용하려면 5.3을 이용중인 프로젝트라면 5.4 이상으로 업데이트를 해야한다는 의미가 됩니다.

메모리 프로파일러는 내부적으로 Profiler API를 이용합니다. 그렇기 때문에, Development Build로 빌드하여야 합니다. 게임을 띄우고나면 유니티 프로파일러를 띄우고 디바이스에 프로파일러를 연결하여 프로파일링이 되는 것을 확인합니다, 그 뒤에 유니티에서 Windows>Memory Profiler를 선택합니다.

그럼 이렇게 생긴 메모리프로파일러 윈도우가 뜹니다. 이 화면에서 Take Snapshot 버튼을 누르면 됩니다. 디바이스에서 데이터 긁어오다보니까 폰이 잠깐 멈췄다가 가져온 데이터를 분석하느라 에디터가 잠깐 멈출겁니다. 데이터가 많으면 좀 시간이 걸릴 수도 있기 때문에 인내심이 좀 필요할수도 있습니다.

데이터 분석이 다 끝나면 메모리 사용량을 종류별로 시각화 보여줍니다. 종류별로 메모리를 얼마만큼 차지하는지를 사각형의 크기로 보여줍니다. 사각형 크기 비율이 실제의 메모리 사용량의 비율을 나타냅니다. 즉, 사각형의 크기가 큰 것 을 우선순위로 줄여나가면 되는 것입니다.

항목을 클릭하면 개별 리소스나 오브젝트들의 메모리 차지 면적을 볼 수 있습니다. 우측에는 선택한 오브젝트의 이름 및 인스턴스 아이디 등등의 자세한 내용을 확인할 수 있습니다. 크기가 작아서 보기 힘든 항목들은 마우스 휠을 굴려서 줌인 줌아웃이 가능합니다. 솔직히 인터페이스가 이쁜 편은 아닙니다. 좀 투박한 느낌은 있지만 시각적으로 확인하고 분석하는 기능은 충분합니다.


실제의 예를 하나 들어보겠습니다. 에셋스토어에서 게임을 하나 받아서 갤럭시S2에서 수행한 모습입니다. 2D게임이라 갤럭시 2S에서도 동작하는데 무리가 없습니다.

근데, 문제가 없어보인 것이지 실제로는 문제가 있는 상황입니다. 프로파일링해보기 전에는 문제가 없다고 단정하면 안됩니다. 실제로 메모리 프로파일러로 확인해보니 텍스쳐 메모리 사용량이 엄청납니다. 어떤 텍스쳐는 16MB나 차지하고 있습니다. 

확인해보니 사이즈가 2048이기는 하지만 ETC2 압축을 설정해놓았으니 16MB는 말이 안되는 것 같아보입니다.

사실, 이건 갤럭시2S가 OpenGL ES 2.0만 지원해서 발생하는 현상입니다. 갤럭시 S2는 오래된 모델이라 OpenGL ES 3.0을 지원하지 못하고 OpenGL ES 2.0만 지원합니다. 문제는 ETC2는 OpenGL ES 3.0부터 지원된다는 것입니다. 즉 OpenGL ES2.0에서는 ETC2를 사용하지 못한다는 것입니다. 그래서 OpenGL ES 2.0에서는 ETC2 텍스쳐를 읽어들일 때 ETC2를 압축을 풀어서 메모리에 올라갑니다. 그래서 2048 * 2048 * 4바이트 = 16MB가 되서 메모리에 올라간 것입니다. 

똑같은 텍스쳐를 OpenGL ES 3.0 지원되는 기기에서 확인해보면  텍스쳐 압축이 잘 되서 4MB로 올라갑니다. 그래서 알파가 있는 텍스쳐를 컴프레스트로 두면 OpenGL ES 2.0까지만 지원하는 갤럭시S2랑 OpenGL ES 3.0을 지원하는 갤럭시 S4에서의 실제 텍스쳐 메모리 사용량이 많이 차이가 날 겁니다. (갤럭시 S3로 비교하지 않은 이유는, 갤럭시 S3 중에서도 세부 기종에따라 지원하는 버전이 다르기 때문입니다)


메모리 프로파일러를 이용하면 이런식으로 어떤 리소스가 얼만큼을 차지하고 있는 지를 쉽게 분석할 수 있습니다. 메모리를 프로파일링하기 위해서는 필수적인 툴일 것입니다.

이 툴에 대하여 건의 사항이나 버그가 있다면 아래 주소로 이슈를 만들어주시면 됩니다. 만약 영어가 불편하면 저한테 말씀해주세요.

링크 : https://bitbucket.org/Unity-Technologies/memoryprofiler/issues?status=new&status=open

 제가 사실 블로그 댓글은 잘 확인하지를 않아서 페이스북이나 트위터로 연락 주세요 :)

페이스북 : ozlael.oz

트위터 : @ozlael

Posted by ozlael
,
336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.

이 글은 유니티 자습서Best Practices 문서 중 Resources Folder를 번역/의역한 것입니다.


Resources 폴더

이 글은 “유니티5의 에셋과 리소스 관리” 시리즈의 3번째 글입니다.


이 챕터는 Resources 시스템에 대하여 다룹니다. 이 시스템은 개발자가 여러 에셋들을 Resources 폴더에 두고 런타임동안 Resources API를 통해서 에셋을 로드/언로드할 수 있도록 해줍니다.


2.1 Resources 시스템을 위한 실용 가이드


사용하지 마세요.


다음과 같은 이유들로 사용하지 않기를 강력하게 권고합니다

  • Resources 폴더를 사용하는 것은 메모리 관리를 더욱 복잡하게 만듭니다.

  • Resources 폴더를 부적절하게 사용하면 어플리케이션 시작 시간과 빌드 시간을 늘리게 됩니다.

  • Resources 폴더들이 많아지면 그 안에 있는 에셋들의 관리가 힘듭니다.

  • Resources 시스템은 사용자 정의 컨텐츠를 특정 플랫폼으로 옮기는 것이 까다로워집니다. 컨텐츠를 업그레이드하는 경우도 마찬가지입니다.

  • 디바이스에 맞는 컨텐츠를을 조정하는데 적합한 주요 기능은 에셋번들입니다.


2.2 Resources 시스템의 적절한 사용

다음과 같은 두 가지의 경우에는 개발 시 Resources 시스템을 사용하기에 적합한 경우입니다.

  1. 빠른 프로토타이핑이나 검증이 필요한 경우에는 Resources를 사용하기에 좋습니다. 간단하고 사용하기 쉽기 때문입니다. 하지만, 프로젝트가 프로덕션 단계에 가게 되면 Resources 폴더 사용을 중단해야 합니다.

  2. Resources 폴더는 다음 모든 경우를 만족하는 상황에서는 유용한 시스템입니다.

    • Resources 폴더에 들어있는 컨텐츠들은 메모리에 민감한 것 들이 아닐 경우

    • 컨텐츠가 어플리케이션의 수행 시간 동안 계속 필요한 경우

    • 컨텐츠가 패치될 일이 거의 없는 경우

    • 컨텐츠가 플랫폼이나 디바이스별로 달라지지 않는 경우

이 중 두 번째 케이스에 해당하는 예시는 전역으로 사용되는 싱클턴 모노비헤이비어나 페이스북 앱 ID등의 서드파티 데이터를 다루는 에셋등이 될 것입니다.


2.3 Resources의 시리얼라이제이션

프로젝트 빌드 시 “Resources” 폴더 내 모든 에셋과 오브젝트들은 하나의 시리얼라이즈된 파일에 정보가 담깁니다. 이 파일은 에셋번들처럼 메타데이터와 인덱싱 정보를 포함합니다. “에셋번들 의 근본”챕터의 “에셋번들이란 무엇인가?” 섹션에서 설명된 바와 같이, 이 인덱싱 정보는 시리얼라이즈된 룩업  트리를 포함합니다. 여기에는 주어진 오브젝트 이름을 파일 GUID와 로컬 ID와 매칭시키는데 쓰이는 정보들을 포함합니다. 오브젝트를 시리얼라이즈된 파일의 몇 바이트째 위치시키느냐등의 정보도 포함합니다.


룩업 데이터 구조가 균형잡힌 탐색 트리라서 구축 시간 비용은 O(N log(N)) 비율로 증가합니다.(대부분의 플랫폼에서는 C++ Standard Template Library의 std::multimap를 사용합니다.) 여기서 N은 트리에 들어가는 오브젝트 갯수를 의미합니다. 이러한 증가치는 인덱스의 로딩 타임에 많은 비용이 들게합니다. 따라서 Resources 폴더 안의 오브젝트들이 많을수록 성능에 좋지 않습니다.   


이 동작은 건너뛸 수 없기때문에 어플리케이션의 초기 시작 시간을 늘리게 되서 인터렉션이 불가능한 스플레시 화면이 떠 있는 시간이 길어지는 요인이 됩니다. 특히 저사양 기기일 수록 Resources 시스템에 많은 오젝트가 포함되있으면 어플리케이션 시작 시간이 매우 길어지게 됩니다. 특히, Resources 폴더의 대부분의 오브젝트들이 첫 씬에서 보여질 필요가 없는 경우에는 효율적이지 못한 상황이 되는 것입니다.




Posted by ozlael
,
336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.

게임을 개발하는 과정에서 콘솔 로그를 출력하는 것은 필수적입니다. 당연히 유니티에서도 로그를 출력하는 함수가 존재하고 Debug.Log()를 많이 사용하시죠.

Manual : https://docs.unity3d.com/ScriptReference/Debug.Log.html 


근데 많이들 오해하시는게, 이 Debug.Log 함수는 Debug클래수 함수니까 Development Build일 때에만 작동하고 릴리즈 빌드때는 자동적으로 제거된 다고 오인하시는 것입니다. 콘솔 출력용으로 남발해서 사용되도 최종 릴리즈에는 영향 미치지 않는다고 오인하시는 경우가 많습니다, 하지만 아쉽게도 그렇지 않습니다. Debug.Log()는 릴리즈 빌드때고 고스란히 작동합니다. 따라서 쓸데 없는 성능을 쉽게 잡아먹는 녀석중 하나가 되버리죠.


예를 들어서 다음과 같이 콘솔 출력용 로그를 수없이 찍는 코드를 만들고

public class TestLog : MonoBehaviour {


public int logCount = 500;

public string testStr;

void Update () {

for( int i=0; i< logCount; i++) {

UnityEngine.Debug.Log("test log : " + testStr + "!" );

}

}

}

빈 프로젝트에서 위 코드만 돌린 프로젝트를빌드하여 홍미 폰에 띄우니 30FPS로 돌아갑니다. 디벨롭먼트 빌드가 아닌데도 불구하구요. 물론 실제로 매 프레임마다 500번의 출력을 하는 경우는 실존하지 않을 것입니다. 테스트를 보여주기 위하여 극악의 상황을 만들어본 것 뿐입니다. 다만, Debug.Log()가 쓸데 없이 작동하는 것을 증명하기에 충분한 코드겠지요. 


이러한 쓸데없는 행위를 막기 위해서 #if 디파인문을 많이들 사용하십니다. 다음과 같이요

#if TRACE_ON

UnityEngine.Debug.Log("test log : " + testStr + "!" );

#endif

이렇게 두고 플레이어 셋팅의 Scripting Define Symbols이나 .rsp 파일에 TRACE_ON을 추가하한 경우에만 수행되고 아닌 경우에서는 Debug.Log()가 수행되지 않고 60FPS가 잘 나옵니다.

Manual : https://docs.unity3d.com/Manual/PlatformDependentCompilation.html

이제 문제가 전혀 없습니다. 없을까요? 

있습니다. 귀찮은게 문제지요.


Debug.Log()를 출력하는 곳이 한두군데도 아니고 매번 작성할 때 마다 #if #endif로 묶으려면 매우 귀찮습니다. 게다가 실수로 잘못 묶으면 짜증이 짜증이 아휴~



대안으로는 Conditional를 사용하시면 편합니다. Debug.log()를 래핑하는 클래스를 만들고 함수에다가 Conditional를 선언하면 됩니다.

using UnityEngine;

using System.Collections;

using System.Diagnostics;


public class TestLog : MonoBehaviour {


public int logCount=500;

public string testStr;


[Conditional("TRACE_ON")]

void DebugLogWrap( string str) {

UnityEngine.Debug.Log(str);

}

void Update () {

for( int i=0; i< logCount; i++) {

DebugLogWrap("test log : " + testStr + "!" );

}

}

}

TRACE_ON 디파인이 켜져있지 않는 경우에는 DebugWrap을 호출하는 라인이 통채로 무시됩니다. 따라서 #if 디파인을 매번 사용하지 않고도 같은 효과를 볼 수 있습니다. 사실 저도 얼마 전에 알게 된 것인데 굉장히 편하네요 ㅋ C# 짱짱맨 :)

MSDN : https://msdn.microsoft.com/en-us/library/4xssyw96(v=vs.90).aspx


궁극적으로는 다음 링크처럼 사용하시면 됩니다 : 

https://gist.github.com/kimsama/4123043


끗!


Posted by ozlael
,
336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.

어느 날 자고 일어나니 다리가 넷 아니 뜬금없이 두통이 생겼다. 혹시 생일날 선물 안사줬다고 나 자는사이에 몰래 뒷빡 갈긴건 아닌가 하고 와이프를 추궁해봤지만 지는 결백하댄다. 내년 생일도 쌩까야지 하고 소심하게 다짐하면서 냅뒀는데 계속 두통이 사라지질 않는 것이다.

참다 못해 동네 약국에 갔더니 약사 아재는 쿨하게 두통약을 던져주더라. 허나 두통약을 먹어도 그때 뿐 여전히 두통은 심해져만 갔다.

이등병의 남은 군생활마냥 두통이 계속 사라지지 않자 일주일째 지속되던 날 결국 회사 근처 아무 내과에 들렀다. 의사 아재는 내 설명을 차근차근 듣더니 갑자기 윗도리를 벗으랜다. 그러고는 내 등 뒤로 가서는 등짝 등짝을 보잔다. 머리아프다는데 웬 등짝이지 이게 뭔 피카츄 파이어볼 쏘는 소린가 내가 역시 마성의 게이라서 노말벡터도 바이노말벡터로 만들어버린건가 싶었지만, 너무나도 당당한 의사 아재의 눈빛에 그만 등짝을 내주어버리고 말았다. 

비루한 내 염통은 눈치도 없이 쫄깃해지며 발랑발랑 거리던 순간 아재의 보드라운 손길이 내 등짝과 목덜미를 어루만졌고 나는 그만 비명인지 신음인지 모를 단말마의 비명소리를 내지르고 말았다. 

아프다. 분명 갱킹을 갔는데 뜻하지 않게 렝가에게 부쉬칼빵을 맞고 갱승당한 것 마냥 뜻하지 않게 아프다. 분명 머리가 아파서 병원엘 갔는데 등짝이 아프다.

알고보니 이유인 즉슨 이러했다. 나는 사실 머리가 아픈게 아니라 등짝이 아픈거였다. 신경이 등짝부터 머리까지 연결되어있기 때문에 등짝 근육이 뭉쳤는데 애꿏은 머리가 아팠던 것이였다. 그러니 두통약을 쳐먹어봤자 발업 안한 저글링마냥 스팀팩 없는 마린새끼마냥 골골댔던 것이였던 것이였다.

의사 아재의 안내대로 피카츄 전기 공격으로 등짝을 몇 분 맛사지 해주니 두통이 대통령 선거 공약같이 말끔히 사라졌다.

마냥 신기했다. 처음에는 마냥 신기했는데 곰곰히 생각해보니 내가 곰이네 아니 곰곰히 생각해보니 의사 아재는 훌륭한 필드 엔지니어였다. 비록 내 몸뚱아리는 신이라는 개발자가 자연의 섭리라는 거대한 플랫폼 하에 만들어낸 것이지만, 의사 아재는 시스템을 정확히 이해하고있는 통찰력으로 내 몸뚱아리를 너 로맨틱 성공적으로 디버깅 한 것이다. 그렇기에 그의 눈빛은 자신감에 차있었으며 나는 그 자신감 넘친 눈빛에 매료되어 등짝을 내주고 말았다.

하지만 나는 어떠한가? 나는 시스템을 깊이 이해하는 통찰력을 가지고 코딩을 해왔는가? 난 그저 머리아프다고 무조건 두통약을 내어준 동네 약사와 다를 바 없이 디버깅하지는 않았는가? 

많은 고민을 하고 나를 돌아보게 만들어준 두통이였다.


(페북에 올렸던 글인데 나름 호응?이 좋아서 백업 ㅋㅋ)

Posted by ozlael
,
336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.

게임 수학을 다루는데, 다른 서적들보다 난이도를 낮게 잡고 있으며 유니티 예제들로 직접 확인을 해 볼 수가 있습니다.

원고 참여는 안하였고 감수로만 간간간간간접적으로 참여했습니다 :)



링크 : http://www.yes24.com/24/goods/30119802?scode=032&OzSrank=9

Posted by ozlael
,
336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.

제목 그대로 텍스쳐와 모델링에 중점. 

TA 혹은 그래픽스 프로그래머라면 필수인 것 같습니다.


참여 원고 :

- 텍스쳐 압축 이해하기



링크 : http://vielbooks.com/215

Posted by ozlael
,
336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.

무크지 형태의 게임 그래픽 서적

참여 원고 :

- 모바일 게임의 케릭터 라이팅을 위한 유니티 쉐이더의 최적화

- 개발자들이 알아두어야 할 VR 컨텐츠 제작의 13가지 주의 점


링크 : http://vielbooks.com/202

Posted by ozlael
,
336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.

드로우콜을 중점으로 다룸



Posted by ozlael
,
336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.

유니티에서 그래픽 최적화를 다룸. 유나이트와 엔디씨 버전






Posted by ozlael
,
336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.

카사 스터디에서 발표했던 약 멋지게 파는 방법 ㅋ


Posted by ozlael
,
336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.

유니티의 라이팅이 안 이쁘다구요? (A to Z of Lighting) 청강 게임 컨퍼런스 2014



기사 : http://www.inven.co.kr/webzine/news/?news=120362


Posted by ozlael
,
336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.

KGC2013



기사 : 


Posted by ozlael
,
336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.

KGC12에서 발표한 내용. 레이더즈와 건즈2에서 자체엔진 개발하면서 겪은 디퍼드 렌더링 적용 노하우


Posted by ozlael
,
336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.

KGC11에서 발표한 




Posted by ozlael
,

optimize Modern GPU

My Speeches 2016. 8. 31. 13:57
336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.

사내(마이에트) 스터디에서 다루었던 현대 GPU에서의 최적화. 11년도 자료라 더 이상 현대가 아니지만...



Posted by ozlael
,

DOF

My Speeches 2016. 8. 31. 13:55
336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.

사내(마이에트) 스터디에서 다루었던 DOF. 개량한 블러도 설명



Posted by ozlael
,

SSAO

My Speeches 2016. 8. 31. 13:54
336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.

사내 스터디에서 다루었던 SSAO 개요



Posted by ozlael
,

Inferred lighting

My Speeches 2016. 8. 31. 13:49
336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.

사내 스터디에서 다루었던 Inferred lighting 자료



Posted by ozlael
,
336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.

G-buffer Normal을 R16FG16F 포맷으로 사용 시 Z 부호 문제 Deferred Rendering

저희 엔진은 G-buffer Normal을 Deferred Rendering in Killzone 2에 나온 방식으로 x,y만 저장하고, 읽을 시 Normal.z = sqrt(1.0f - Normal.x제곱 - Normal.y제곱)으로 계산해냅니다.  하지만 값의 부호는 양수지요. 처음에는 뷰 스페이스 노말이므로 뒷면을 그리지 않는 한 z의 부호는 항상 음수일 것으로 생각하고 무조건 음수로 사용했었습니다만 경기도 오산이였습니다. 양수가 되는 경우도 있더군요. 카메라가 지면 가까이서 하늘을 향했 을 시 지면의 뷰 스페이스 노말 z가 양수가 되는 경우가 발생합니다. 
그래서 G-buffer Depth의 값에 뷰 스페이스 노말의 z 부호를 저장하여 해결했습니다. 어짜피 깊이 값의 부호는 항상 양수이므로 저장 값의 부호는 노말 z의 부호로 사용하면 문제 해결이지요. depth를 안읽고 normal만 읽는 경우는 없으니 추가 비용에 대한 부담도 없다고 보면 되겠지요.
티스님의 글을 보고 생각나서 몇자 적어보았습니다 :-)


Posted by ozlael
,
336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.

10년도 작성 글 백업

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


디퍼드 랜더 시스템 개략( Forward, Deferred, Light Prepass, Inferred)Deferred Rendering

얼마 전 저희 엔진 랜더 시스템에 대한 간략한 리뷰를 가졌습니다. 그때 사용 된 PT 슬라이드를 올리기는 좀 거시기 하여서 그 중 올려도 상관 없는 몇 장만 살포시 올려봅니다 -ㅈ-; 대강의 디퍼드 랜더 시스템 개략 설명입니다. Forward, Deferred, Light Prepass, Inferred 시스템을 대략적으로 한장씩 표현하였습니다.


Posted by ozlael
,
336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.

Compact Normal Storage for Small G-Buffers Deferred Rendering

원문 : http://aras-p.info/texts/CompactNormalStorage.html

Compact Normal Storage for Small G-Buffers

Intro

Various deferred shading/lighting approaches or image postprocessing effects need to store normals as part of their G-buffer. Let’s figure out a compact storage method for view space normals. In my case, main target is minimalist G-buffer, where depth and normals are packed into a single 32 bit (8 bits/channel) render texture. I try to minimize error and shader cycles to encode/decode.

Now of course, 8 bits/channel storage for normals can be not enough for shading, especially if you want specular (low precision & quantization leads to specular “wobble” when camera or objects move). However, everything below should Just Work (tm) for 10 or 16 bits/channel integer formats. For 16 bits/channel half-float formats, some of the computations are not necessary (e.g. bringing normal values into 0..1 range).

If you know other ways to store/encode normals, please let me know in the comments!

Various normal encoding methods and their comparison below. Notes:

  • Error images are: 1-pow(dot(n1,n2),1024) and abs(n1-n2)*30, where n1 is actual normal, and n2 is normal encoded into a texture, read back & decoded. MSE and PSNR is computed on the difference (abs(n1-n2)) image.
  • Shader code is HLSL. Compiled into ps_3_0 by d3dx9_42.dll (February 2010 SDK).
  • Radeon GPU performance numbers from AMD’s GPU ShaderAnalyzer 1.53, using Catalyst 9.12 driver.
  • GeForce GPU performance numbers from NVIDIA’s NVShaderPerf 2.0, using 174.74 driver.

Note: there was an error!

Original version of my article had some stupidity: encoding shaders did not normalize the incoming per-vertex normal. This resulted in quality evaluation results being somewhat wrong. Also, if normal is assumed to be normalized, then three methods in original article (Sphere Map, Cry Engine 3 and Lambert Azimuthal) are in fact completely equivalent. The old version is still available for the sake of integrity of the internets.

Test Playground Application

Here is a small Windows application I used to test everything below: NormalEncodingPlayground.zip(4.8MB, source included).

It requires GPU with Shader Model 3.0 support. When it writes fancy shader reports, it expects AMD’s GPUShaderAnalyzer and NVIDIA’s NVShaderPerf to be installed. Source code should build with Visual C++ 2008.

Baseline: store X&Y&Z

Just to set the basis, store all three components of the normal. It’s not suitable for our quest, but I include it here to evaluate “base” encoding error (which happens here only because of quantization to 8 bits per component).

Encoding, Error to Power, Error * 30 images below. MSE: 0.000008; PSNR: 51.081 dB.
  

EncodingDecoding
half4 encode (half3 n, float3 view){    return half4(n.xyz*0.5+0.5,0);}
half3 decode (half4 enc, float3 view){    return enc.xyz*2-1;}
ps_3_0def c0, 0.5, 0, 0, 0dcl_texcoord_pp v0.xyzmad_pp oC0, v0.xyzx, c0.xxxy, c0.xxxy
ps_3_0def c0, 2, -1, 0, 0dcl_texcoord2 v0.xydcl_2d s0texld_pp r0, v0, s0mad_pp oC0.xyz, r0, c0.x, c0.ymov_pp oC0.w, c0.z
1 ALURadeon HD 2400: 1 GPR, 1.00 clkRadeon HD 3870: 1 GPR, 1.00 clkRadeon HD 5870: 1 GPR, 0.50 clkGeForce 6200: 1 GPR, 1.00 clkGeForce 7800GT: 1 GPR, 1.00 clkGeForce 8800GTX: 6 GPR, 8.00 clk
2 ALU, 1 TEXRadeon HD 2400: 1 GPR, 1.00 clkRadeon HD 3870: 1 GPR, 1.00 clkRadeon HD 5870: 1 GPR, 0.50 clkGeForce 6200: 1 GPR, 1.00 clkGeForce 7800GT: 1 GPR, 1.00 clkGeForce 8800GTX: 6 GPR, 10.00 clk

Method #1: store X&Y, reconstruct Z

Used by Killzone 2 among others (PDF link).

Encoding, Error to Power, Error * 30 images below. MSE: 0.013514; PSNR: 18.692 dB.
  

Pros:
  • Very simple to encode/decode
Cons:
  • Normal can point away from the camera. My test scene setup actually has that. See Resistance 2 Prelighting paper (PDF link) for explanation.
EncodingDecoding
half4 encode (half3 n, float3 view){    return half4(n.xy*0.5+0.5,0,0);}
half3 decode (half2 enc, float3 view){    half3 n;    n.xy = enc*2-1;    n.z = sqrt(1-dot(n.xy, n.xy));    return n;}
ps_3_0def c0, 0.5, 0, 0, 0dcl_texcoord_pp v0.xymad_pp oC0, v0.xyxx, c0.xxyy, c0.xxyy
ps_3_0def c0, 2, -1, 1, 0dcl_texcoord2 v0.xydcl_2d s0texld_pp r0, v0, s0mad_pp r0.xy, r0, c0.x, c0.ydp2add_pp r0.z, r0, -r0, c0.zmov_pp oC0.xy, r0rsq_pp r0.x, r0.zrcp_pp oC0.z, r0.xmov_pp oC0.w, c0.w
1 ALURadeon HD 2400: 1 GPR, 1.00 clkRadeon HD 3870: 1 GPR, 1.00 clkRadeon HD 5870: 1 GPR, 0.50 clkGeForce 6200: 1 GPR, 1.00 clkGeForce 7800GT: 1 GPR, 1.00 clkGeForce 8800GTX: 5 GPR, 7.00 clk
7 ALU, 1 TEXRadeon HD 2400: 1 GPR, 1.00 clkRadeon HD 3870: 1 GPR, 1.00 clkRadeon HD 5870: 1 GPR, 0.50 clkGeForce 6200: 1 GPR, 4.00 clkGeForce 7800GT: 1 GPR, 3.00 clkGeForce 8800GTX: 5 GPR, 15.00 clk

Method #3: Spherical Coordinates

It is possible to use spherical coordinates to encode the normal. Since we know it’s unit length, we can just store the two angles.

Suggested by Pat Wilson of Garage Games: GG blog post. Other mentions: MJP’s blogGarageGames threadWolf Engel’s bloggamedev.net forum thread.

Encoding, Error to Power, Error * 30 images below. MSE: 0.000062; PSNR: 42.042 dB.
  

Pros:
  • Suitable for normals in general (not necessarily view space)
Cons:
  • Uses trig instructions (quite heavy on ALU). Possible to replace some of that with texture lookups though.
EncodingDecoding
#define kPI 3.1415926536fhalf4 encode (half3 n, float3 view){    return half4(      (half2(atan2(n.y,n.x)/kPI, n.z)+1.0)*0.5,      0,0);}
half3 decode (half2 enc, float3 view){    half2 ang = enc*2-1;    half2 scth;    sincos(ang.x * kPI, scth.x, scth.y);    half2 scphi = half2(sqrt(1.0 - ang.y*ang.y), ang.y);    return half3(scth.y*scphi.x, scth.x*scphi.x, scphi.y);}
ps_3_0def c0, 0.999866009, 0, 1, 3.14159274def c1, 0.0208350997, -0.0851330012,    0.180141002, -0.330299497def c2, -2, 1.57079637, 0.318309873, 0.5dcl_texcoord_pp v0.xyzadd_pp r0.xy, -v0_abs, v0_abs.yxzwcmp_pp r0.xz, r0.x, v0_abs.xyyw, v0_abs.yyxwcmp_pp r0.y, r0.y, c0.y, c0.zrcp_pp r0.z, r0.zmul_pp r0.x, r0.x, r0.zmul_pp r0.z, r0.x, r0.xmad_pp r0.w, r0.z, c1.x, c1.ymad_pp r0.w, r0.z, r0.w, c1.zmad_pp r0.w, r0.z, r0.w, c1.wmad_pp r0.z, r0.z, r0.w, c0.xmul_pp r0.x, r0.x, r0.zmad_pp r0.z, r0.x, c2.x, c2.ymad_pp r0.x, r0.z, r0.y, r0.xcmp_pp r0.y, v0.x, -c0.y, -c0.wadd_pp r0.x, r0.x, r0.yadd_pp r0.y, r0.x, r0.xadd_pp r0.z, -v0.x, v0.ycmp_pp r0.zw, r0.z, v0.xyxy, v0.xyyxcmp_pp r0.zw, r0, c0.xyyz, c0.xyzymul_pp r0.z, r0.w, r0.zmad_pp r0.x, r0.z, -r0.y, r0.xmul_pp r0.x, r0.x, c2.zmov_pp r0.y, v0.zadd_pp r0.xy, r0, c0.zmul_pp oC0.xy, r0, c2.wmov_pp oC0.zw, c0.y
ps_3_0def c0, 2, -1, 0.5, 1def c1, 6.28318548, -3.14159274, 1, 0dcl_texcoord2 v0.xydcl_2d s0texld_pp r0, v0, s0mad_pp r0.xy, r0, c0.x, c0.ymad r0.x, r0.x, c0.z, c0.zfrc r0.x, r0.xmad r0.x, r0.x, c1.x, c1.ysincos_pp r1.xy, r0.xmad_pp r0.x, r0.y, -r0.y, c0.wmul_pp oC0.zw, r0.y, c1rsq_pp r0.x, r0.xrcp_pp r0.x, r0.xmul_pp oC0.xy, r1, r0.x
26 ALURadeon HD 2400: 1 GPR, 17.00 clkRadeon HD 3870: 1 GPR, 4.25 clkRadeon HD 5870: 2 GPR, 0.95 clkGeForce 6200: 2 GPR, 12.00 clkGeForce 7800GT: 2 GPR, 9.00 clkGeForce 8800GTX: 9 GPR, 43.00 clk
17 ALU, 1 TEXRadeon HD 2400: 1 GPR, 17.00 clkRadeon HD 3870: 1 GPR, 4.25 clkRadeon HD 5870: 2 GPR, 0.95 clkGeForce 6200: 2 GPR, 7.00 clkGeForce 7800GT: 1 GPR, 5.00 clkGeForce 8800GTX: 6 GPR, 23.00 clk

Method #4: Spheremap Transform

Spherical environment mapping (indirectly) maps reflection vector to a texture coordinate in [0..1] range. The reflection vector can point away from the camera, just like our view space normals. Bingo! See Siggraph 99 notes for sphere map math. Normal we want to encode is R, resulting values are (s,t).

If we assume that incoming normal is normalized, then there are methods derived from elsewhere that end up being exactly equivalent:

  • Used in Cry Engine 3, presented by Martin Mittring in “A bit more Deferred” presentation (PPT link, slide 13). For Unity, I had to negate Z component of view space normal to produce good results, I guess Unity’s and Cry Engine’s coordinate systems are different. The code would be:
    half2 encode (half3 n, float3 view){    half2 enc = normalize(n.xy) * (sqrt(-n.z*0.5+0.5));    enc = enc*0.5+0.5;    return enc;}half3 decode (half4 enc, float3 view){    half4 nn = enc*half4(2,2,0,0) + half4(-1,-1,1,-1);    half l = dot(nn.xyz,-nn.xyw);    nn.z = l;    nn.xy *= sqrt(l);    return nn.xyz * 2 + half3(0,0,-1);}
  • Lambert Azimuthal Equal-Area projection (Wikipedia link). Suggested by Sean Barrett in commentsfor this article. The code would be:
    half2 encode (half3 n, float3 view){    half f = sqrt(8*n.z+8);    return n.xy / f + 0.5;}half3 decode (half4 enc, float3 view){    half2 fenc = enc*4-2;    half f = dot(fenc,fenc);    half g = sqrt(1-f/4);    half3 n;    n.xy = fenc*g;    n.z = 1-f/2;    return n;}

Encoding, Error to Power, Error * 30 images below. MSE: 0.000016; PSNR: 48.071 dB.
  

Pros:
  • Quality pretty good!
  • Quite cheap to encode/decode.
  • Similar derivation used by Cry Engine 3, so it must be good :)
Cons:
  • ???
EncodingDecoding
half4 encode (half3 n, float3 view){    half p = sqrt(n.z*8+8);    return half4(n.xy/p + 0.5,0,0);}
half3 decode (half2 enc, float3 view){    half2 fenc = enc*4-2;    half f = dot(fenc,fenc);    half g = sqrt(1-f/4);    half3 n;    n.xy = fenc*g;    n.z = 1-f/2;    return n;}
ps_3_0def c0, 8, 0.5, 0, 0dcl_texcoord_pp v0.xyzmad_pp r0.x, v0.z, c0.x, c0.xrsq_pp r0.x, r0.xmad_pp oC0.xy, v0, r0.x, c0.ymov_pp oC0.zw, c0.z
ps_3_0def c0, 4, -2, 0, 1def c1, 0.25, 0.5, 1, 0dcl_texcoord2 v0.xydcl_2d s0texld_pp r0, v0, s0mad_pp r0.xy, r0, c0.x, c0.ydp2add_pp r0.z, r0, r0, c0.zmad_pp r0.zw, r0.z, -c1.xyxy, c1.zrsq_pp r0.z, r0.zmul_pp oC0.zw, r0.w, c0.xywzrcp_pp r0.z, r0.zmul_pp oC0.xy, r0, r0.z
4 ALURadeon HD 2400: 2 GPR, 3.00 clkRadeon HD 3870: 2 GPR, 1.00 clkRadeon HD 5870: 2 GPR, 0.50 clkGeForce 6200: 1 GPR, 4.00 clkGeForce 7800GT: 1 GPR, 2.00 clkGeForce 8800GTX: 5 GPR, 12.00 clk
8 ALU, 1 TEXRadeon HD 2400: 2 GPR, 3.00 clkRadeon HD 3870: 2 GPR, 1.00 clkRadeon HD 5870: 2 GPR, 0.50 clkGeForce 6200: 1 GPR, 6.00 clkGeForce 7800GT: 1 GPR, 3.00 clkGeForce 8800GTX: 6 GPR, 15.00 clk

Method #7: Stereographic Projection

What the title says: use Stereographic Projection (Wikipedia link), plus rescaling so that “practically visible” range of normals maps into unit circle (regular stereographic projection maps sphere to circle of infinite size). In my tests, scaling factor of 1.7777 produced best results; in practice it depends on FOV used and how much do you care about normals that point away from the camera.

Suggested by Sean Barrett and Ignacio Castano in comments for this article.

Encoding, Error to Power, Error * 30 images below. MSE: 0.000038; PSNR: 44.147 dB.
  

Pros:
  • Quality pretty good!
  • Quite cheap to encode/decode.
Cons:
  • ???
EncodingDecoding
half4 encode (half3 n, float3 view){    half scale = 1.7777;    half2 enc = n.xy / (n.z+1);    enc /= scale;    enc = enc*0.5+0.5;    return half4(enc,0,0);}
half3 decode (half4 enc, float3 view){    half scale = 1.7777;    half3 nn =        enc.xyz*half3(2*scale,2*scale,0) +        half3(-scale,-scale,1);    half g = 2.0 / dot(nn.xyz,nn.xyz);    half3 n;    n.xy = g*nn.xy;    n.z = g-1;    return n;}
ps_3_0def c0, 1, 0.281262308, 0.5, 0dcl_texcoord_pp v0.xyzadd_pp r0.x, c0.x, v0.zrcp r0.x, r0.xmul_pp r0.xy, r0.x, v0mad_pp oC0.xy, r0, c0.y, c0.zmov_pp oC0.zw, c0.w
ps_3_0def c0, 3.55539989, 0, -1.77769995, 1def c1, 2, -1, 0, 0dcl_texcoord2 v0.xydcl_2d s0texld_pp r0, v0, s0mad_pp r0.xyz, r0, c0.xxyw, c0.zzwwdp3_pp r0.z, r0, r0rcp r0.z, r0.zadd_pp r0.w, r0.z, r0.zmad_pp oC0.z, r0.z, c1.x, c1.ymul_pp oC0.xy, r0, r0.wmov_pp oC0.w, c0.y
5 ALURadeon HD 2400: 2 GPR, 4.00 clkRadeon HD 3870: 2 GPR, 1.00 clkRadeon HD 5870: 2 GPR, 0.50 clkGeForce 6200: 1 GPR, 2.00 clkGeForce 7800GT: 1 GPR, 2.00 clkGeForce 8800GTX: 5 GPR, 12.00 clk
7 ALU, 1 TEXRadeon HD 2400: 2 GPR, 4.00 clkRadeon HD 3870: 2 GPR, 1.00 clkRadeon HD 5870: 2 GPR, 0.50 clkGeForce 6200: 1 GPR, 4.00 clkGeForce 7800GT: 1 GPR, 4.00 clkGeForce 8800GTX: 6 GPR, 12.00 clk

Method #8: Per-pixel View Space

If we compute view space per-pixel, then Z component of a normal can never be negative. Then just store X&Y, and compute Z.

Suggested by Yuriy O’Donnell on Twitter.

Encoding, Error to Power, Error * 30 images below. MSE: 0.000134; PSNR: 38.730 dB.
  

Pros:
  • ???
Cons:
  • Quite heavy on ALU
EncodingDecoding
float3x3 make_view_mat (float3 view){    view = normalize(view);    float3 x,y,z;    z = -view;    x = normalize (float3(z.z, 0, -z.x));    y = cross (z,x);    return float3x3 (x,y,z);}half4 encode (half3 n, float3 view){    return half4(mul (make_view_mat(view), n).xy*0.5+0.5,0,0);}half3 decode (half4 enc, float3 view){    half3 n;    n.xy = enc*2-1;    n.z = sqrt(1+dot(n.xy,-n.xy));    n = mul(n, make_view_mat(view));    return n;}
ps_3_0def c0, 1, -1, 0, 0.5dcl_texcoord_pp v0.xyzdcl_texcoord1 v1.xyzmov r0.x, c0.znrm r1.xyz, v1mov r1.w, -r1.zmul r0.yz, r1.xxzw, c0.xxywdp2add r0.w, r1.wxzw, r0.zyzw, c0.zrsq r0.w, r0.wmul r0.xyz, r0, r0.wmul r2.xyz, -r1.zxyw, r0mad r1.xyz, -r1.yzxw, r0.yzxw, -r2dp2add r0.x, r0.zyzw, v0.xzzw, c0.zdp3 r0.y, r1, v0mad_pp oC0.xy, r0, c0.w, c0.wmov_pp oC0.zw, c0.z
ps_3_0def c0, 2, -1, 1, 0dcl_texcoord1 v0.xyzdcl_texcoord2 v1.xydcl_2d s0mov r0.y, c0.wnrm r1.xyz, v0mov r1.w, -r1.zmul r0.xz, r1.zyxw, c0.yyzwdp2add r0.w, r1.wxzw, r0.xzzw, c0.wrsq r0.w, r0.wmul r0.xyz, r0, r0.wmul r2.xyz, -r1.zxyw, r0.yzxwmad r2.xyz, -r1.yzxw, r0.zxyw, -r2texld_pp r3, v1, s0mad_pp r3.xy, r3, c0.x, c0.ymul r2.xyz, r2, r3.ymad r0.xyz, r3.x, r0, r2dp2add_pp r0.w, r3, -r3, c0.zrsq_pp r0.w, r0.wrcp_pp r0.w, r0.wmad_pp oC0.xyz, r0.w, -r1, r0mov_pp oC0.w, c0.w
17 ALURadeon HD 2400: 3 GPR, 11.00 clkRadeon HD 3870: 3 GPR, 2.75 clkRadeon HD 5870: 2 GPR, 0.80 clkGeForce 6200: 4 GPR, 12.00 clkGeForce 7800GT: 4 GPR, 8.00 clkGeForce 8800GTX: 8 GPR, 24.00 clk
21 ALU, 1 TEXRadeon HD 2400: 3 GPR, 11.00 clkRadeon HD 3870: 3 GPR, 2.75 clkRadeon HD 5870: 2 GPR, 0.80 clkGeForce 6200: 3 GPR, 12.00 clkGeForce 7800GT: 3 GPR, 9.00 clkGeForce 8800GTX: 12 GPR, 29.00 clk

Performance Comparison

GPU performance comparison in a single table:

#1: X & Y#3: Spherical#4: Spheremap#7: Stereo#8: PPView
Encoding, GPU cycles
Radeon HD24001.0017.003.004.0011.00
Radeon HD58700.500.950.500.500.80
GeForce 62001.0012.004.002.0012.00
GeForce 88007.0043.0012.0012.0024.00
Decoding, GPU cycles
Radeon HD24001.0017.003.004.0011.00
Radeon HD58700.500.950.501.000.80
GeForce 62004.007.006.004.0012.00
GeForce 880015.0023.0015.0012.0029.00
Encoding, D3D ALU+TEX instruction slots
SM3.01264517
Decoding, D3D ALU+TEX instruction slots
SM3.08189822

Quality Comparison

Quality comparison in a single table. PSNR based, higher numbers are better.

MethodPSNR, dB
#1: X & Y18.629
#3: Spherical42.042
#4: Spheremap48.071
#7: Stereographic44.147
#8: Per pixel view38.730


Posted by ozlael
,