책을 보며 개인적으로 공부하는 내용이므로 틀린 부분이 있을 수 있습니다.
자료 및 내용 출처
<게임 프로그래밍을 위한 3차원 그래픽스>
모델링(Modeling)
그래픽스 관점에서 3차원 게임의 제작 단계는 보통 다음과 같은 파이프라인(pipeline) 과정을 가진다. 이는 각 단계의 결과가 다음 단계의 입력으로 사용되기 때문이다.
게임 제작의 첫 번째인 모델링 단계에서는 게임 환경을 구성하는데 필요한 각 사물, 물체들을 만들게 된다. 예를 들면 나무, 지형맵, 사람 등이 존재한다.
폴리곤 메쉬(polygon mesh)
이러한 객체는 여러 기법으로 구성될 수 있지만, 게임과 같은 실시간으로 처리되는 물체들은 거의 대부분 폴리곤(polygon) 모델링 기법을 사용한다.(GPU가 폴리곤 메쉬 처리에 최적화되어 있기 때문) 폴리곤의 원래 뜻은 다각형이지만 컴퓨터 그래픽에선 주로 삼각형을 의미한다. 그리고 다음과 같이 여러개의 폴리곤으로 구성된 하나의 객체를 폴리곤 메쉬라고 한다. 그림에서 볼 수 있듯이 더 많은 폴리곤을 사용할 수록 물체를 더욱 정교하게 표현할 수 있다. 이러한 폴리곤 메쉬는 정확한 곡면이 아닌 곡면에 근사한 표현이라는 것을 알아두자.
텍스쳐(Texture)
위와 같은 폴리곤 메쉬(뼈대)의 표면에 그림을 입히면 아래와 같이 우리가 게임에서 보는 실제 물체가 된다. 이렇게 폴리곤 메쉬에 입혀지는 그림을 텍스쳐(texture)라고 한다. 아래 그림의 좌측부터 순서대로 폴리곤 메쉬
, 이미지 텍스쳐
, 텍스쳐가 입혀진 폴리곤 메쉬
이다. 텍스쳐가 입혀지는 과정은 런타임에 발생한다.
리깅(Rigging)
폴리곤 메쉬는 움직이지 않는 물체일 수 있지만 위와 같이 움직일 수 있어야 하는 폴리곤 메쉬일 수 있다. 예를 들어 위와 같이 사람일 경우 이동할 때 다리가 움직일 것이다. 그러면 다리에 해당하는 폴리곤들은 적절하게 따라서 움직여야 한다. 이러한 움직임(애니메이션)을 위해 폴리곤 메쉬 내에 골격을 삽입한다. 그리고 골격이 움직일 때마다 그에 해당하는 폴리곤들이 따라서 움직이도록 한다. 이러한 작업을 리깅이라 하며 주로 애니메이터 또는 그래픽 아티스트가 담당한다. 다음은 이해를 돕기위한 사진이다.
애니메이션(Animation)
컴퓨터 게임 또는 애니메이션은 이러한 작업을 거친 폴리곤 메쉬의 움직임을 연속으로 스크린 상에 출력함으로써 우리에게 보여진다. 수많은 움직임 중 한 부분을 프레임(Frame)이라 하며 초당 프레임 수를 fps(frame per second)라 한다. 초당 프레임 수가 많다면 초당 그려지는 그림이 많다는 뜻이고 이는 더욱 자연스러운 모션이 된다. 초당 60개의 프레임을 그린다면 이는 60fps를 의미한다. 애니메이션은 실행 전 미리 처리되는 것(오프라인)이 있고, 런타임에 실행되는 것이 있다.
렌더링(Rendering)
위와 같은 과정을 거쳐 생성된 애니메이션에 한 가지 더 필요한 것이 있다. 생성된 애니메이션은 다양한 물체들과 상호작용을 해야 한다. 이를 위해 애니메이션의 각 프레임마다 물체의 위치와 방향을 계산해야 한다. 즉 어떤 물체와의 충돌 또는 외부적인 힘에 의한 역학(dynamic)을 처리하여 물체들의 위치와 방향을 갱신하여야 한다. 조명이나 시점 역시 매 프레임마다 변경될 수 있다. 이러한 것들이 모두 처리되어 최종적으로 우리가 보는 2D 화면상에 그려져야 한다. 이처럼 3D 물체(그림)를 2D 화면상에 나타내는 것을 렌더링(Rendering)이라 한다. (오프라인)애니메이션 단계까지는 그래픽 아티스트에 의해 미리 처리되고, 런타임 애니메이션과 렌더링은 게임 프로그램에 의해 실행된다.
그래픽 라이브러리
게임 프로그램은 주로 그래픽 라이브러리를 통해 구현된다. DirectX의 Direct3D
와 OpenGL
이 대표적인 예이다. 이러한 라이브러리에 존재하는 함수들은 GPU(Graphic Processing Unit) 내에 하드웨어적으로 구현된 기능(함수)들을 사용하기 위한 인터페이스로서 사용된다. 이는 GPU를 직접 제어할 수 없는 우리에게 간접적으로 제어할 수 있도록 제공되는 함수 모음이다. GPU는 그래픽 카드에 내장된 프로세서로 CPU가 하나의 작업을 처리하는데 특화되어 있다면, GPU는 여러 작업을 동시에(병렬) 처리하는데 특화되어 있다.
OpenGL에선 삼각형 이상(사각형, 오각형 등)의 폴리곤을 처리할 수 있다. 하지만 Direct3D에선 삼각형만을 폴리곤으로 취급한다. 위에서 살펴 보았듯이 많은 폴리곤이 사용될 수록 더욱 정교하게 표현이 가능하다. 즉 고해상도로 표현이 가능하다. 하지만 그만큼 많은 폴리곤을 그려야 하므로 효율적인 면에선 떨어진다. 따라서 품질과 효율성을 적절히 타협하여야 한다.
폴리곤 메쉬를 표현하는 방법
3ds Max, Maya와 같은 프로그램을 통해 그래픽 아티스트가 만든 폴리곤 메쉬를 파일로서 저장하면 게임의 입력으로 사용할 수 있다. 이러한 폴리곤 메쉬를 컴퓨터 프로그램에선 정점(vertex)의 집합으로 표현한다. 예를 들어 폴리곤이 1개면 정점(꼭지점)이 3개이므로 3개의 점을 저장함으로써 폴리곤을 표현한다. 이처럼 정점을 저장하는 공간을 Direct3D에선 정점 버퍼
라고 한다. 다음은 폴리곤 3개로 구성된 폴리곤 메쉬와 그에 대응하는 정점 버퍼이다.
그런데 위 정점 버퍼엔 비효율적인 부분이 존재한다. 삼각형이 3개이기 때문에 총 9개의 정점이 존재하는 것은 맞지만 삼각형이 서로 접해있기 때문에 중복되는 정점이 존재한다. 이렇게 중복이 되더라도 하나의 정점 버퍼로 각 삼각형을 표현하는 방법을 삼각형 리스트(triangle list)
라고 한다. 삼각형 리스트는 간단하게 연속으로 3개의 정점을 읽어 하나의 삼각형을 구성할 수 있다는 장점이 있지만 중복되는 정점들에 의해 메모리가 낭비된다는 단점이 있다.
이러한 메모리 낭비를 막기 위해 2가지의 버퍼를 사용하는 방법이 있다. 바로 다음과 같이 정점 버퍼와 인덱스 버퍼를 이용하여 중복되는 정점 저장을 막는다. 이러한 방식을 인덱스 삼각형 리스트(indexed triangle list)
라고 한다. 우선 정점 버퍼에 unique(유일)한 정점을 모두 저장한다. 그리고 인덱스 버퍼엔 정점 버퍼의 인덱스를 이용하여 삼각형의 세 정점을 연속으로 저장한다. 버퍼가 2개라 오히려 더 많은 메모리를 사용하는 것이 아니냐고 생각할 수 있지만, 정점은 부동 소수점을 이용하고 실제론 2차원 좌표가 아닌 3차원 좌표를 저장하게 된다. 그에 비해 인덱스 버퍼의 각 요소는 정수 값이기 때문에 실제로 더 적은 메모리 공간을 사용하는 것이 맞다. 그리고 정점 버퍼엔 실제로 정점만 저장되는 것은 아니다. 정점 외에도 vertex normal(정점의 법선 벡터), 텍스처 좌표 등 다양한 데이터가 저장된다.
또 다른 방법으로 삼각형 스트립(triangle strip)이 있다. 삼각형 스트립은 다음과 같이 정점 버퍼 하나만 이용한다. 인덱스 버퍼를 사용하지 않는 삼각형 리스트와 다른 점은 중복되는 정점을 저장하지 않는다는 점이다. 우선 t1 삼각형을 렌더링하기 위해 첫 세 정점을 처리 후 캐시에 저장한다. t2를 처리하기 위해 첫 두 정점은 캐시에서 읽고 마지막 정점만 정점 버퍼에서 가져와 처리한다. 이를 위해선 정점을 저장하는 순서가 중요하다. 인덱스 삼각형 리스트와 삼각형 스트립이 가장 많이 사용된다.
GPU의 삼각형 메쉬 렌더링
이렇게 저장된 정점들이 구성하는 폴리곤(삼각형) 메쉬는 GPU에 의해 화면에 렌더링된다. 우선 GPU가 폴리곤 메쉬를 렌더링하는 과정을 간단히 살펴보면 다음과 같다. 우선 정점 버퍼에 저장된 정점들이 GPU의 정점 처리
단계에 의해 캐시에 저장된다. 캐시는 용량이 적기 때문에 모든 정점을 담진 못한다. 이후 삼각형을 처리하기 위해 인덱스 버퍼를 이용하여 세 정점씩 확인 후 처리한다. 여기서 각 정점이 캐시에 저장된 정점인지 아닌지 확인한다. 만약 캐시에 저장된 정점이 아니라면(캐시 미스) 인덱스 버퍼를 통해 정점 버퍼에서 직접 정점을 가져와서 처리한다.
이처럼 캐시에서 정점을 찾지 못하면, 즉 캐시 미스가 발생하면 정점을 새롭게 처리하게 된다. 캐시 미스의 횟수가 정점 처리 횟수와 동일하기 때문에 캐시 미스 횟수는 종종 렌더링 성능 측정을 위해 사용된다.
캐시 미스를 줄이기 위해선 삼각형을 참조하는 규칙이 중요할 수 있다. 예를 들어 아래 폴리곤 메쉬에서 붙어있는 삼각형 순서로 처리한다고 생각해보자. 1번 삼각형이 처음에 새롭게 처리되어 세 정점이 캐시에 저장된다. 2번 삼각형은 하나의 정점을 제외하면 1번 삼각형과 동일하므로 1개의 정점만 새롭게 처리하면 된다. 3번도 2번 삼각형의 두 정점과 동일하므로 1개의 정점만 새롭게 처리한다.
이와 달리 랜덤으로 1-10-2 과 같이 불규칙하게 참조한다고 생각해보자. 1번 삼각형은 새롭게 처리되고 세 정점은 캐시에 저장된다. 10번 삼각형 또한 모두 새롭게 처리되고 캐시에 저장한다. 3번 삼각형은 1번 삼각형의 두 정점과 동일하므로 캐시에서 꺼낼 수 있다. 하지만 캐시의 크기가 작아 10번 삼각형의 세 정점을 캐시에 저장하면서 그 전의 정점들이 삭제되었다고 하면 세 정점 모두 캐시 미스가 발생한다. 이처럼 삼각형을 처리하는 순서는 성능에 큰 영향을 끼친다.
Normal vector (법선 벡터)
법선의 정의는 주어진 객체에 수직인 객체를 의미한다. 즉 법선 벡터는 주어진 벡터에 수직인 벡터를 의미한다. 법선 벡터는 외적(cross product)를 이용하여 구할 수 있다. 외적은 벡터곱으로도 불리며 벡터와 벡터의 곱을 의미한다. 곱은 보통 X로 표시한다. (i.e. v1 X v2)
조명(lighting)
단순한 그림이 아닌 사실적인 그림을 렌더링 하기 위해선 조명(lighting)이라는 것을 알아야 한다. 우리가 실제로 물체를 볼 때 우리는 광원에서 나와 물체에 반사된 빛을 본다. 즉 태양, 전등과 같은 광원에서 나온 빛이 물체의 표면에 닿아 여러 방향으로 반사되는데, 이들 중 우리 눈에 들어오는 빛을 통해 우리는 사물을 인지한다. 컴퓨터 그래픽에서 이러한 사실적인 것을 표현하기 위해 조명을 사용한다. 조명에선 물체의 표면에 대한 법선이 필요하다. 따라서 조명을 처리하려면 메쉬의 표면에 대한 법선 벡터를 필요로 한다.
위 폴리곤 메쉬에서 삼각형 <p1,p2,p3>
에 대한 법선 벡터를 구하기 위해서 p1p2(v1)
, p1p3(v2)
의 외적을 구한다. v1,v2가 이루는 모든 면이 삼각형 <p1,p2,p3>
를 포함하기 때문에 해당 면에 수직인 벡터는 모두 동일하다. 외적은 다음과 같이 표현하고 그 결과는 스칼라가 아닌 벡터이다. 다만 컴퓨터 그래픽에서 법선 벡터는 단위 벡터(크기가 1인 벡터)로 표현하기 때문에 그 크기로 나누어준다.
외적 공식과 오른손 법칙은 서로 연관되어 있다. 우선 첫 번째 벡터인 v1 벡터 방향으로 오른손을 펼친다. 그리고 두 번째 벡터인 v2 방향으로 손을 감싼다. 그리고 엄지 손가락을 펴면 그 방향이 법선 벡터의 방향이다. 손바닥이 두 번째 벡터가 있는 곳을 향해야 하기 때문에 v1과 v2의 순서가 바뀐다면 법선 벡터의 방향은 정반대가 된다. 예를 들어 v2 X v1
은 아래 그림과 같다.
위 법선 벡터의 방향은 물체의 내부를 향한다. 그러나 컴퓨터 그래픽에서 법선 벡터의 방향은 물체의 바깥쪽을 향하는 것이 관례이다. 때문에 삼각형을 표현할 때 정점의 순서는 중요하다. 삼각형 (1)<p1,p2,p3>
가 아닌 삼각형 (2)<p1,p3,p2>
라면 p1p3
가 v1이 되고 p1p2
가 v2가 된다. 이 경우 오른손 법칙에 따르면 법선벡터의 방향은 물체 안쪽이 된다. (위 그림과 동일)
삼각형 (1)의 순서는 반시계 방향이며, (2)의 순서는 시계 방향으로 정렬되었다. 결론적으로 오른손 법칙을 따를 때 반시계 뱡향으로 정점을 정렬하면 물체의 바깥을 향하는 법선 벡터를 얻고, 시계 방향으로 정점을 정렬하면 물체의 안쪽을 향하는 법선 벡터를 얻는다. 만약 왼손 법칙을 따른다면 시계 방향일 때 물체의 바깥 방향을 향한다.
정점 노멀(vertex normal)
지금까지는 면(폴리곤)에 대한 노멀을 살펴보았는데, 실제 정점 버퍼에 저장되는 노멀은 정점 노멀이다. 아래 그림처럼 왼쪽의 부드러운 표면을 나타내기 위해 오른쪽과 같은 폴리곤 메쉬를 만들었다고 가정하자.
왼쪽은 완벽한 원형이지만 폴리곤 메쉬는 실제로 그렇지 않다. 확대해보면 그림과 같이 각진 부분이 존재한다. 즉 최대한 실제 모델에 가깝게 만들고자 했으나 각이 존재한다. 때문에 면에 대한 법선 벡터를 사용하면 실제 모델과 달라지는 부분이 나온다. 즉 퀄리티가 떨어지는 원인이 된다.
다음 그림은 표면 버퍼와 정점 버퍼간의 차이를 나타낸 그림이다.
위 그림에서 녹색 선이 실제 모델이고 하늘색 선은 폴리곤 메쉬이다. 4개의 녹색 점들은 폴리곤 메쉬의 정점을 의미한다. surface normal은 지금까지 봐왔던 면에 대한 법선 벡터를 의미한다. surface normal의 정점은 실제로 부드럽게 휘어지는 부분이다. 하지만 그림상엔 휘어짐을 고려하지 않고 면(surface)에 대한 법선 벡터를 그리고 있다. 반대로 vertex normal는 폴리곤 메쉬의 면에 대한 법선 벡터가 아닌 실제 모델에 대한 법선 벡터를 그리고 있다.
위 surface normal을 vertex normal로 바꿔보면 다음과 같다.
이처럼 정점 노멀은 정점마다 각 접선에 수직인 법선 벡터를 얻어야 한다. 하지만 실제 모델이 항상 주어지는 것은 아니기 때문에 실제 모델에 대한 법선 벡터를 만든다는 것은 이상적인 방법이다. 따라서 최대한 근사치를 만드는 것이 현실적인 계산 방법이다.
따라서 정점 노멀은 보통 다음과 같이 해당 정점을 공유하는 삼각형들의 노멀의 평균을 계산하여 사용한다.
이러한 정점 노멀은 보통 폴리곤 메쉬를 모델링할 때 모두 작업되어 게임 프로그램으로 넘어온다. 따라서 프로그래머가 크게 신경 쓸 부분은 아니지만 정점 노멀 자체의 개념은 중요하니 알아두는 것이 좋다.
폴리곤 메쉬 내보내기(export) 및 불러오기(import)
3ds max 또는 maya와 같은 프로그램에서 만들어진 폴리곤 메쉬와 정점의 정보들은 스크립트나 플러그인을 통해 게임 프로그램으로 전달하게 된다. 예를 들어 3ds max에서 아래의 스크립트를 통해 폴리곤 메쉬에 대한 정보를 export하면 오른쪽과 같이 정점에 대한 정보들이 담긴 file이 생성된다.
위 파일에 대한 정보를 살펴보면, 우선 정점에 대한 정보 530 line이 차례대로 나열되어 있고, 1024개의 face(면, 폴리곤)으로 구성되어 있다. 정점의 각 라인은 총 6개의 floating point 값으로 정의되어 있는데, 각각 정점 및 정점 노멀에 대한 x,y,z 좌표를 의미한다. 1024개의 폴리곤을 나타내는 각 라인은 정점에 대한 인덱스로 구성되어 있다. 즉 첫 번째 폴리곤은 0번, 5번, 6번 정점으로 구성되어 있다는 뜻이다.
위와 같은 결과 파일을 이제 게임 프로그램에서 import 하게 되면 정점 버퍼와 인덱스 버퍼에 각 정보가 저장이 되고, 그래픽스 API들을 통해 GPU로 옮기면 최종적으로 GPU가 폴리곤 메쉬를 렌더링할 수 있게 된다.
'게임 공부 > Computer graphics' 카테고리의 다른 글
4 - 프래그먼트 처리(Fragment processing) (0) | 2020.08.10 |
---|---|
3 - 래스터화(Rasterization) (5) | 2020.08.07 |
빠른 렌더링을 위한 오브젝트 제외 기술(유영천님 발표) (0) | 2020.08.02 |
2 - 정점 처리(Vertex processing) (1) | 2020.07.24 |
게임 개발을 위한 선형대수학 기본 정리 (1) | 2020.07.12 |
댓글