이 글은 조만간 출판될 Game Programming Gems 2 한국어판(류광 역, 정보문화사)의 일부입니다. 이 글에 대한 모든 권리는 정보문화사가 가지고 있으며, 사전 허락 없이는 웹 사이트 게시를 비롯한 어떠한 형태의 재배포도 금지되어 있습니다.

이 글은 최종 교정 전의 상태이므로 오타나 오역이 있을 수 있습니다. 또한 웹 페이지의 한계 상, 실제로 종이에 인쇄된 형태와는 다를 수 있습니다.

Game Programming Gem 2 한국어판에 대한 정보는 GPG 스터디(http://www.gpgstudy.com/)에서 얻을 수 있습니다.

5.1 카툰 렌더링: 실시간 외곽선 변 검출 및 렌더링

Carl S. Marshall, Intel Architecture Labs
carl.s.marshall(at)intel.com

외곽선 변(Silhouette edge) 검출 및 렌더링은 3D 카툰 렌더링의 독특한 느낌을 표현하고자 할 때 필수적인 요소이다. 외곽선 변 검출은 모델의 윤곽을 구성하는 중요한 변(edge, 邊)들을 찾는 작업이다. 많은 카툰 애니메이터들은 모델 테두리를 따라 검은 외곽선을 그려서 외곽선을 표현한다. 이 글에서는 카툰 렌더링의 외곽선 변 검출을 위한 변 기반 검출, 프로그래밍가능 정점 셰이더 기법, 기타 고급 텍스쳐링 기법을 이야기하겠다. 각 기법의 장점과 단점도 지적하겠다.

잉크선

선화(inking, 線畵.먹선 그리기)나 채색(painting) 같은 용어들은 전통적인 셀 애니메이션(cel animation) 제작 과정에 빌려 온 것이다. 선화란 장면 안의 캐릭터나 물체의 외곽선을 짙은 색의 잉크로 그리는 것을 말하며, 채색은 말 그대로 그 외곽선 내부를 색으로 채우는 것을 말한다. 이 글은 카툰 렌더링의 선화에 중점을 둔다. 채색에 대해서는 Adam Lake의 글 "텍스쳐 매핑과 프로그래밍 가능한 정점 셰이더를 이용한 카툰 렌더링"에서 이야기한다. 선화와 채색 기능을 합치면 하나의 완전한 카툰 렌더링 엔진이 된다. 그림 5.1.1은 3D 오리 모델의 선화와 채색 과정을 보여주는 것이다. 선화 과정은 크게 두 부분으로 나뉜다. 첫 번째는 외곽선 변들을 검출하는 것이고, 두 번째는 그들을 이용해서 외곽선을 그리는 것이다. 이 글은 접힌 메시와 접히지 않은 메시 모두에 대해 실시간적으로 선화 과정을 수행하는 몇 가지 기법들을 이야기한다.

그림 5.1.1 왼쪽: 구로 셰이딩을 적용한 오리. 가운데: 먹선을 그리고 면들을 배경색과 동일한 색으로 칠한 것. 오른쪽: 먹선과 함께 단색 셰이딩으로 채색한 오리

중요한 변들

외곽선 변 검출(silhouette edge detection, SED)은 선화 과정의 가장 중요한 부분이다. 여기서 말하는 외곽선에는 모델의 바깥 테두리를 규정하는 실제 외곽선 변들뿐만 아니라, 모델 안의 특정한 부분들을 규정하는 변들과 모델 안쪽에서의 주름(접혀진 부분)을 나타내는 변들도 포함한다. 이들을 각각 경계 변(boundary edge)과 주름각 변(crease angle edge)이라고 부른다. 그림 5.1.2에서 보듯이, 모델의 외곽 변들뿐만 아니라 모델 내부의 선들도 외곽선에 포함된다(역주: 그림 5.1.2에서 T 셔츠나 그 안의 00, 눈과 부리 일부분이 경계 변이다. 이들은 모두 다른 부분과는 재질이나 텍스쳐, 색상이 다르다. 그리고 겨드랑이의 접힌 부분이나 배와 다리 사이의 선들이 주름각 변이다. 두 종류 모두 모델의 테두리가 아니라 모델 내부에서 생기는 먹선들이다). 외곽선 변 검출의 한 가지 매우 중요한 특징은, 외곽선 변들이 시점에 의존적이라는 점이다. 이는 카메라나 모델이 움직일 때마다 다시 외곽선 변들을 검출해야 한다는 뜻이 된다. 한 변을 공유하는 두 면들 사이의 각도(이를 상반각(dihedral angle, 上反角). 또는 2면각이라 한다)가 특정한 각도 이내일 때 그 변이 바로 주름각 변이다. 그 특정한 각도는 임의로 정할 수 있다. 경계 변들은 메시 안의 불연속성이 일어나는 변들이다. 메시 안의 불연속성(discontinuity, 不連續性)은 한 변이 동일한 정점들을 담고 있으나, 정점들이 서로 다른 텍스쳐 좌표나 재질, 법선을 가지는 경우 일어난다.

그림 5.1.2 왼쪽) 카메라를 향하고 있는 첫 번째 계단으로부터 검출한 외곽선들. 오른쪽) 카메라의 시점에서 본 층계의 외곽선들

외곽선 변 검출 기법

하나의 표준적인 외곽선 변 검출 기법이 딱히 정해지지 않은 이유는, 3D 렌더링 API마다, 그리고 그래픽 카드마다 렌더링 성능이나 제공하는 기능들이 조금씩 다르기 때문이다. 이 글은 여러 가지 상황에서의 여러 가지 선화 기법들에 대한 실험을 바탕으로 한 것이다. 텍스쳐 매핑 기능이나 선분 렌더링, 다각형 오프셋 같은 기능들은 하드웨어나 API마다 상당한 차이가 있다.

변 기반 선화

변 기반(edge-based)라는 말은 모델 다각형들의 변들을 분석해서 중요한 변들을 검출한다는 뜻이다. 이 기법은 모델의 경계 변들과 주름진 변들을 모두 정확하게 검출한다. 또 다른 변 기반 선화 기법으로는 면의 곡률(curvature, 曲率)을 통해서 경계 변과 주름진 변을 추출하는 방법이 있다. 변 기반 기법은 전처리 단계와 실행 시점 단계로 나뉜다.

전처리

전처리 과정에서는 우선 모델의 변들을 중복되지 않게 하나의 목록으로 모은다. 모델의 각 면의 세 변들을 각각 검사해서 해시 테이블에 넣는데, 이 때 이미 저장되어 있는 변이면 중복해서 저장하지 않는다. 일단 해시 테이블이 완성되면, 그것을 참조가 좀 더 빠른 선형 배열에 넣는다. 그것이 중복되지 않은 변들의 목록이다. 목록의 한 항목(하나의 변)은 두 개의 정점 색인들과 두 개의 면 색인들, 그리고 하나의 플래그로 구성된다. 플래그 항목은 그 변이 외곽선 변인지, 경계 변인지, 주름각 변인지, 또는 중요하지 않은 변인지를 가리킨다.

외곽선 변들을 정확하게 검출하기 위해서는 면의 법선들을 계산해야 한다. 모델이 정적이라면(애니메이션이 없다면) 전처리 단계에서 법선들을 계산해 둘 수 있으며, 또한 주름각들도 계산, 검출해 둘 수 있다. 비점진적(non-progressive) 메시의 경우 경계 변들은 모델 생성 시점 또는 로드 시점에서 처리될 수 있다. 움직이는 모델의 경우 정확한 외곽선 변들을 얻으려면 매 프레임마다 표면 법선들을 다시 계산해야 한다.

실행 시점

실행 시점 단계에서는 모델의 중요한 변들을 검출하고 렌더링한다. V를 시선 벡터, N1과 N2를 판단하고자 하는 변을 공유하는 두 면들의 법선들이라고 하겠다.

실행 시점 선화

  1. 기하 구조가 애니메이션되었다면, 면의 법선들을 다시 계산한다.
  2. 중복되지 않는 변 목록의 각 변에 대해:
    • 변 정점들 중 하나의 위치로부터 시점(카메라)의 위치를 빼서 V를 얻는다.
    • 변을 공유하는 두 면들 중 하나 또는 둘 모두가 불연속성을 가지고 있으면 '경계' 플래그를 설정한다.
    • 애니메이션되는 기하구조의 경우, 인접한 두 변들 사이의 상반각이 주어진 한계를 넘으면 '주름변' 플래그를 설정한다.
    • (N1 . V) x (N2 . V) <= 0 이면 '외곽선 변' 플래그를 설정한다.
  3. 3. 변 목록의 변을 &#55005;어나가면서 플래그가 설정된 변들만 그린다. 외곽 변과 주름진 변, 경계 변들의 목록을 개별적으로 만들고 각 변 목록을 한 번의 API 호출로 그린다면 성능을 조금 더 향상시킬 수 있다. API가 다각형 오프셋팅(polygon offsetting)과 선 굵기 변경을 지원한다면, 그들을 이용해서 각 종류의 변들을 조금씩 다르게 그릴 수도 있을 것이다(시각적인 느낌을 향상시키기 위해).

단계 2가 외곽선 변 검출 알고리즘의 핵심이다. 변을 공유하는 인접한 두 면의 법선들과 시선 벡터의 내적들을 비교하면 매우 정확하게 외곽선을 검출할 수 있다. 속도를 위해 면의 법선 대신 정점 법선들을 사용할 수도 있겠지만, 그러면 여러 정점 및 픽셀 셰이더 기반 기법들처럼 외곽선들을 제대로 검출하지 못하게 된다. 목록 5.1.1에 예제 코드가 나와 있다.

장점

단점

목록 5.1.1 변 기반 외곽선 변 검출


// 외곽선 검출
Edge *pEdge;

// 모든 변들을 점검
for(unsigned int i=0; i < numEdges; i++){
	pEdge = &m_pEdgeList[i];

	     //시선 벡터를 계산
	pEdgeVertexPosition =
	     pMesh->GetVertexPosition(pEdge->GetVertexIndex(0));


	// 변의 정점에서 시점의 위치를 뺀다
	viewVector =  pEyePosition - pEdgeVertexPosition;
	viewVector.normalize();

	// 시선 벡터와 면 법선의 내적들을 구한다.
	uDotProduct1 = DotProduct(viewVector, *pFaceNormalA);
	uDotProduct2 = DotProduct(viewVector, *pFaceNormalB);

	if((uDotProduct1 * uDotProduct2) <= 0.0f){
	     // 이 변은 외곽선 변이다.
	     AddEdgeToRenderEdges(pEdge, uNumRenderedVertices);
	}
}

프로그래밍 가능한 정점 셰이더 기반 선화

프로그래밍 가능한 정점 셰이더(programmable vertex shader)는 최신의 그래픽 카드들에 쓰이는 매우 유연한 그래픽 API이다. 앞에서 시선 벡터와 면 법선의 내적을 구해서 외곽선 변을 검출하기 했다. 프로그래밍 가능한 정점 셰이더를 이용하면 그러한 과정을 그래픽 하드웨어에게 직접 맡길 수 있다.

프로그래밍 가능한 정점 셰이더를 이용한 실시간 선화

  1. 그래픽 API를 통해서 텍스쳐 파이프라인을 설정한다. 이를 위해서는 1 차원 텍스쳐를 만들거나 로딩해야 한다. 2차원 텍스쳐를 사용해도 되나, 첫 번째 행만 쓰일 뿐이다. 이 때 1차원 텍스쳐(또는 2차원 텍스쳐의 첫 줄)의 색상은 외곽선 색상(보통 검정)과 원하는 임의의 색상으로 구성되는데, 그 사이에 중간 단계 색상들을 넣을 수도 있다. 일반적으로 1x32 또는 1x64의 경우 u=0 근처의 처음 몇 픽셀은 검은 색, 그 이후부터는 면의 재질 색상이 되게 하면 깔끔한 결과를 얻을 수 있다
  2. 프로그래밍 가능한 정점 셰이더의 레지스터들을 로드한다. 어떤 것들을 로드해야 하는지는 특정한 응용에 따라, 그리고 어디에서 변환을 수행할 것이냐에 따라 다르다. 일반적으로는 세계-시점 변환 행렬과 투영 행렬을 보내고, 그 다음으로 시점 위치를 보내며, 그들을 통해서 텍스쳐 좌표를 계산한다. 새 텍스쳐 좌표들은 계산에 의해 생성되므로 카드로 보낼 필요가 없다. 그러나, 단 모델에 대해 텍스쳐 좌표들이 생성되어야 함을 그래픽 유닛에게 확실하게 알려줘야 한다.
  3. 정점 위치를 모델 공간에서 동차 절단 공간(homogenous clip space)으로 변환한다.
  4. 각 정점에 대해, 시점 위치로부터 세계 공간에서의 정점 위치를 빼서 시선 벡터를 만든다. 그리고 정점 법선과 시선 벡터의 내적을 구한다(목록 5.1.2). 시선 벡터와 법선 벡터가 동일한 기준계에 있어야 정확한 결과를 얻을 수 있다. 이러한 내적은 그래픽 유닛에서 계산될 수도 있고, CPU에서 계산한 후 정점 셰이더의 상수 레지스터들에 로드할 수도 있다.
  5. 단계 4의 결과를 텍스쳐 좌표 u로 저장한다.

두 패스로 이루어진 또 다른 정점 셰이더 기법도 있다. 첫 번째 패스에서는 정점을 그의 법선쪽으로 돌출시킨다. 그런 다음 깊이 버퍼를 비활성화시키고 원하는 외곽선 색상으로 모델을 렌더링한다. 두 번째 패스에는 깊이 버퍼를 다시 활성화시키고 모델을 원래 상태대로 렌더링한다.

장점

단점

목록 5.1.2 DirectX 8.0의 프로그래밍 가능한 정점 셰이딩 기능을 위한 외곽선 셰이더


;----------------------------------------------------------------
; 응용 프로그램에 의해 지정되는 상수들
;    c8-c11  = 세계->시야 행렬
;    c12-c15 = 시야 행렬
;    c32   = 세계 좌표의 시점 위치
;
; 정점 성분들(정점 DECL에 의해 지정됨)
;    v0    = 위치
;    v3    = 법선
;----------------------------------------------------------------

;----------------------------------------------------------------
; 정점 변환
;----------------------------------------------------------------
;  시야 공간으로 변환(세계 행렬은 단위 행렬)
;  m4x4는 4x4 행렬 곱셈
m4x4 r9, v0, c8
; 투영 공간으로 변환
m4x4 r10, r9, c12
; 결과(위치)를 저장
mov oPos, r10

;----------------------------------------------------------------
; 시선 벡터와 법선 벡터의 내적
;----------------------------------------------------------------
;우선 정점으로부터 시점을 잇는 벡터를 만든다.
;r9의 값은 세계 공간 좌표이다(위의 정점 변환을 참고할 것).
sub r2, c32, r9     ;정점으로부터 시점을 잇는 시선 벡터를 만든다.
; 벡터를 정규화한다.
; dp3은 내적 연산.
mov r3, r2          ;임시 변수
dp3 r2.x, r2, r2         ;r2^2
rsq r2.x, r2.x      ;1/sqrt(r2^2)
mul r3, r2.x, r3    ;(1/sqrt(r2^2))*r3 = r3
dp3 oT0.x, r3, v3   ;시점->표면 벡터와 표면 법선의 내적

고급 텍스쳐 기능을 이용한 선화

[Dietrich00]에 완전히 그래픽 프로세서 상에서 수행되는 하나의 혁신적인 기법이 설명되어 있다. 이 방법은 정점마다 N과 V의 내적을 계산해서 외곽선을 얻는다. 알파 테스트를 이용해서 값들을 하나의 검은 선으로 제한하며, 기본 도형 당 알파 참조 값을 이용해서 선의 굵기를 변경한다. 다음은 이 방법의 기본적인 단계들이다. 좀 더 자세한 내용은 해당 자료를 참고하기 바란다.

기본적인 단계들

  1. 법선 참조를 위한 입방체 맵 텍스쳐를 만든다. 이 맵은 입방체의 여섯 면들을 담은 하나의 텍스쳐이다. 각 면의 텍셀들은 RGB 값을 가지며, 그 값은 원점으로부터 텍셀의 해당 위치(단위 입방체의 면 상의 해당 위치로 정규화된 점)를 잇는 벡터를 의미한다.
  2. 시야 공간의 법선 텍스쳐 좌표들을 색인으로 사용해서 입방체 맵 텍스쳐의 텍셀을 읽고, 그 RGB 값을 면의 법선 벡터 N으로 사용한다.
  3. 시야 공간 위치 텍스쳐 좌표들을 색인으로 사용해서 입방체 맵 텍스쳐의 텍셀을 읽고, 그 RGB 값을 시선 벡터 V로 사용한다.
  4. 알파 테스트 함수를 LESS_EQUAL로 설정한다.
  5. 알파 테스트 기준값을 설정한다. 0이면 얇은 선, 그 보다 클수록 두꺼운 선이 된다.
  6. 단계 2와 3에서 얻은 N과 V의 내적을 구한다.
  7. 알파 테스트를 LESS_EQUAL로 해서 렌더링하면 외곽선이 그려진다. 거기에다 알파 테스트를 GREATER로 해서 렌더링하면 색조가 입혀진 객체가 그려진다.

장점

단점

결론

카툰 렌더링에 쓰이는 선화와 채색 기법은 비실사 렌더링(nonphotorealistic rendering, NPR. 非實寫-)이라는 좀 더 큰 범주에 속하는 기법들이다. NPR 분야에는 카툰 렌더링 이외에도 연필 스케치, 수채화, 흑백 일러스트레이션, 유채화, 기술도면 렌더링 같은 다양한 양식화된 렌더링 기법들이 포함된다. 선화는 NRP의 기본 도구로, NPR 분야에 입문하기 위한 출발점이라 할 수 있다. NPR 기법들을 쌓아 나간다면, 다른 게임들과는 확연하게 구별되는 독특한 느낌을 가진 게임을 만들 수 있을 것이다.

참고자료