The water effects in Super Mario Sunshine: Implementation
이 토이 프로젝트(?)의 github repository 및 Live Demo는 아래 링크에서 확인할 수 있습니다.
- github repository
- Live Demo (PC 접속 권장, 모바일 불가)
*Reflection 관련 Bug Fix 남음.
개요
이전 글에서도 언급했지만 물이라는 물질을 그래픽스로 구현하기 위해서는 물의 4가지 특성에 유의해야한다.
- wave
- refraction
- reflection (environment & specular)
- transparency
이중에서 wave, specular, transparency는 이 포스트의 도움으로 손쉽게 작성할 수 있었다.
내용을 요약하면 이렇다:
- 물결 무늬 텍스처 두 장을 겹쳐두고 시간 흐름에 따라 각기 다른 방향으로 이동시킴으로서 wave 구현.
- 텍스처는 흑백이며, 하얄수록 밝은 부분을 어두울 수록 투명한 부분을 의미. 즉, 색상 합성은 더하기(additive)를 이용한다.
- LOD (Level Of Detail) 값에 따라 텍스처가 취사 선택
- LOD값이 가장 낮은 단계와 가장 높은 단계에서 텍스처는 완전히 검은색, 중간 단계일수록 하얗다
- 따라서 플레이어 바로 근처와 멀리 있는 수면은 투명하게 보이게 된다. 그리고 LOD값이 중간 단계에 속하는 범위에 대해서는 마치 실제 바다에 태양빛이 비추는 것처럼 아주 밝은 띠(band)를 형성하게 된다.
- 모든 계산을 마친 후 Alpha test를 통해 밝기가 중간 범위의 밝기 점들은 그리지 않고, 보다 극단적인 범위에 속하는 점들만 그린다.
- 앞서 생성했던 밝은 띠(band)에서 극단적으로 밝은 점들만 선택해 그림으로써 주위와 대비되는 밝은 강조 효과 = highlight, 즉 specular 효과를 줄 수 있다.
- 중간 범위에 속하는 상당수의 점이 버려지기 때문에 좀더 transparent하게 보인다.
Alpha Test 적용 이전 Alpha Test 적용 이후
여기까지가 wave, specular, transparency이다. 비록 코드 자료는 찾아볼 수 없었으나 설명이 아주 상세히 적혀있었기에 여기까지는 빠르게 작성할 수 있었다. 문제는 refraction과 reflection이었다. 일단 내가 재현하고자 하는 대상인 ‘Super Mario Sunshine’의 물 효과는 조금 거칠게 말하면 ‘텍스처 차력쇼’이다. 앞서 설명했던 부분들도 사실상 거진 텍스처를 이용해 구현한 것이었는데, refraction과 reflection 또한 그렇다. 사실 지금까지 refraction과 reflection을 구현하는데 있어서 이런식으로 텍스처를 사용해본 적은 없었다.
지금까지 내 전적들:
- refraction -> 비싼 방법 씀 (e.g Ray-Tracing)
- reflection -> 쉐이딩 모델의 수식 사용하여 간단히 처리 (e.g Phong Shading)
앞선 wave, specular, transparency 구현만으로도 핵심 트릭은 전부 구현했다고 생각한다. 그러나, 텍스처를 이용한 refraction과 reflection 기법들이 CG업계에서 꽤 흔함에도 지금껏 구현해 본 적이 없었다는 사실이 갑자기 창피해져서(?) 이왕 이렇게 시작한거 전부 구현해보았다.
그래서 나온 결과가 아래와 같다.
Refraction과 Reflection의 구현
refraction과 reflection을 구현함에 있어 어려움을 겪었던 부분은 다음과 같다.
- refraction과 reflection의 처리 프로세스 (특히 순서)
- 먼 환경의 environment reflection 구현
Refraction과 Reflection의 처리 프로세스
가장 크게 착각했던 부분은 refraction과 reflection의 처리 순서가 정해져있다고 생각한 것이다. 결론부터 말하자면, refraction과 reflection 처리에 있어 선후 관계는 존재하지 않는다.
나는 이렇게 착각했다: ‘반사되는 오브젝트도 굴절된다’…
물리적으로 refraction과 reflection은 전혀 다른 현상인데 어떻게 이런 착각을 할 수가 있냐고? 아니, 나는 물리적 현상인 ‘반사’와 ‘굴절’을 헷갈린 것이 아니다. 이 착각은 ‘출렁이는 수면’을 그럴듯하게 표현하기 위해선 refracted object와 reflected object에 모두 distortion을 넣어야한다, 라는 생각에서 온 것이다.
컴퓨터 그래픽스 관점에서 보았을 때, 특히 이번과 같은 텍스처 처리 기반 표현이라고 할 때 굴절은 단지 uv좌표 값을 조금 흔들어서 distort를 넣는 것이다. 그런데 보통 출렁거리는 물의 수면을 보면 반사되는 물체 또한 일렁이게 보이지 않는가? 그 점에서 나는 반사되는 오브젝트 또한 refraction와 같은 효과를 얻어야한다고 생각했다. 따라서 reflection의 처리 후에 그 결과가 refraction 단계에서 함께 처리되어야한다고 생각했다.
그런데 그렇게 구현하니 보이는 화면이 많이 이상했고, 그제서야 reflection과 refraction은 각기 ‘대상 오브젝트’만이 그려져있는 별도의 텍스처로 작업해야한다는 사실을 깨달았다. 두 처리간 의존성이 있어 순서가 존재하는 것(i.e 렌더 패스)이 아니라 텍스처끼리 적당히 합성(blending)만 하면 되는 것이었다.
vec2 screenUV = gl_FragCoord.xy / screenSize;
vec2 distort = (vec2(wave1, wave2) - 0.5) * 0.05;
vec2 distortedUV = clamp(screenUV + distort, 0.0, 1.0);
vec3 refractionTexColor = texture(refractionTex, distortedUV).rgb;
vec3 reflectionTexColor = texture(reflectionTex, distortedUV).rgb;
위 코드에 나와있는 것처럼 distort된 uv좌표를 이용해 색상을 얻는 과정은 각 텍스처마다 진행한다. ‘적당히’ 자연스러워 보이기만 하면 되기 때문에 refract와 reflect 텍스처에 똑같은 uv좌표값을 사용해도 무방하다. 물리적으로는 엄연히 다른 현상이니 수식도 달라야겠지만, 애초에 텍스처 기반 구현에서는 정확한 묘사는 불가하다. ‘적당히 자연스러워’ 보이기만 하면 된다.
(조금 변명을 해보자면, 아마 내게는 프로그래머로서 이런 신념(?)이 있었던 것 같다: ‘같은 작업은 한번에 처리한다’. 일반적인 경우 batch processing이 성능상 이점이 있기 때문이다. 근데 결과물이 의도한대로 나오지 않으면 성능상 좋아도 의미가 없다 …. 이번에 그 사실을 뼈저리게 깨달았다.)
먼 환경의 Environment Reflection 구현
그 다음으로 했던 실수는 ‘환경 반사’, 그 중 특히 하늘과 같은 먼 환경의 반사 구현이다. (수면 위 오브젝트의 경우에는 해야할 일이 명확했기 때문에 딱히 문제를 겪지 않았다.)
내가 사용한 skybox는 하늘에 구름이 가득하기 때문에, 수면 위 상당 부분이 하얗게 뒤덮여 있다. 처음 환경 반사를 구현하기 시작 했을 때 나는 ‘환경에는 배경도 포함’이라는 생각으로 skybox까지 그대로 FBO에 그려 텍스처로 사용했다.
근데 그 텍스처를 활용해 환경 반사를 구현하니까 수면이 회색 빛으로 탁해지고, 수중 시야 또한 보이지 않아 전혀 물 같지 않았다.
성능상 이점을 취해야하는 경우가 아니라면 물리 법칙인 프레넬(Fresnel) 공식, 즉 각도를 기반으로 한 반사 공식을 사용하는 것이 정석적인 방법이다. 프레넬 공식에 따르면, 물을 보는 방향이 수평일 수록 반사가 강해지고 수직일 수록 약해진다. 중요한 건 입사각을 parameter로 하여 그에 따라 반사율이 계산된다는 것이다.
만약 나 또한 입사각을 parameter로 삼아 반사율을 동적으로 조절했으면 조금 덜 어색해보였을 수 있다. 근데… ‘정석’과는 별개로, 이 게임은 하드웨어 한계상 ‘입사각에 따른 반사율 동적 조절’ 그런거 못 한다.
그래서…
The third mipmap level is the brightest, which corresponds to that “band” of bright shininess in the middle. This band, I believe, is a clever way of faking enviornment reflections. At that camera distance, you can imagine we’d mostly being the reflection from our skybox when at a 20 degree angle looking into the water, like our clouds. In Sirena Beach, this band is tinted yellow to give the level a beautiful yellow glow that matches the evening sunset.
… 그렇다. 가장 밝은(brightest)한 부분이 specular이면서 (다소 부정확하지만 그럴 듯한)enviornment reflection이기도 한 것이다. 윗 글에 언급은 안 되어있지만 입사각이 수직에 가까울 수록 반사율이 낮아지는, 즉 투명해지는 것 또한 가장 어두운 부분에 의해 구현된다. (색상 합성 방법이 ‘더하기’이므로 텍스처의 검은색이 곧 투명색이다.)
결국 텍스처를 활용해 wave, specular, transparency를 구현하면서 하늘에 대한 environment reflection은 자연스럽게 겸사겸사(…) 구현이 된 것이다. 배경 색에 따라 brightest한 부분의 색만 조금 조절해주면 된다.
이게 가능한 이유는 역시 ‘출렁이는 표면이 있는 물’의 구현이라서 그렇다. 더 정확히 말해보자면… ‘아무튼 현실 세계에서도 그렇게 보이니까’.
[!note] 물리적으로 specular와 environment reflection은 똑같은 ‘반사’ 현상이지만 그래픽스에선 따로 취급한다. 컴퓨터 상으로 이를 구현한다고 했을 때 구현 방법이 다르기 때문이다.
마치며
초등학생 시절부터 관심이 있었던 Super Mario Sunshine의 물 표현 구현을 대학생이 돼 직접 해보게 되어 감개무량하다.
물론 Super Mario Sunshine은 무려 거의 20년 전인 2006년에 출시된 상당히 옛날 게임이다. 고로 당시에는 이 게임의 물 표현이 센세이셔널하게 다가왔다한들, 지금 시점에서는 구닥다리 기술들이라고 말할 수도 있겠다. 심미적으로 예민한 사람들은 상대적으로 더 사실적인 현대의 물 표현과 비교해서 ‘별로’라고 할 수도 있고 말이다.
그러나 나는 한 시대를 풍미했던 기술이라면, 어떤 기술이라도 의미가 있다고 생각한다. 한때라도 극찬을 받았다고 한다면, 그건 분명 그때는 ‘대단한 것’이었다. 그 이전에는 아무도 할 수 없었던 것이다.
중요한 건 우리의 세상과, 내가 풀어야만 하는 문제가 주어지는 상황은 시시때때로 변한다는 것이다. 나는 범재이기 때문에 그런 ‘대단한 것’들을 최대한 습득하고 내 것으로 만들어 내게 주어진 문제를 그들과 같이 창의적으로 해결하고 싶다. (솔직히 이번에는 내 사심이 많이 들어가긴 했다. 어릴 때 아주 좋아했었던 게임이었기 때문에…)
그리고 이번에 컴퓨터 그래픽스라는 분야의 특성이자 목적성을 더욱 잘 알게 됐다.
한마디로, ‘일단 그럴 듯 해보이면 OK’
… 뭐 보이는게 중요한 분야니까 맞다고 생각한다. 물론 물리적 엄밀함을 충족시키면 좋겠지만, 하드웨어 조건이 안 좋으면 그런 건 우선순위에서 밀려난다.
선샤인의 물 표현은 지금봐도 꽤나 수준급이기에 구현 방법을 알아보기 전까지는 엄청 복잡해 보였는데 생각보다 간단하게 구현되어서 놀랐다. 물론 그러니까 이 기술을 고안한 사람들이 대단한거지만….
아무튼, 당시 하드웨어 기술의 한계를 넘어 물 같은 물을 표현하기 위해 이런 창의적인 기술을 고안한 닌텐도에게 경의를 표하며 글 마칩니다.
References
- YouTube: Recreating the Water Effect from Super Mario Sunshine in Unity (exp1Yrxt50A)
- PC Perspective: The Graphics Technology of Super Mario Sunshine
- mechEYE blog: Deconstructing the Water Effect in Super Mario Sunshine
- Dolphin Emulator Blog: Add Heuristic for Detecting Custom Mipmaps
- The Models Resource: Super Mario Sunshine – Water (Object)
- Reddit: I’m trying to recreate the water effect from Super Mario Sunshine (Blender Help)
- YouTube: Super Mario Sunshine Water Effect Breakdown (8rCRsOLiO7k)
- YouTube: Super Mario Sunshine’s Water is Wild (Ipsd6rYj6Mk)
- Copetti: GameCube Architecture and Analysis