Light.Readman MYPI

Light.Readman
접속 : 5564   Lv. 61

Category

Profile

Counter

  • 오늘 : 14 명
  • 전체 : 7561 명
  • Mypi Ver. 0.3.1 β
[스크랩.게임 클라/서버] 붕괴3 방식의 카툰렌더링 구현하기 (0) 2022/01/05 AM 11:04


안녕하세요 마둠파입니다. 간만의 게시글이네요.

한동안 NDC준비하느라고 정신없었고 끝나고나선 여행도 다녀오고 이런저런 밀린 공부도 하느라 늦어졌습니다.

늦어진 만큼 이번에는 조금 흥미로운 주제를 들고 와 봤어요.

MjAxOTA1MTlfODYg/MDAxNTU4MjAwNjAyNzA0.WV2hnOqiCVqPvTsKBTp2HItQ18U5N7OiK6ijxYy5ie0g.XwftUX3E_Fk8Rh7Wg7Q0-yr1ejaQWOb1HFc1H4qhcvwg.PNG.mnpshino/image.png?type=w966

바로 유나이트 2018에 Jack He 님이 강연하신 붕괴3 렌더링 강연에 대한 분석을 해 보고 실제 구현까지 해 보는것을 해 볼 예정인데요, 그것을 구현하면서 다른 여러가지 카툰렌더링 방식에 대해서도 함께 세트로 알아보도록 하겠습니다.

유나이트 강연 링크 - https://www.youtube.com/watch?v=egHSE0dpWRw

오늘의 목차는 다음과 같습니다.

1. Floor를 이용한 카툰렌더링

2. Ramp를 이용한 카툰렌더링

3. IF를 이용한 카툰렌더링 + Shadow Threshold

4. 붕괴3에서의 카툰렌더링

그럼 시작해 볼게요.

사실 1,2,3번을 구현하기 전에 라이트벡터와 노멀벡터의 내적(Dot)을 이용한 Lambert와 HalfLambert를 먼저 구현해야 하지만

MjAxOTA1MTlfMjk4/MDAxNTU4MjAxMTM3ODIz.S-TvqNoGQ3X95UfpNept2mwWTAZsu7VLvfHQnw-sHBMg.FZoBeIkaB4g51HFio7l-xs0xGGf93eD_2U073vHpOI0g.PNG.mnpshino/image.png?type=w966

그것은 이전에 이미 여러번 해봤으니 과감하게 패스하도록 하겠습니다.

또한 외곽선에 대한 구현은 이미 외곽선 완벽정리에서 다룬적이 있으니 그것또한 패스하도록 하겠습니다.


1. Floor를 이용한 카툰렌더링

먼저 Floor 함수의 정의부터 알아봐야겠네요. Floor 함수는 '내림' 입니다. (반올림은 Round, 올림은 Ceil)

Floor의 뜻은 '바닥'이니 정말 직관적인 이름이라 할 수 있겠네요. 바닥으로 찍어 누른다고 생각해 주세요

0.1, 0.2, 0.3... 0.9 등을 내림하면 0이 되는 것과같이 그냥 소수점이하를 날려버린다고 생각해도 되지요.

MjAxOTA1MTlfMjc1/MDAxNTU4MjAxNzExNDg3.L8RhXlWsqhd8vmKsBL-9-Dmv0WD0p46znjxxtJ3qHokg.oLS7L2kSrugZg5lfy41kXvjKmwbZIHIJAyUamFxOag0g.PNG.mnpshino/image.png?type=w966

그러면 0에서 0.9999...까지는 같은 값 0 을 갖게 되겠죠?

이제 이것을 예를들어 한 5단계쯤으로 나눠보도록 할게요.

HalfLambert는 0에서 1까지의 값을 갖게 되니, 여기에 5를 곱하면, 0에서 5까지의 값을 고루 갖게 되겠죠

여기에 Floor를 적용하면 0,1,2,3,4,5의 값으로 소수점 없이 딱 떨어지게 될 것이고,

이 값을 아까 5를 곱했으니, 이번엔 0.2를 곱해주어 (5로 나눠준 것과 같죠) 다시 컬러의 Range를 1로 맞춰주면

0, 0.2, 0.4, 0.6, 0.8, 1의 값을 가지게 될 거에요.

MjAxOTA1MTlfMjc5/MDAxNTU4MjAyNTA4OTUw.2k4z4OGEzbKQqVQ34wZei8ZAv3YMEEwQbwHNzao9lAIg.JaTUGFpPM1-KTp3GSIgp-jjz21GUjuEctNEbEOsWKPkg.PNG.mnpshino/image.png?type=w966

위 그래프는 3단계지만.. 뭐 암튼 이런 모양을 갖게 되겠죠.

셰이더로도 한번 구현해 볼게요.

MjAxOTA1MTlfMTI5/MDAxNTU4MjA0MDA4MjE0.faPpcmpcRbs6p9xIkaqvJwnaLA7qxaZ8Gx5n1apLoHwg.c-xNjIMrA2RK0L0k6YZ3SfsQyr1gZzFixhthqA1-058g.PNG.mnpshino/image.png?type=w966

위에서는 단계를 5라는 임의의 수로 정해봤지만, 실제로 작업할 때는 이렇게 _StairNum이라는 변수를 하나 추가해 주시면

동적으로 단계를 조절할 수 있게 됩니다.

LightMode를 ForwardBase로 적어주지 않으면 노멀이 뒤집히는 경우가 생길 수 있으니 이부분에도 신경써주세요.

MjAxOTA1MTlfNDEg/MDAxNTU4MjA0MTA5NjQ2.LejYjc3eEQQhD91_2cGw4aUse-D-VZgHNvDU95exKzgg.Cd4j_87A6V0OW4mfPcCp36vNEVSn5Wug_dH47wfyT38g.PNG.mnpshino/image.png?type=w966

이렇게 픽셀 셰이더에서 _Stair 단계만큼 미리 곱하고, floor로 내림하고, 다시 _Stair단계만큼 나누면 원하는 값을 얻을 수 있게 됩니다.

MjAxOTA1MTlfMjE5/MDAxNTU4MjA0Mjc3MDY2.E-rrhCRWAJ7I71yrjVraLhw4E6qB3sRAU2FAiXeERAkg.WyvVrY37pih9YdqXNDezHlJeEKqiNE3Ql1SwmNm-eqAg.GIF.mnpshino/FloorToon.gif?type=w966

_StairNum을 조절하면 이렇게 다양한 명암 단계를 얻을 수 있습니다.

이 방법은 계산이 가볍고 명암경계가 깔끔한 것이 장점이라고 할 수 있고요,

여러가지 응용이 어려운 부분이 단점이라고 할 수 있겠네요.

그럼 다음 방법으로 넘어가 보겠습니다.


2. Ramp를 이용한 카툰 렌더링

이 방식은 Diffuse Warp(Warped Diffuse) 라는 방식으로, 이 방식에 사용되는 텍스처가 Ramp 텍스처 입니다.

MjAxOTA1MjBfMjQ3/MDAxNTU4Mjk1NzMwNjM5.GWM5_UN1_IRDzHPB-EQ2miuXst5gJTX0ER5T-fY1NO0g.TfDU6opnQU8uRRuPWYEOykKuHDHKNTe7yV3RQHcc6V4g.PNG.mnpshino/image.png?type=w966

이렇게 생긴 텍스처죠

밸브의 팀포트리스2에서 최초로 사용하기 시작한 셰이딩 방식이죠. 상당히 유서가 깊은? 방식이고

선명한 명암을 표현할 수 있으면서도 부드러운 명암도 표현할 수 있어서 저도 자주 사용하는 방법입니다.

관련 문서 - https://steamcdn-a.akamaihd.net/apps/valve/2007/NPAR07_IllustrativeRenderingInTeamFortress2.pdf

MjAxOTA1MjBfMTk0/MDAxNTU4Mjk1ODU5MDc1.gpGDkFLw5e8UYS74mAVv0OJ7XvCxyqWUXD4bw_GCDGkg.iVtyFcn484m20Temk-_09H2XCI9ns8h95DfirFN2Z68g.PNG.mnpshino/image.png?type=w966

위쪽의 Ramp텍스처와 아래쪽 결과를 함께 보시면 램프 텍스처가 어떻게 결과물에 영향을 미치는지 알 수 있습니다.

이 방식은 RampTexture를 샘플링할때 uv에 HalfLambert 값을 적용해 주어,

HalfLambert의 값이 어두우면 텍스처의 왼쪽, 밝으면 텍스처의 오른쪽을 매핑해주는 방식이라고 생각하시면 됩니다.

가로축을 HalfLambert에 대응하는 방법 외에도 세로축을 어떻게 쓰느냐에 따라서 여러가지로 응용이 가능한데요.

MjAxOTA1MjBfMjY4/MDAxNTU4Mjk4NTU4MDgx.vIZeb4PRl379TmTh7vcndcXLh066YJ2ug7O22XX60Rog.5YMNp40QYYBhiQpjg9harWGJpe83JufRsUdjhjOILbEg.PNG.mnpshino/image.png?type=w966

예를들면 붕괴3 MMD에서는 이렇게 버텍스 컬러 페인팅을 이용하여 버텍스 컬러값으로 UV의 Y축을 이동시켜 소프트한 명암과 하드한 명암을 직접 컨트롤 하기도 합니다. (MMD셰이더와 인게임 셰이더는 다른방식으로 구현되어 있습니다.)

또, 밸브의 DOTA2를 보면 Diffuse Warp Mask 라는 하나의 텍스처를 추가로 이용하는데요,

MjAxOTA1MjBfMTg3/MDAxNTU4Mjk2NTMxNDUz.LbOSS4aUIGoafqXUYwd2oJCc64FUdtCl7imAG4uf92Ug.54r0SyfnM4ivHjggOatkXaBKtdjaVXQXqw7nIp6fJskg.PNG.mnpshino/image.png?type=w966

마스킹된 부분의 값으로 각각 어느 그래디언트를 가리킬지 정하기도 합니다. (버텍스 컬러를 이용하는게 최적화엔 더 좋겠죠)

MjAxOTA1MjBfMSAg/MDAxNTU4Mjk4MjgxMzE5.ytCF2cIw6lgH7tHNtCmZpYokhzpq13XCFoFsTeHsxyEg.qQT2iyFz8CI3oTGupniZHxC1UkXGaDIkq3ypjMVi0DEg.PNG.mnpshino/image.png?type=w966

또 시그라프의 Pre-Integrated Skin Rendering 자료를 보면 가로uv는 NdotL을 사용하고, 세로 uv는 1/Radius(Curvature)를 사용했는데요,

관련문서 - http://advances.realtimerendering.com/s2011/Penner%20-%20Pre-Integrated%20Skin%20Rendering%20(Siggraph%202011%20Advances%20in%20Real-Time%20Rendering%20Course).pptx

MjAxOTA1MjBfNjcg/MDAxNTU4Mjk4MzIzNDYx.wrA4E6xGFXGsasd7UEDdfnVMoxvRRIKbtBXx9foYxGwg._XEP5D60fEFD2TIEijClakZH42wnI9oQO45Ei0ks9ggg.PNG.mnpshino/image.png?type=w966

이렇게 노멀편미분을 포지션 편미분으로 나눈 값을 사용하는 방식입니다.

미분이라는 얘기가 나오면서 다소 어렵게 생각이 드실 수 있는데요, '변화량' 정도로 생각해 주시면 됩니다.

포지션대비 노멀의 변화량이 큰 눈, 코, 입 쪽에 SSS의 붉은 기운을 더 넣어준다는 정도로 이해하시면 될 것 같아요.

fwidth의 정의를 살펴보면 다음과 같습니다.

MjAxOTA1MjBfMTM2/MDAxNTU4MzE1NDczNTM1.d9hXE7ONripnlTWTwVfQIzJpOvxOXhV_uhwtrYZdENkg.cYo8-IVji7TAhsg7OPncDDYrdBtpiNgtExA5Qv5F1Tgg.PNG.mnpshino/SE-856b7a3c-6325-404c-8483-ffa7c9f80248.png?type=w966

그 외에 조금 더 쉽게 사용하기 위해서 NdotV를 세로축으로 이용한 BRDF를 사용하기도 합니다.

자 그럼 Ramp를 이용한 방식도 만들어 보도록 하겠습니다.

MjAxOTA1MjBfMTk2/MDAxNTU4Mjk5NTQ3ODkw.gfhz6oFinF-0lKmh5r0Ydtcyc4Q-LqEO7Xe0cFRvNEMg.9Q5d2_2RLU7xAZ_SUKxK-NqIQJjvKRBwiyQZvdCDsQIg.PNG.mnpshino/image.png?type=w966

_RampTex 라는 sampler2D 변수를 프로퍼티에 선언해두고 이렇게 사용하면 되겠죠.

float2(halfLambert, 0) <- 이부분이 uv인데요, 세로축이 지금은 0으로 되어있는데, 여러가지 방법으로 변화시켜 보시기 바랍니다. 여러가지 재미있는 효과를 만들 수 있어요.

MjAxOTA1MjBfMTY5/MDAxNTU4Mjk5OTEyMTEx.wkaffKVDs7_yWOgkYE99EyPAlA6ppI-oclbCfebC-2og.ZA6WWeUSBSSwOVkyGHiiqDWZOMSA0d0GeQ8ytUUnGfwg.PNG.mnpshino/image.png?type=w966

저는 일단 대충 위와같은 텍스처를 사용해서 이렇게 구와 석상에 적용해 보았습니다.

그럼 오늘의 하이라이트인 다음방식으로 넘어가 보겠습니다.


3. IF를 이용한 카툰렌더링

셰이더에서 IF쓰면 느린거 아냐? 라고 생각하실 수 있으실 텐데요, 이전에 스파이더맨 느낌의 셰이더 구현할때 이미 Step()으로 대체하는 방식을 공유했으니 그부분을 참고하여 봐 주시면 될 것 같습니다.

IF를 이용한 방식은 사실 코드로 보면 더 간단합니다.

MjAxOTA1MjBfNjMg/MDAxNTU4MzAwMzY5OTI0.wLNl697zHaQfZolotfEtiHehxEIsGC-ISDOeKSuEaf4g.jILHKQWqIgOEdTCsWPSrUjnR-TA9ZDZ6DocRbihn94kg.PNG.mnpshino/image.png?type=w966

이렇게 HalfLambert 값이 0.5보다 작으면 그림자 컬러를 곱해주어라. 하고 셰이더에 적어주면 끝이지요.

MjAxOTA1MjBfMjI3/MDAxNTU4MzAwMTc1Nzc3.Qzo2QngZVj7WVkLJoIRive8D8lLnh3N4R5D5FoH0qf4g.qXFnSI5TpOGyL6H3U1QpG9I6YvNdPp_OXEOkyj4GvLIg.PNG.mnpshino/image.png?type=w966

길티기어 Xrd에서도 이 방식을 이용했습니다. 다만, 왼쪽과같이 AO를 버텍스 컬러에 페인팅 해 준 값을 곱하여 가중치로 사용했지요

MjAxOTA1MjBfNjQg/MDAxNTU4MzAwNTQxODQ4.L0ExfPS9sfiLDt-VYqe4gYynDLlQp9JJQygJB3ZuCEog.fhy0jTOGrAUsZZMhB7S_8Bb49_BCb1uArx6mgjLNizMg.PNG.mnpshino/image.png?type=w966

4. 붕괴에서의 카툰렌더링

자 그럼 붕괴는 어떻게 명암을 구현했을까요? 먼저 유나이트 자료를 보도록 하겠습니다.

MjAxOTA1MjBfMzQg/MDAxNTU4MzAwNjIzODg0.MutrD794LKet0qF4TiuEraRwnbOGBwZSFK81gFWHkH8g.LltCSa6Q1QkH2NIc9lJq0ce7i9L8sJlijDlBC5AyeOQg.PNG.mnpshino/image.png?type=w966

발표자료를 보면, Lightmap이란 것을 사용했다고 나오는데요, 이 부분이 붕괴 인게임 셰이더의 핵심이라고 할 수 있겠습니다.

Specular 맵과 Shadow Threshold 맵을 Lightmap이라는 Mask텍스처 안에 채널별로 모은 형태로 볼 수 있겠는데요,

각 채널이 하는 일을 알기 위해 이 텍스처를 R,G,B로 분해해 볼게요

MjAxOTA1MjBfMTQx/MDAxNTU4MzAxMDk4Njkw.fAh1qBUhqLaofGj0bJd0wM-ZFDkIf8KicCFg-EoFwr8g.P3mm619b0zHq_SHEcWYO4BV2LrQkrJ-gq2iVJXIch6sg.PNG.mnpshino/image.png?type=w966

저는 이 텍스처와 위의 모델을 보고, R,B채널은 스펙큘러 관련 채널일 것으로 짐작했고, G채널의 역할이 명암에 가중치를 주는 부분일 것이라 생각했습니다. 그 이유는 이런 방식의 카툰렌더링에서는 스펙큘러사용을 굉장히 제한적으로 하기 때문입니다. 금속재질을 표현하거나, 면이 꺾이는 부분이나, 가죽재질을 표현하거나 할때 사용하게 되기 때문이죠.

MjAxOTA1MjBfMjcz/MDAxNTU4MzAxNzE1NTM0.MCPZh2eCdRBAwUaEywD7ngoJ3AoTc4I3DOvUzbTFxrIg.FQoNbV6kfJC-j0IBuQAHI1pqsupGwVUKFTMcWpWR8p8g.PNG.mnpshino/image.png?type=w966

R채널

R채널을 자세히 보면, 한 재질에서는 모든 값이 동일한 것을 알 수 있습니다. 이는 그 재질에서의 번들거림이 모두 동일하게끔 만들어 진 것이라 생각해서, Specular Glossiness(Power라고도 하죠) 인것을 알 수 있었고요,

MjAxOTA1MjBfMjU4/MDAxNTU4MzAxNjg3NTk3.Y3dmQWhOcOG_rEr-eULIZQApj1EudsSTc5z2k38a38Eg.epSFFbSzmTt18MQZqwLgLUI7jWbQAUNqp200QyhXx-Yg.PNG.mnpshino/image.png?type=w966

B채널

B채널을 자세히 보면 면이 꺾이는 부분에 특히 강조를 해 준것을 볼 수 있습니다. 이것을 봐서 Specular Masking 채널임을 알 수 있었습니다.

그럼 이제 G채널이 가장 중요해 지는데요.

MjAxOTA1MjBfMjA1/MDAxNTU4MzAyODA1MTA4.wLl5Wyn1l4xR_F6OsKBteNhv6UvNk7NeC7ATuorxfWwg.TdxpwNVyJrTCLUu9MWpXF27tRXLBBRSK403Vo4T8dCYg.PNG.mnpshino/image.png?type=w966

G채널

G채널을 잘 보면 중간 회색을 베이스로 하는것을 알 수 있습니다. 중간회색이라 함은 0.5구요.

0.5...? 여기서 뭔가 전기가 파바박 하고 스쳤을 것입니다.

Threshold라는 단어의 뜻은 한계점, 문턱 이라는 뜻인데요, 바로 이 G채널이 명암을 나누는 Threshold 역할을 하는 것이죠.

0.5 대신에 이 Threshold 맵을 넣으면 됩니다.

MjAxOTA1MjBfMTI2/MDAxNTU4MzAxNDI1NTY3.sqLU8CdhXLlZGukQRFlUcROhhACtvtYbnamet_mSg-cg.niDjmkYmQyUILuTCXt7TY1Zxkz5YHz44MHYnWIGO9YIg.PNG.mnpshino/image.png?type=w966

코드로 보면 이렇습니다. 정말 간단하죠

이것을 적용하면

MjAxOTA1MjBfMTQg/MDAxNTU4MzAxNTE3ODc3.LacJ8VmIpoVDyQoOnx-xR3gJF13t0IUgAmG35z4qnlAg.ZlZLaxJhCfaROJAnKRn0NkNgQPFAaWHgNIYCQx6d_WAg.GIF.mnpshino/threshold.gif?type=w966

왼쪽 - Threshold 적용 / 오른쪽 - 미적용

이렇게 Threshold를 사용한 쪽은, 머리카락으로 인하여 생기는 얼굴명암등에 가중치를 줄 수 있게 됩니다.

(0.5가 아니라 0.2혹은 0.1만 넘어도 어두운면으로 넘어가게 되는 거죠)

그러므로써 조금 더 원하는 느낌의 그림자 모양을 컨트롤 할 수 있게 되는 것입니다.

명암을 디테일하게 그려줄 수 있다는 부분에서, 일부 노멀맵과 같은 효과가 난다고 볼 수도 있겠습니다.

여기에 스펙큘러도 R채널과 B채널 적용을 마친 결과값이 특정 변수를 넘어간 부분만 SpecularColor가 나오게 하는 방식으로 구현하면 붕괴 인게임 셰이더를 구현할 수 있습니다.

구현을 보면 아시겠지만, 기술자체는 어려울 것이 없습니다. 그리고 셰이더 적용 안해도 기본 모델링 퀄리티가 훌륭합니다. 셰이더라는것이 때때로 마법을 부리기도 합니다만, 훌륭한 모델링이 훌륭한 아웃풋으로 이어진다는 사실을 새삼 깨닫게 해주는 케이스인 것 같습니다. 기본 모델링 퀄리티가 좋지 않으면 아무리 마법을 부리려고 해도 잘 안되는 경우가 있으니까요. 그리고 카툰렌더링은 정말 모델러와 TA가 함께 만들어 가야 하는 분야라는 생각이 드네요.

그럼 다음에도 읽을만한 게시글로 돌아오겠습니다. 감사합니다.

신고

 
X