저는 COCOS 2D-X로 개발을 하고 있습니다만, 유니티는 교양(?)으로 알아두어야 할 것 같아서 만지작거려볼까 하는 중입니다. 그래서 유니티 포럼이나 웹을 돌아다니다보니 라이팅에 관한 질문이 가끔 보이더군요.
유니티의 라이팅이 안이뻐요.
유니티의 라이팅이 딱딱해요.
유니티의 라이팅이 입체감이 없어요.
이번 포스팅에서는 이에 대한 이야기를 해볼까 합니다. 사실 이 주제가 유니티에만 국한되어 하는 이야기는 아닙니다. (제목 낚시 ㅈㅅ) CG 특히 게임같은 실시간렌더링(real-time rendering)은 은 자연 그대로의 빛을 표현하는 것을 불가능하기때문에 어느정도 간략화하여 묘사하는 알고리즘을 쓰고 있고, 많은 엔진들이 제공하는 기법들은 비슷비슷합니다. 동네 표준이란게 있어서;; 그렇기때문에 본 포스팅에서 언급되는 이야기는 Unity 3D에만 국한 된 것이 아니라 통상적인 실시간 렌더링에 관한 이야기이며, 보여드리는 자료도 Unity 3D에만 국한되지 않음을 미리 말씀드립니다.
또한 직접조명(direct light)에 관해서만 다룰것이며 구면조화함수(spherical harmonics)나 앰비언트큐브(ambient cube)등의 라이트프로브(light probe)를 이용한 지역조명(local light)에 관해서는 다루지 않을 것입니다. 당연히 실시간 GI(global illumination) 역시 마찬가지이구요. 이미 그걸 찾으시려는 분은 이 글을 읽을 필요가 없어요.
Default Directional Light
일반적으로 엔진들에서 기본적으로 제공되는 디렉셔널 라이팅은 모두 딱딱해보이고 입체감이 부족해보입니다. 기본 라이팅이 너무 간략화되서 표현 된 라이팅이기 때문이죠. 일단 다음 그림을 보시죠.
빛은 보시는바와 같이 단순하지가 않습니다. 기본적인 음영 외에도 반사광, 역광, 강세 등등 다양한 빛의 요소가 존재합니다. 하지만 엔진의 기본적인 디렉셔널라이트에는 그러한 것이 생략되어 있습니다. 단순히 빛의 방향을 보는 면은 밝고 그 반대 방향 즉 빛을 등지는 부분은 어둡게 표현하는게 다입니다. 그렇기때문에 빛이 “딱딱하게” 보이는 것 입니다.
거기다가 문제가 더 있습니다. 빛의 반대면은 단순히 어둡기만 한 것이 아니라 어두운 부분은 명암이 아예 사라진다는 것입니다.
빛의 명암은 빛 벡터와 면의 노말 벡터와의 내적값을 사용합니다. 정확히는 물체에서 빛을 보는 방향의 벡터와 면의 노말 벡터를 내적하는 값이죠. (벡터와 내적에 대한 개념을 모르신다면 대마왕님의 시리즈 글을 읽어보시길 권장합니다. 아티스트 프로그래머 모두에게 도움이 될 것입니다. 프로그래머시면 맥스의 디테일한 내용은 건너 뛰고 설명만 보세요. 시작! http://www.gamedevforever.com/228)
음영값 = N(면의 노말) dot L(면에서 태양으로의 방향)
두 벡터가 같은 방향을 보고 있으면 1이 나오고, 직각을 이루고 있으면 0이 나오고, 반대방향을 보고 있으면 -1이 나옵니다. 따라서 내적 값을 음영값으로 사용하게 되면 빛을 보는 면은 1의 값 즉 제일 밝은면이 됩니다. 근데 빛의 정 반대면은 -1이 나오게 됩니다. 이런 경우에는 음수 조명은 존재하지 않으므로 0 이하의 값은 그냥 0으로 취급해버립니다.
diffuse = max(0, dot(L, N));
그러다보니 빛의 반대 영역은 음영이 존재하지 않고 죄다 까맣게 되어버리는 것이지요. 그러다보니 어두운 영역의 입체감이 존재하지 않아서 “입체감이 없다”고 느껴지는 것입니다.
하이엔드 PC게임 렌더링에서는 역광, 반사광, AO 등의 간접조명들이 처리되어 이러한 현상이 보완됩니다. 하지만 그러한 성능이 나오지를 못하는 모바일에서는 현실적으로는 디렉셔널라이트같은 직접조명만 처리가 가능하기때문에 다양한 방식이 필요합니다.
Half Lambert
어두운 영역이 죄다 0으로 되어 음영이 죽어버리는 현상은 하프램버트를 적용해줘도 크게 완화됩니다. 빛 벡터와 노말 벡터의 내적 음영값이 1~0~-1로 나와서 0이후부터는 0으로 채워버리니까 애초에 결과 값을 0~0.5~1로 나오게 하면 되는 것이지요. N dot L의 식을 조금만 손봐주면 됩니다.
halflambert_diffuse = max(0, (dot(L, N) + 1) / 2);
이렇게 하면 암부 영역의 음영이 살아나게되서 널리 쓰이곤 합니다.
다만 쉐도우맵 방식의 그림자와는 궁합이 안맞아서 신중히 생각해봐야 한다는 문제는 있지만 모바일에서 아직 쉐도우맵은 사치일테니 우선 당장은 고민거리는 아닐 것 같네요.
참고 자료 :
Diffuse Wrap
음영을 아티스트가 직접 그려넣는 방식도 있습니다. 음영 스펙트럼을 직접 아티스트가 선택하여 칠한 텍스쳐를 오브젝트에 감싸는 것이지요. 앞서서 언급했듯이 자연상태의 빛은 직접조명만 존재하는 것이 아니라 주변 사물에 빛이 반사되어 들어오는 간접조명들도 존재합니다. 따라서 음영의 스펙트럼이 단순히 선형적인 흰색~검은색만 되는 것이 아닙니다. 텍스쳐로 음영을 표현하면 이러한 색의 스펙트럼을 직접 제어가 가능해져서 아티스트들이 선호합니다.
방식도 간단합니다. 다음과 같은 라이팅 스펙트럼 텍스쳐를 준비해놓습니다. 위의 하프램버트 값을 바로 음영 값으로 사용하는 것이 아니라 스펙트럼 텍스쳐의 U좌표로 사용하면 땡인 것이지요. 큰 노력 들이지 않고도 아티스트느님께 이쁨받을 수 있어요 하앍하앍
더 나아가서 큐브맵(Cube-Map)을 통채로 라이팅 결과로 사용하는 IBL(Image Based Lighting) 방식이 이용되기도 합니다. 큐브맵은 일반적으로 쓰는 2차원 텍스쳐와는 달리 상하좌우앞뒤 6면을 가지고 있는 텍스쳐입니다. 보통 스카이박스가 큐브맵으로 만들어집니다.
이러한 큐브맵에 그림 대신 라이팅을 새겨넣고 오브젝트의 면에 바로 입혀버리는 것이지요.
위의 1차원 방식과는 달리 간접조명을 동서남북위아래 모두 반영할 수 있어서 아티스트의 자유도가 더 높아져서 더 높은 퀄리티를 낼 수 있습니다. 하지만 큐브맵은 추가적인 성능이 요구되니 사양을 고려해서 사용해야 합니다.
참고 자료:
http://www.gamedevforever.com/150
http://www.gamedevforever.com/269
http://www.gamedevforever.com/272
http://www.gdcvault.com/play/1014362/Cinematic-Character-Lighting-in-STAR
http://pds8.egloos.com/pds/200803/05/32/TeamFortress2mitchell.pdf
http://www.slideshare.net/valhashi/2011-03-gametechtadptforpdf
Hemisphere Lighting
다소 저렴한 방법으로 간접조명을 흉내 낼 수 있는 방법으로 반구조명(Hemisphere Lighting) 기법이 존재합니다. 월드를 감싸는 구를 반토막 내서 하늘에서 수직으로 내려오는 및과 땅에서 수직으로 올라오는 빛이 존재한다고 가정하는 것입니다. 수직으로 향하는 각각 다른 컬러를 가지는 라이트를 디렉셔널라이트에 추가적으로 더해주는 것입니다.
그러한 컨셉과 마찬가지로 디렉셔널 라이트를 여러개를 추가해버리는 방법도 가능합니다.
관련 자료:
http://www.slideshare.net/ozlael/deferred-rendering-case-study
http://www.gamasutra.com/view/feature/2817/hemisphere_lighting_with_radiosity_.php
Sub-Surface Scattering
아무리 빛을 이래 저래 만지작거려봐도 인간형 케릭터에게는 어색함이 사라지지 않을 수도 있습니다. 얼굴이나 팔 등의 피부가 느낌이 안살아서 그러는 것이지요.
손을 전등이나 태양을 향해 대보세요. 빛이 완전 차단되는 것이 아니라 조금은 반영되어 보일것입니다. 또한 음영을 자세히 살펴보세요. 어두운 영역과 밝은 영역 사이에 붉은 영역이 존재할 것입니다. 피부는 완전 불투명한 재질이 아니라 반투명 재질이 여러겹으로 구성되어 있기때문에 빛이 직선으로 통과되는 것이 아니라 빛이 산란되어 통과됩니다. 그 안에 존재하는 모세혈관들이 산란된 빛을 통해 비춰지면서 특이한 느낌이 나는 것입니다. 그러한 피부 재질을 단순한 방식으로 표현하려니 어색함이 존재하는 것이지요.
이러한 현상을 표면하산란(Sub-Surface Scattering,SSS)라 부르고 비실시간 렌더링에서는 이를 시물레이션해서 표현합니다. 하지만 게임에서는 이를 시뮬레이션하는 수준까지는 못하고 대충 흉내내는 방식을 사용합니다.(fake SSS) 음영 사이에 붉은 빛이 도는 느낌을 표현해주는 것이지요.
이 느낌을 코드 공식으로 만들어 내는 것도 복잡하지는 않습니다만 그냥 텍스쳐로 표현해버리세요. 모바일에서는 텍스쳐로 표현하는게 더 싸게 먹힐꺼예요. 느낌 아니까~
관련 자료:
http://http.developer.nvidia.com/GPUGems/gpugems_ch16.html
http://www.slideshare.net/ozlael/deferred-rendering-case-study
Color Correction ( or Color Grading)
컬러커렉션(Color Correction) 혹은 컬러그레이딩(Color Grading)으로 화룡정점을 찍어보는 것도 괜챦습니다. 컬러그레이딩은 엄밀히 따지자면 조명 연산이 아니라 색의 톤을 조절하는 방식입니다. 모바일에서는 성능 문제로 무리가 있을 것이라 생각했었는데 기기의 성능들이 좋아지면서 가능해졌습니다.
상단이 컬러그레이딩을 적용하기 전이고 하단이 컬러그레이딩을 적용한 후의 이미지입니다. 적용하고 나니 뭔가 파스텔톤의 느낌이 나고 훨씬 그래픽이 부드러워진 느낌이 납니다. 추가적인 비용 부담도 존재하긴 하지만 시각적인 효과가 뛰어나서 옵션으로 적용하는 등 여건만 된다면 적용해볼 만 합니다. 원리는 간단합니다. 디퓨즈까지 반영 된 최종 픽셀의 값을 스펙트럼 텍스쳐로 매핑해서 보여주면 땡입니다.
관련 자료:
http://http.developer.nvidia.com/GPUGems/gpugems_ch22.html
http://docs.unity3d.com/Documentation/Components/script-ColorCorrectionEffect.html
마무리
이래 저래 조명 방식들을 설명해드렸습니다만 프로젝트에 적합한 조명 방식을 정하는 것은 쉬은일이 아닙니다. 게다가 한번 정해지면 다시 변경하기란 불가능에 가깝습니다. 조명이 바뀌면 거기에 맞춰서 만들어져왔던 리소스들을 다시 엎어야 하는 상황들이 발생하기 때문이죠. 따라서 프로젝트 초반에 프로그래머와 아티스트가 함께 테스트를 해보며 신중히 결정하여야 할 것입니다. 그럼 모두들 즐삽질~!
지금까지 설명 드린 기능들의 유니티 관련 페이지
http://docs.unity3d.com/Documentation/Components/script-ColorCorrectionEffect.html
http://docs.unity3d.com/Documentation/Components/SL-SurfaceShaderExamples.html
http://docs.unity3d.com/Documentation/Components/SL-SurfaceShaderLightingExamples.html
http://www.unitymanual.com/thread-1272-1-1.html
http://www.farfarer.com/blog/2011/07/25/dynamic-ambient-lighting-in-unity/
http://www.farfarer.com/blog/2013/02/11/pre-integrated-skin-shader-unity-3d/
https://www.youtube.com/watch?v=XBTB17hcbio&feature=related
'프로그래밍' 카테고리의 다른 글
cocos2d-x 3.0 beta의 확장 라이브러리 사용 설정 : win32, android (0) | 2014.02.04 |
---|---|
cocos2d-x 3.0 beta의 개발 환경 설치 및 빌드 : win32, android (7) | 2014.01.20 |
프로그래머가 수학을 못하면 (6) | 2013.12.11 |
복셀 에디팅 팁 (0) | 2013.12.07 |
알파혼합 가능한 RGBM (0) | 2013.11.29 |