Unreal/Shader & VFX

UE4 Cel Shading : 카툰렌더링

CatDarkGame 2019. 6. 1. 17:42

 

Git : https://github.com/CatDarkGame/PostProcess-CelShading

 

Cel Shading을 구현하는 방법은 렌더링 방식, 구현 방식에 따라 여러 가지 있습니다.

이번 포스팅에서는 언리얼4 엔진에서 Deferrd(지연) 렌더링 기반 Post process(후처리) 쉐이더를 통해 Cel Shading을

구현해 보겠습니다.

 

 

Deferrd(지연) 렌더링 특성

Deferrd 렌더링 파이프라인은 기본적으로 Color Map, Depth Map, 등 여러 가지 G-Buffer들을 연산후에 최종적으로 합쳐서

최종 결과물을 렌더링 하는 방식입니다.

그 결과 Post process(후처리) 쉐이더에서 G-Buffer들을 이용해서 다양한 효과를 구현해 낼 수 있습니다.

 

 

Post process Material 세팅

언리얼 에디터상에서 새 머티리얼을 생성한 후 위 사진과 같이 디테일 옵션을 설정합니다.

 

Material Domain은 현재 머티리얼을 Post Process전용 설정하는 것이고,

Before Tonemapping으로 설정하는 것은 해당 쉐이더가 동작하는 순서를 Tonemapping 전으로 설정한다는 뜻입니다.

 

 

SceneTexture노드를 생성하여 위 사진과 같이 노드를 세팅합니다.

SceneTexture는 G-Buffer를 선택하여 가져올 수 있는 노드입니다, 위 사진에 설정한 PostProcessInput0는 SceneColor를 의미하며 해당 머티리얼 이전에 연산된 Buffer데이터입니다. 

 

결과적으로 지금 만든 머티리얼은 단순히 최종결과 Buffer를 그대로 출력하는 Post Process머티리얼입니다. 

 

 

다음 월드상에 포스트 프로세스 볼륨 오브젝트를 아무 곳에 설치합니다. 

 

 

그리고 포스트 프로세스 볼륨 오브젝트의 디테일에서 좌측 사진과 같이 방금 전에 만든 머티리얼을 넣어주고

Infinite Extent(Unbound) 옵션을 체크해줍니다. 

 

 

포스트 프로세스 볼륨은 기본적으로 위 사진과 같이 해당 영역에 카메라가 들어가야만 세팅한 머티리얼이 동작합니다.

Infinite Extent(Unbound) 옵션을 체크하면 해당 영역에 들어가지 않아도 월드 어느 곳에서든 머티리얼이 동작합니다.

 

 

Banded Lighting(단계별 음영)

Cel Shading을 구현하기 위해 첫 번째 단계로 라이팅 음영을 단순화하여 만화처럼 보이게 해보겠습니다.

 

 

- Directional Lighting Buffer 계산하기 - 

일반 쉐이더에서 NDotL연산을 구하는 것 처럼 라이팅 음영 버퍼정보를 시각화한 Lighting Buffer를 연산해야 합니다.

 

Lighting Buffer을 구하기 위해서는 두 가지 버퍼가 필요합니다.

 

-Post process Input : 현재 머티리얼에 블랜딩되기 전까지의 최종 결과 버퍼화면

-Diffuse Color : 라이팅, 후처리가 없는 단순히 난반사 색만 가지고 있는 버퍼화면

 

Desaturation노드는 Color맵을 GrayScale로 바꾸는 기능을 합니다, PostProcessInput0와 DiffuseColor를 GrayScale로 

바꾸고 나누면 아래 사진과 같이 Directional Light Buffer를 만들 수 있습니다.

 

 

 

- Banded Lighting - 

Light Buffer를 통해 0~1사이의 명암값을 if문으로 0.5를 분기로 명암을 분리하여 만화같은 영상을 구현할 수 있습니다.

 

 

- LUT Texture로 Banded Lighting 구현 - 

기존에 if문을 사용해서 임의로 명암을 나눴다면 이번엔 LUT Texture를 기반으로 명암을 다뤄보겠습니다.

 

LUT(Look Up Table)란?

단순히 뜻을 풀이하면 미리 계산된 데이터 배열이라는 뜻으로, 비유하자면 우리가 수학 문제지 안에 문제들을 풀이 하기 위해서 는 해당 문제를 읽고 풀어야 합니다, 하지만 정답지를 가지고 있다면 해당 문제 번호만 정답지에서 찾으면 문제를 풀지 않아도 바로 답을 알수 있죠, 여기서 정답지가 바로 LUT입니다.

 

그래서 위 사진과 같이 Texture를 기반으로 명암을 정하기 때문에 LUT Texture라고 명칭합니다.

 

 

먼저 간단한 LUT Texture를 엔진에 넣고 세팅을 하겠습니다.

 

엔진에서 LUT텍스처를 열어서 디테일창 몇 가지 세팅이 필요합니다.

Linear Color로 변형되지 않아야 하므로 sRGB 옵션을 체크 해제하고, 텍스처의 Tiling이 반복되면 안되니깐 Clamp로

설정합니다.

 

다음 이전에 만든 Banded Lighting부분을 삭제하고 위 노드로 수정하면 됩니다.

 

원리는 간단합니다, Light Buffer에서 얻는 0~1값의 명암을 그대로 LUT Texture의 UV로 사용하여 텍스처의 색상값을

명암으로 사용합니다.

 

 

- Shadow Color -

LUT Texture에서 나온 값중에 어두운 부분을 추출해서 원하는 색을 합칠 수 있는 기능을 만들어 좀더 이쁘게 만들 수

있게 Shadow Color를 설정할 수 있는 노드입니다.

 

 

 

Sky Light Color(Ambient Color)

좀더 퀄리티있는 쉐이더를 만들기 위해서 Sky Light의 색을 받아서 입히는 기능을 만들어 봅시다.

 

Sky Light 색을 가져오기 위해서 PostProcessInput0 버퍼와 BaseColor(for lighting) 버퍼를 사용 했습니다.

BaseColor(for lighting)버퍼는 Diffuse버퍼와 비슷하지만 라이팅정보를 담고 있는 듯 합니다.(명확한 자료 조사 실패)

 

그 결과 이전에 만든 Lighting Buffer와 비슷한 화면이 나오지만 Sky Light색상이 섞인 결과물이 나옵니다.

이제 이 화면에서 정확히 Sky Light색만 추출 해봅시다.

 

위 결과에서 Desaturation으로 흑백으로 만든 맵을 한번 더 나누면 Sky Light색만 남게 됩니다.

 

 

마지막으로 Banded Lighting 결과물과 곱하여 혼합하면 Sky Light가 섞인 결과물이 완성됩니다.

 

 

 

Specular(High Light)

SkyLight와 마찬가지로 더 퀄리티를 높게 만들기 위해 Specular를 추가 했습니다. 

 

이전에 Diffuse Buffer이나 Sky Light Buffer를 추출한 것과 마찬가지로 이번엔 Specular(for lighting)노드를 이용해서 

Specular Buffer를 추출 합니다.

 

다음 곱셈을 통하여 Specular 강도를 조절하고 if노드로 1이상 값을 나눠 그라디언트를 잘라줬습니다.

 

정확히 Specular만 추출된 모습입니다.

 

마지막으로 Specular 색을 조절이 가능하도록 Vector4노드와 Multiply를 하고 Banded Lighting노드와 Add로 합치면 끝.

 

 

 

Exception Unlit Shader

Post process Material로 쉐이더를 작성하는데 SkyBox가 보이지 않습니다, 그외 Unlit쉐이더는 모두 보이지 않습니다.

왜냐하면 현재 라이팅을 받는 Diffuse Buffer만 이용해서 쉐이더를 작성했기 때문입니다.

 

간단하게 Lerp를 이용해서 Diffuse Color 버퍼에 해당하는 픽셀은 이전에 작성한 쉐이더 값을 출력하고 그게 아니면

일반 출력 결과물인 PostProcessInput0 픽셀을 출력하게 하여 해결합니다.

 

 

 

Outline 소개

http://pafuhana1213.hatenablog.com/entry/2014/12/02/121649

Post process(후처리)방식에서 외각선을 검출하는 법은 여러가지 존재한다, 해당 이론들은 영상처리 분야에서 많은 자료를 찾을 수 있습니다.

 

이 포스팅에서는 두 가지 방식의 외각선 검출을 합니다.

 1. Custom Depth Buffer에서 Sobel Operator 외각선 검출 알고리즘 사용

 2. Normal Buffer에서 주변 픽셀 차이 값을 이용한 외각선 검출

 

이 두 가지 방식을 동시에 사용하는 이유는 Sobel Operator는 외부 외각선을 검출에 용의하고 Normal Buffer를 이용

하는 방식은 내부 외각선을 검출하는데 용의하기 때문에 두 방식을 동시에 사용합니다.

 

 

Sobel Operator with Custom Depth Buffer

Sobel Operator 알고리즘의 핵심 원리는 위 3x3크기의 행렬을 Horizontal(수평), Vertical(수직) 방향별로 픽셀 하나를

기준으로 주변 픽셀을 비교하여 외각선인지 아닌지 결정합니다.

 

 

해당 알고리즘을 Custom Depth Buffer에 이용하면 정확히 오브젝트의 외부 외각선을 검출 하는게 가능합니다.

 

Custom Depth Buffer에 오브젝트를 보이게하기 위해서는 오브젝트를 Custom Depth설정을 따로 해야합니다.

 

시작하기 앞서 해당 쉐이더를 제작하기 위해서는 많은 노드들을 사용해야 하므로 깔끔한 정리를 위해서 머티리얼 펑션을 이용했습니다, 머티리얼 펑션은 위 사진과 같이 만들 수 있습니다.

 

-주의-

수 많은 머티리얼 펑션을 사용한 노드들을 설명하면서 개념설명을 해야 하기 때문에 최상단 머티리얼 펑션부터 시작

해서 설명하겠습니다, 포스팅을 읽으시는 분들은 순서대로 읽으셔서 개념 파악 이후 거꾸로 다시 노드들을 보면서 제작

하면 되겠습니다.

(블로그에서 확인하기 힘든 노드는 배포한 프로젝트에서 확인 부탁드립니다.)

 

우선 "MF_SobelFilter"라는 머티리얼 펑션을 만들었습니다.

 

 

- 주변 방향 UV 정보 얻기 -

우선 한 픽셀의 주변 8방향의 UV정보를 얻어야 합니다, 위 노드와 같이 구성하기 위해 "MF_GetNeighbourUVs"머티리얼 펑션을 만들어 봅시다.

 

새 탭에서 이미지를 보면 크게 볼 수 있습니다.

노드가 매우 많아서 복잡해 보이지만 사실 매우 간단하게 입력된 UV좌표 기준으로 주변 8방향 UV 정보 값을 출력하는 머티리얼 펑션입니다.

위 노드에 있는 또 다른 "MF_GetOffsetPixel" 머티리얼 펑션을 만들어 봅시다.

 

이 머티리얼 펑션은 SceneTexture크기(InvSize)와 가져올 UV 정보 방향(Offset Direction)을 입력하면 해당 UV정보를

출력하는 머티리얼 펑션입니다. 

 

 

- 주변 UV정보로 Depth Buffer 픽셀 정보 얻기 -

이전에 얻어온 UV정보들을 위 사진과 같이 9개의 "MF_ExtractDepthMap" 머티리얼 펑션 노드에 입력합니다.

위 작업을 통해 실제 Depth Buffer의 픽셀값을 얻을 수 있습니다.

 

"MF_ExtractDepthMap" 머티리얼 펑션은 간단하게 이전에 계산해서 받아온 UV값을 Custom Depth Buffer에 넣어서 

해당 UV에 맞는 픽셀값을 가져옵니다.

 

마지막으로 추출한 Depth Buffer 픽셀값을 이용해 Sobel Operator 알고리즘을 실행하는 부분입니다.

빨간 네모는 처음에 소개한 Sobel 알고리즘의 행렬 정보입니다, "MF_CombineMap"머티리얼 펑션에서 외각선 여부를 검사하여 결과 값을 출력합니다, Horizontal(수평), Vertical(수직) 픽셀을 각각 검사한 후 결과 값을 합칩니다.

 

Input에서 받은 9개의 픽셀 행렬을 Sobel Filter 행렬값대로 모두 곱한뒤 모두 더한 값이 픽셀 변화량이 나오게 됩니다. 

 

 

 

최종적으로 이전에 제작한 쉐이더에서 Lerp를 이용하여 위 노드와 같이 구성하면 외각선 검출 완성입니다.

 

 

 

 

Normal Base Outline(Inner Outline)

Normal Base Outline은 간단하게 World Normal Buffer를 이용해서 외각선, 특히 내부 외각선을 검출 할 수 있습니다.

 

원리는 사진과 같이 "일반 노말 버퍼 - UV 이동한 노말 버퍼" 연산을 하여 차이 값에 의해 자연스럽게 외각선이 나오게 합니다.

 

이번 기능도 머티리얼 펑션을 만들어 구현합니다, "MF_NormalOutline"을 만들어 봅시다.

 

또 다시 노드 지옥입니다, 하지만 원리가 매우 간단하여 설명이 가능합니다.

 

위 노드는 이전에 설명했던 World Normal - UV조정한 World Normal 계산 부분입니다.

빨간 네모 부분에 UV 조정할 방향 값을 집어 넣는데 상하 좌우로 "-1, 0", "1, 0", "0, -1", "0, 1" 값을 반복적으로 계산합니다. 

 

이후 4방향을 계산한 값들을 모두 Add노드로 더 합니다.

 

마무리 작업으로 값에 오차가 없도록 Abs, Clamp 처리를 해주고 Power노드를 통해 선 굵기를 정하는 등 마무리 작업을 합니다.

 

Sobel Filter와 마찬가지로 Lerp를 이용해 Outline을 적용합니다.

 

 

 

 

Rim Light

Cel Shader를 더욱더 퀄리티 높이기 위해서 Rim Light를 넣어 보겠습니다.

 

위 3개의 버퍼를 전부 합치면 

 

위와 같이 Rim Light형태의 버퍼가 완성됩니다. 이제 노드를 짜봅시다.

 

 

맨 처음 Directional Lighting Buffer를 연산하는 것을 다시 만들고 이번엔 if문을 이용해 2 Banded Lighting을 하면 

첫 번째로 필요한 Banded Lighting Buffer가 완성 됩니다.

 

여기서 추가로

 

정확한 Rim Light Buffer를 만들기 위해서는 오브젝트의 Light Buffer정보가 필요하니 Custom Depth Buffer를 이용하여 외부 오브젝트 요소를 제거 합니다.

 

 

 

DepLine Buffer는 이전에 Sobel Filter 머티리얼 함수를 그대로 사용해서 제작하고 Diffuse Color Buffer 노드를 제작하여

방금 만든 BandedLighting Buffer와 2개의 Buffer를 모두 곱하여 Rim Light Buffer를 완성합니다.

 

이후 앞서 Outline들과 마찬가지로 Lerp를 통해 Buffer를 합쳐 줍니다.