서론
이전 포스트의 마지막 문단에서 미분이라는 수학 용어를 사용해서 초중고 내내 소위 말하는 수포자였던… 저 같은 초보 아티스트들이 지레 겁먹고 바로 뒤로가기를 누르는 참사를 막고자 최대한 쉽게 차근차근 개념을 설명하려고 합니다.
먼저 외곽선의 정의에 대해서 생각해봅시다.

여기 사과 사진이 하나 있습니다. 먹음직스러운 붉은 알베도 컬러에 조명의 스펙큘러가 맺힙니다. 사과의 그림자를 자세히 보시면 사과의 바닥 면에서 튕긴 붉은색 빛이 그림자 속에 묻어나고 있습니다. 또한 구 형태기 때문에 사과의 중심에서 바깥으로 갈수록 프레넬이 강해져서 주변을 거울처럼 반사합니다.
사과 이미지의 빛과 구성 요소를 카라렌처럼 분석 해보았습니다. 이 사과 이미지에서 사과의 외곽선이라고 말할 수 있는 부분은 어디일까요?

포토샵으로 마우스로 대충 삐뚤삐뚤하게 제가 외곽선이라 생각하는 부분을 그렸습니다. 제가 그린 외곽선과 원본을 비교 해봅시다.
첫번째는 그림자가 있는 부분과 없는 부분입니다. 명암이 극적으로 대비되는 부분이 외곽선으로 생각했습니다. 사과 내부에서도 꼭지 부분으로 갈수록 빛을 수직으로 받는 면과 그렇지 않은 면의 명도 차이로 외곽선이라고 판단하였습니다.
두번째는 사과와 흰 배경이 맞닿는 부분입니다. 채도의 유무로 외곽선을 판단 하였습니다.
세번째는 사과 표면에 조명이 반사되는 스펙큘러 부분입니다. 하지만 저는 이것을 외곽선으로 판단하지 않았습니다. 사과 표면의 일부라고 생각했기 때문이고 이 사실은 매우 중요합니다.
저희가 코딩으로 작성하려고 하는 것은 실제 현실의 카메라로 촬영한 영상 매체의 정보 처리 기술을 그대로 게임 엔진에서 응용하는 경우입니다.
그래서 우리에게 익숙한 포토샵에서도 당연히 외곽선 검출 방식들이 존재합니다. 포토샵은 외곽선을 어떻게 판단하는지 한 번 직접 확인해봅시다.

(포토샵 강조된 가장자리 효과)
제가 생각한 외곽선의 조건과 거의 일치합니다. 저희가 게임 엔진에서 사용할 방식과는 다르지만 포토샵의 외곽선 효과들도 위와 같이 몇가지 원칙을 따릅니다.

(무채색의 사과 이미지)
이미 여러분들은 눈치채셨을겁니다. 외곽선 검출 방식들의 공통점은 어떤 픽셀하나가 주변픽셀보다 극적인 변화를 가진다면 외곽선일 확률이 높다고 판단 하는 계산인 것입니다. 증명을 위해서 사과 이미지의 채도를 제거 해봤습니다.


(포토샵 포스터 가장자리 효과)
흑백으로 사과 이미지를 수정하여 채도를 제거하면 같은 효과임에도 불구하고 컬러 이미지에서는 외곽선으로 인식하지 않는 부분도 외곽선으로 판단을 내리게 됩니다. 흑백이라 RGB 채널 값이 모두 똑같아졌으니 변화가 커진 것이지요.
저 또한 사과의 스펙큘러는 사과 표면의 일부라고 생각해서 외곽선으로 그리지 않았다고 위에서 말했었습니다. 포토샵 또한 R,G,B 채널의 값으로 외곽선이 아니란 것을 판단한 것입니다.

(가장자리 광선 효과)
컬러 이미지 상태에서 다른 포토샵 효과를 적용 해봤습니다. 이 포토샵 효과는 내부 외곽선 때문에 사과가 쭈글쭈글하게 보일 지경입니다. 채도를 넣고 무조건 정보를 많이 준다고 해서 외곽선이 더욱 정밀 해지는 것은 아니란 것입니다.
위 효과로 알 수 있듯이 이미지가 깨끗 할 수록, 변화의 차이가 뚜렷할 수록, 쓸모 없는 픽셀과 데이터가 제거되어 있을수록 더욱 정밀한 결과를 얻을 수 있습니다. 이것을 노이즈(noise) 혹은 잡음 이라고 합니다.
이미지에 노이즈가 끼면 잘못된 외곽선이 생기고 오차가 커집니다. 다행히도 저희는 게임 엔진 속에서 노이즈가 없는 순수한 ‘버퍼 데이터’를 가지고 검출을 하기 때문에 사실 노이즈에 대해서는 크게 걱정할 필요가 없습니다.
다만 외곽선을 가진 오브젝트들이 카메라 앞에 너무 많이 뿌려진다거나 하면 **복잡도(밀도)**가 올라가서 외곽선이 더러워질 수 있습니다.
그래서 완벽한 외곽선 검출 방식 같은 것은 없으며 비교할 데이터가 많을수록 이미지의 복잡도가 증가 하기 때문에 외곽선의 정밀도가 높아진다고 할 수도 없습니다.
게임 엔진에서 사과를 렌더링 하는 일은 렌더링 파이프라인에 의거하여 사과를 구성하는 모든 요소를 하나 하나씩 따로 따로 만든 상태에서 최종 결과물로 합치는 방식이기 때문에,
(물론 디퍼드 렌더링에만 버퍼가 있지만 포워드에서도 이전 포스트에서 다뤘듯이 C# 스크립트로 명령하여 카메라로 직접 촬영, 버퍼를 찍어낼 수 있습니다)
최종 결과물 이전의 버퍼에 접근하여 노이즈를 분리해내고 원하는 데이터로만 외곽선을 검출해 낼 수 있다는 것이 매우 큰 장점으로 다가오는 순간입니다...
외곽선 검출 이론

그림으로 도식화한다면 외곽선으로 판단할 수 있는 픽셀은 위와 같은 네가지 종류의 경우들로 간추릴 수 있습니다. 세로축은 밝기값, 혹은 채도값이며 가로축은 픽셀의 배열입니다.

흑백 이미지일 경우 위 자료와 같이 밝기 값으로 외곽선을 검출 할 것입니다. 인접한 픽셀들과의 값 비교를 통해 '차이'를 구하는 계산을 하는 것입니다.

이 자료 역시 바로 이해하실 수 있을 겁니다. 인접 픽셀으로의 변화량은 그래프의 **기울기(미분계수)**로 치환 될 수 있습니다. 그래서 포스트에서 **'미분'**이라는 표현을 사용한 것 입니다. 하지만 우리는 코딩으로 어려운 수학공식을 쓸 필요는 전혀 없습니다.

이미지를 구성하는 픽셀의 가로 혹은 세로 딱 한줄만 떼어내서 이어 붙이면 그라데이션이 될겁니다. 따라서 기울기란 그라데이션의 시작 픽셀과 끝 픽셀의 변화량 값입니다. 이제 인접 픽셀과의 변화량을 측정하는 방법을 알아봅시다.

자 여기 세개의 흑백(float) 픽셀이 위와 같이 배치 되어 있습니다. 변화량을 측정하는 방법은 간단합니다. 중간에 있는 흰색 픽셀이 중심에 있으니 메인픽셀이고 왼쪽, 오른쪽에 있는 픽셀을 좌, 우 픽셀이라고 부르겠습니다.
메인픽셀에서 오른쪽에 인접한 우 픽셀만큼 뺄셈을 합니다. 그것이 바로 변화량입니다. 우 픽셀의 값은 0이니까 변화량(기울기)은 1입니다. 왼쪽에 인접한 좌 픽셀도 메인픽셀에서 뺄셈을 합니다. 변화량은 0.5입니다.

이제 좌우 뿐만 아니라 상하, 대각선의 픽셀과도 변화량을 측정 할 수 있습니다. 하지만 이런식의 단순한 미분은 변화량이 조금이라도 있으면 그 부분을 외곽선으로 판단하는 문제가 있습니다.
그래서 저희는 필터(filter) 혹은 **마스크(mask)**라고 부르는 방식으로 미분 할 것입니다.

소벨(Sobel) / 프리윗(Prewitt) / 로버트 에지(Roberts Edge)
위와 같이 대표적으로 세가지 필터를 사용한 1차 미분방식이 존재합니다. 각각의 필터는 노이즈를 잘 걸러내거나, 특정 방향(수직 수평 대각) 외곽선을 정밀하게 캐치해낸다거나, 식이 간단해서 계산이 빠르거나 하는 나름의 장단점을 가지고 있습니다.
필터 라는 것은 변화량을 측정하기 위한 도구로 이해하시면 됩니다. 일정량 이상의 변화가 있다면 외곽선으로 판단을 내리고, 변화량이 작을수록 외곽선의 색이 점점 약해지다가 외곽선이 아닌 수준의 변화량이라면 외곽선이 사라지는 것입니다. //제가 아까 사과의 스펙큘러를 외곽선으로 판단하지 않는 것으로 ‘인간 필터’가 된 것 처럼요 ㅎㅎ
또한 필터의 크기가 꼭 3x3 일 필요도 없습니다. 어디까지나 인접 픽셀의 변화량을 측정하는데 의미가 있으니 상황에 따라 마스크를 확장해서 5x5, 7x7 까지 개조할 수도 있습니다.
하지만 필터를 마음대로 생성할 수는 없습니다. 다음과 같은 조건을 만족해야합니다.
- 필터 크기의 가로, 세로가 같고, 홀수여야한다. (3,5,7,9 ...)
- 중심 지점을 기준으로 상하좌우가 대칭이어야 한다.
- 중심 지점의 값은 항상 0을 포함한 양수이다. (n >= 0)
- 모든 지점의 값들의 합은 0이다. (Sum = 0)
이렇게 각각의 필터를 수직으로 한번, 수평으로 한번 미분하고 측정된 변화량이 최종 픽셀의 색(float)을 결정하게 됩니다.
2차 미분 방식인 라플라시안 필터(Laplacian filter)도 있고 캐니 에지(Canny Edge)라는 가장 연산이 무겁지만 확실하게 에지를 검출하는 방식도 있습니다만, 모두 노이즈를 최대한 감소시키기 위해 연구된 방식이므로 포스트에서는 제외하도록 하겠습니다.
다음 포스트에서는 이전 포스트에서 구한 뎁스, 노말 버퍼 텍스쳐 들과 소벨 필터를 이용한 쉐이더 코드 작성으로 찾아 뵙겠습니다.