▲ 부산게임아카데미 김성완 교수

[인벤게임컨퍼런스(IGC) 발표자 소개] 前 미리내소프트웨어 개발이사부터 부산게임아카데미 교수까지 많은 경험을 가진 대한민국 게임사의 산 증인. 지금은 후학을 양성하며 인디게임 커뮤니티를 운영하고 있다.

폴리곤으로 구를 그리려면 상당히 많은 데이터와 작업량을 수반한다. 코드 역시 산더미 처럼 불어난다. 폴리곤을 잘게 잘라 구현해야 하기 때문이다. 그런데 레이마칭(Ray Marching) 알고리즘을 활용하면 단 한 줄의 코드로 구를 구현할 수 있다. 코드를 줄이는 것뿐만 아니라 단 4K(4096byte)로 크기를 줄일 수도 있다.

부산게임아카데미 김성완 교수는 아직 국내에서는 생소한 알고리즘인 거리함수 기반의 레이마칭 알고리즘의 원리를 소개했다.



■ 강연주제: ShaderToy에서 구현하는 거리함수 기반의 레이마칭 렌더링 알고리즘

⊙ 쉐이더 토이(Shadertoy)란?

쉐이더 토이는 WebGL을 바탕으로 만들어진 사이트로 웹 페이지 상에서 바로 쉐이더 프로그래밍을 할 수 있게 해주며 이를 공유할 수 있게 해준다. 기본적으로 HTML5와 오픈지엘(Open GL)의 간략한 버전인 오픈지엘 이에스(Open GL ES)를 사용한다.

과거 픽사 에니메이션 스튜디오에서 테크니컬 디렉터를 역임한 이니고 퀼레즈( Iñigo Quilez)등 5인이 만든 이 사이트에서는 순수하게 수학적인 알고리즘을 활용한 코딩만으로 간단하게 원하는 물체를 그릴 수 있다.

웹 페이지상에서 코딩하면 바로 실시간으로 렌더링 되는 것을 확인할 수 있다. 전부 순수하게 코딩만으로 구현하는 것으로 텍스쳐도 어디서 불러오는 게 아니라 코딩만으로 구현할 수 있다.

일반적으로 3D 모델링은 맥스나 마야의 작업물을 가져와서 띄우는데 이처럼 코딩만으로도 나뭇잎의 움직임, 물방울 표현, 빛의 굴절 등을 만들 수 있다. 원리는 간단하다. 수학 공식이다.

[▲formulanimations tutorial :: a simple apple]


[▲formulanimations tutorial :: making a snail]



⊙ 프랙털 그래픽(Fractal Graphic)

하루아침에 코드 몇 줄로만 렌더링할 수 있게 된 것은 아니다. 이 부분 역시 꾸준히 발전을 해왔다. 알고리즘만으로 렌더링할 수 있게 된 계기에는 '프랙털'이 있다.

프랙털은 일부 작은 조각이 전체와 비슷한 기하학적 형태를 말한다. 이런 특징을 자기 유사성이라고 하며, 다시 말해 자기 유사성을 갖는 기하학적 구조를 프랙털 구조라고 한다. 이 단어는 브누아 망델브로(Benoit B. Mandelbrot )가 처음 사용한 단어로 수학적 분석, 생태학적 계산, 위상 공간에 나타나는 운동모형 곳곳에서 발견된다. 대표적인 프랙털 도형인 망델브로 집합은 망델브로의 이름에서 따왔다.

▲망델브로 집합.

▲망델브로 집합의 경계선

망델브로 집합은 복소수 공간에 존재하는 집합이다. 흰색 공간은 집합 내에 포함됐다는 뜻이고 검은색은 그 반대다. 그 경계선이 무한히 복잡한 경계를 가지는 것이 특징이다. 무한히 확대해도 무한히 다른 모습을 발견할 수 있다.

무한히 복잡한 경계를 가지고 있기 때문이 이런 집합을 생성하는 것이 매우 어려울 것 같지만, 사실 아주 간단한 공식으로 생성할 수 있다.

▲ 망델브로 집합의 공식

고민은 삼차원 프랙털 도형을 어떻게 렌더링할 것인가로 이어졌다. 일반적으로 삼차원 공간은 폴리곤으로 렌더링해왔다. 그러나 복잡하고 기묘한 프랙털을 폴리곤으로 표현하기에는 사실상 불가능했다. 얼마나 많은 폴리곤을 쪼개야 하는지 감도 안 잡혔다. 당연히 데이터양도 많이 늘어날 수밖에 없고 말이다. 그래서 만들어진 것이 레이 마칭 알고리즘이다.

▲ 3D 망델로브의 확대



⊙ 레이 트레이싱 & 라이팅 (Ray tracing & Lighting)

레이 마칭 알고리즘을 이해하기 위해서는 레이 트레이싱을 먼저 이해할 필요가 있다. 레이 트레이싱은 빛이 가는 길을 추적해 굴절, 반사를 계산해주는 전통적인 알고리즘으로 빛을 아주 사실적으로 묘사할 수 있다. 과거에는 실시간으로 계산해 구현하는 게 불가능했지만, 현재는 실시간 렌더링이 가능하다.

▲ Ray tracing이 적용된 화면.

레이 트레이싱 알고리즘의 원리는 간단하다. 사람이 빛을 인지하는 과정을 역으로 수행하면 된다. 사람과 같은 과정을 추적하기에는 눈에 들어오지 않는 광선까지 추적해야 하므로 낭비가 심하기 때문이다. 가상의 스크린을 통과하는 것으로 추정되는 광선만 추적하면 된다.

▲Ray tracing 알고리즘

1980년 공개된 최초의 레이 트레이싱은 평면상에 반사구가 있고 빛의 굴절 및 투명을 표현했다. 당시 컴퓨터 사양 때문에 실시간 렌더링은 불가능했다. 1984년에는 루카스 아츠 CG 연구팀에서 모션블러를 넣은 레이 트레이싱을 선보인다. 당시 루카스 아츠의 CG 팀은 현재 픽사의 전신이다.

[▲ Amiga Juggler © 1986 by Eric Graham]

1986년에는 8bit 컴퓨터인 아미가로 만든 초창기 낮은 사양 레이 트레이싱 영상이 등장한다. 영상을 보면 캐릭터가 전부 '구'로 이뤄져 있는 사실을 발견할 수 있다. 폴리곤으로 구를 렌더링하려면 엄청 많은 폴리곤을 쪼개야 해서 부하가 걸리는 데 반해 레이트레이싱은 구를 제일 구현하기 쉽기 때문이다.

▲ 반직선(ray)와 구(Sphere)의 백터 방정식

▲ 반직선(ray)과 구(Sphere)의 교점 구하는 공식

레이 트레이싱은 빛(레이)이 일직선으로 진행하다 구에 부딪혀 굴절하는 지점을 찾아야 한다. 즉 반직선과 구의 교점을 찾아야 한다는 말이다. 기하학적으로 해가 없으면 만나지 않는 것이고, 해가 중근이면 한 지점에서 빛과 구가 만난다는 뜻이다. 해가 두개인 경우가 구와 부딪친 것으로 판단할 수 있다. 이 교점으로부터 여러 가지 활동을 수행할 수 있다.

레이 트레이싱은 방정식과 해석 기하학에 대응한다. 즉 레이 트레이싱을 잘하려면 수학적인 부분을 무겁게 다뤄야 한다는 말이다. 허들이 생긴 것 같지만, 흥미롭게도 수학적일수록 코드는 간단해진다.

▲반직선(ray)과 평면(Plane)의 교차점

▲ 반직선(ray)과 평면(Plane)의 교차 조건

빛이 부딪힌 점이 빛을 받아 얼마나 밝을지 어두울지를 판단하는 것은 퐁 반사 모델로 판단한다. 퐁 반사 모델은 베트남 태생의 퐁 박사(Bùi Tường Phong)가 발명한 모델로 현재의 모든 CG의 기초 라이팅의 기반이 되고 있다. 이 모델은 반사광의 강도를 구할 수 있게 해준다.

반사광의 강도를 구하는 공식
Ambient = ka la
Diffuse = kd ld(L • N) = kd ldcosβ
Specular = ks ls(R • V)n = ks ls(cosα)n

엠비언트(Ambient)는 주변의 반사광선, 디퓨즈(Diffuse)표면의 울퉁불퉁함, 스펙큘러(Specular)는 하이라이팅을 뜻한다. 이 모델을 적용하면 플라스틱같이 CG느낌이 많이 나기 때문에 요즘은 PBR 같은 방법을 많이 사용한다.

▲ Phong Reflection Model Formula


Ray Tracer Code
void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
vec2 uv = fragCoord.xy / iResolution.xy;
float aspectRatio = iResolution.x / iResolution.y;
uv *= 2.0; uv -= vec2(1.0, 1.0);
uv.x *= aspectRatio;

vec3 sphere = vec3(0.0, 0.0 + sin(iGlobalTime * 4.0), 5.0);
float R = 1.0;
vec3 ray = vec3(uv, 2.0);

vec3 light = vec3(0.0 + 2.0 * sin(iGlobalTime * 4.0), 1.0, 5.0 + 2.0 * cos(iGlobalTime * 4.0));

float A = dot(ray, ray);
float B = -2.0 * dot(ray, sphere);
float C = dot(sphere, sphere) - R*R;
float Det = B*B - 4.0*A*C;
vec3 color;

if (Det > 0.0) {
color = vec3(1.0, 0.0, 0.0);
float t = (-B - sqrt(Det)) / (2.0 * A);

vec3 pos = t * ray;

vec3 N = normalize(pos - sphere);
vec3 L = normalize(light - pos);
float diffuse = max(dot(N, L), 0.0);

vec3 Ref = reflect(-L,N);
vec3 V = -normalize(pos);
float specular = pow(max(dot(V, Ref),0.0), 32.0);

color *= diffuse;
color += specular;
}
else{
color = vec3(0.5, 0.5, 1.0);
}

fragColor = vec4(color,1.0);

실제 레이 트레이싱 코드의 길이는 이 정도 밖에 되지 않는다. 만약 이를 폴리곤으로 구현하려고 했다면 거대한 라이브러리를 만들어야 할 정도로 방대한 양인데도 말이다. 수학적인 대신에 코드는 매우 간결하다.


⊙ 레이 마칭 알고리즘

레이 마칭 알고리즘은 앞서 언급했듯 3차원 공간에 프랙털을 묘사하기 위해 고안됐다. 직진하는 빛이 항상 장애물과 접점이 생긴다는 보장은 없다. 그래서 완전히 자유롭게 움직이는 임의의 물체에 빛이 부딪히는 걸 찾는 방법이 필요해졌고 그 필요성에 의해 나온 것이 레이 마칭 알고리즘이다.

레이 마칭 알고리즘은 수식이 없어도 교점을 찾을 수 있다. 처음에는 일직선으로 움직이면서 교점을 계속 탐색하는 방식이었는데 이는 시간이 너무 오래 걸린다는 단점이 있었다. 어두운 방 안에서 조금씩 이동하는 것과 같았다.

이 방법을 획기적으로 보완한 것이 거리함수다. 일정 간격으로 움직이는 것이 아니라 가장 가까운 장애물과의 거리를 구한 다음 다음 장애물까지의 거리로 이동하기 때문에 교점을 빠르게 찾을 수 있다.

[▲ 데모씬을 충격과 공포로 몰아넣은 iq의 4k, elevated]

▲Elevated (ShaderToy version)

폴리곤으로 복잡한 물체를 표현하려면 대단히 많은 폴리곤이 필요하다. 하지만 레이 마칭 알고리즘은 모든 것을 픽셀 단위로 추적해서 폴리곤 데이터 자체가 필요 없다. 단 하나의 버텍스안에 픽셀을 채워 넣는 것이다. 그러므로 4K (4,096바이트)로 위와 같은 영상을 만들 수 있다. 음악마저 수학식을 이용해 넣었다.

▲ 거리함수를 이용한 스피어 트레이싱(Sphere Tracing)

▲ 부호있는 거리 함수 Signed Distance Function.

▲ 몇 줄 안 되는 코드로 어떤 모양이든 모두 그릴 수 있다.

▲ (x^2 + y^2 -1)^3 - x^2 * y^3 = 0 공식 만으로 만든 하트

구와 직사각형과 같이 간단한 물체 말고도 복잡한 물체도 구현할 수 있다. 이를 CSG(Constructed Solid Geometry) 연산이라고 한다. 일종의 집합 연산으로 합집합, 교집합, 차집합에 대응하는 논리 연산으로 복잡한 도형을 손쉽게 만들 수 있다. 통상적으로 복잡한 모양을 렌더링하려면 대단히 많은 시간이 걸리지만, 레이 마칭 알고리즘을 사용하면 시간을 획기적으로 단축할 수 있다.

심지어는 앰비언트 오큘루전(Ambient occlusion)도 간단하게 적용할 수 있다. 일종의 눈속임인데, 반사된 빛이 수직 방향으로 튕기며 자신의 주위에 있는 장애물을 체크한다. 장애물이 없으면 개방된 곳이라고 판단하여 오목한지 아닌지 쉽게 구분할 수 있다.