23.6 C
Seoul
Thursday, June 4, 2020
Home Blog NGUI와 간단한 셰이더 포팅

[URP 포팅] NGUI와 간단한 셰이더 포팅

Universal Render Pipeline(이하 URP) 포팅 과정을 기록하는 시리즈의 두 번째 글입니다. 지난 편에서는 렌더링 엔진을 URP로 바꾸는 방법, LWRP 셰이더를 URP로 바꾸는 방법, URP의 내장 포스트 프로세싱 스택에 대해서 적었습니다. 이번 편에서는 NGUI와 자체 제작한 셰이더를 포팅하면서 고민한 내용을 적어보려고 합니다. 작업 메모를 옮긴 것이라 내용이 두서없을 수 있습니다~ 😙


NGUI 카메라를 활성화하니 UI는 잘 나오는데 인게임 화면이 나오지 않는다. URP에서 Camera Stacking을 지원하지 않기 때문. 왜 지원하지 않는지 설명해 놓은 문서를 찾았다.

유니티에서 Camera Stacking을 지원하면서 여러 가지 버그 때문에 고생을 했나보다. 성능 고민도 있었던 것 같고. 어쨌든 의도적으로 Camera Stacking을 지원하지 않는 것 같은데, 기능을 비교해놓은 문서를 보면 또 Camera Stacking 기능은 In Research라고 나온다. 다음 버전에서는 지원할 수도 있다는 얘기일까.

우선 생각해볼 수 있는 방법은 Render Texture를 사용하기. 인게임 카메라를 Render Texture로 찍고, NGUI의 배경에 그 Render Texture를 한 번 그려주는 방식. 동작은 잘한다. Frame Debugger로 원래 프레임 버퍼의 포맷을 확인해서 Render Texture의 포맷도 맞춰주면 Bloom도 잘 된다. 좀 더 테스트를 해봐야지.

장점은 Render Texture의 크기를 조절하는 것만으로 간단하게 Upscale Sampling이 되는 것. 어차피 Upscale Sampling을 해야 해서 잘됐다 싶기도 하고.

Upscale Sampling 이란

인게임의 해상도를 낮춰서 성능을 향상하면서, UI 해상도는 높게 해서 해상도가 낮아진 것이 잘 눈에 띄지 않도록 만드는 방법입니다.

근데 유니티 내장 uGUI를 사용하면 URP 애셋의 Render Scale을 조절하는 것만으로 Upscale Sampling이 된단다. 내부적으로 Render Texture 방식을 쓰는 것인지 혹은 좀 더 효율적인 방법으로 구현된 것인지 확인할 필요가 있다. 우선은 돌아는 가니까 다음 스텝으로 넘어가자.

할 일 추가: URP와 uGUI의 구현 원리 파악

인게임 플레이 테스트를 하다 보니 Grab Pass를 쓴 경우에 인게임 화면이 나오지 않는다. URP에서는 Grab Pass는 쓸 수 없고 대신에 URP Asset에 있는 Opaque Texture 옵션을 사용하거나 카메라 별로 오버라이드 할 수 있다고 한다. 나중에 다시 보자.

할 일 추가: Grab Pass를 Opaque Texture로 대체

이제 구매한 애셋인데 URP 지원이 없거나 자체 제작한 셰이더들을 포팅할 차례. 가장 간단한 몹 셰이더부터. 서피스 셰이더처럼 서피스 함수랑 라이팅 함수만 구현하면 알아서 코드 생성을 해주는 기능은 없나보다. URP의 표준 셰이더인 Lit 셰이더를 열어보니 vertex와 frag를 직접 코딩하고 있다. Lit 셰이더를 참조해서 만들면 어찌어찌 될 것은 같은데, 아티스트분들이 직접 만질 수 있으면 좋으니 Shader Graph로 변경.

Shader Graph로 하면 키워드로 #ifdef 분기하는 것은 어떻게 하는지 궁금하다. 역시 매뉴얼부터 읽어봐야겠지.

키워드 기능은 있다. 많이 좋아졌네. 작업하다 보니 우리 Metallic Map이 RGB로 되어 있는데 이거 모노 바꿔야겠다. 역시 URP로 포팅하다 보니 꼼꼼하게 보게 된다.

PBR Master를 사용해서 그래프를 하나 만들고 우리 프로젝트에서 쓰는 방식으로 맵만 연결해주면 끝. 작업은 간단한데 코딩하는 것보다는 오래 걸린다. 프로그래머 입장에서는 텍스트로 된 셰이더 파일이 눈에 더 잘 들어온다. 사실상 이런 간단한 셰이더는 프로퍼티 받아서 텍스쳐 샘플링하고 이리저리 값 곱해줘서 Albedo, Normal 등등을 세팅해주는 게 끝이라서, 어느 프로퍼티가 어디로 들어가는지는 텍스트로 된 코드가 더 명확하게 보인다. 반면에 Shader Graph는 중간중간 결과를 눈으로 볼 수 있고, HLSL 문법을 몰라도 작업할 수 있으니 아티스트 분들에게는 도움이 되겠지?

이제 몸도 풀었으니 복잡한 셰이더에 도전해보자. 몹 셰이더처럼 유니티의 PBR Lighting을 쓰는 간단한 셰이더 말고, 헤어와 스킨 셰이더처럼 자체적인 라이팅을 구현한 것들. 사실 이런 셰이더야말로 Shader Graph로 구현되어야 아티스트 분들이 이런저런 수정을 해볼 수 있는데, Shader Graph로 만들기 애매한 부분이 있다.

Shader Graph로 커스텀 라이팅을 구현하려면 Unlit Master를 사용하고 직접 계산한 라이팅을 Emission 노드에 연결해줘야 한다. 문제는 여러 광원을 쓸 때 모든 광원에 대한 처리를 직접 구현해 주어야 한다. PBR Master를 쓰면 하나의 Shader Graph를 광원 개수만큼 돌리면서 알아서 처리해준다. 또, 예전에 테스트했을 때는 Unlit Master를 쓰면 Shadow Map을 받지 못해서 다른 오브젝트의 그림자를 받지 못하는 문제가 있었다. 어찌어찌 다 해결할 수 있다고 해도 뭔가 억지로 쓰는 느낌이 들어서 토이 프로젝트가 아닌 이상에야 찝찝한 기분이 든다.

아무래도 복잡하고 성능이 중요한 셰이더를 Shader Graph로 만드는 것은 리스크가 있어 보인다. URP에서의 셰이더 코딩도 익힐 겸 HLSL로 가자.

Albedo와 Normal을 지원하고 Albedo.a에 smoothness를 넣는 간단한 Lit 셰이더로 연습을 하자. URP의 Lit 셰이더를 가져다가 고쳐보기로 하자.

기존의 서피스 셰이더는 surface 함수와 light 함수를 코딩하면 유니티가 알아서 필요한 pass를 생성해주는 식이었는데, 이제는 그런 게 없고 바로 vert, fragment 함수를 짜야 한다. Lit 셰이더를 보니 여러 패스를 구현하기 쉽게 셰이더를 쪼개 놓았고, 입력 처리는 LitInput.hlsl에서 각 패스를 LitForwardPass.hlsl이나 LitMetaPass.hlsl 같은 함수에서 구현했다.

어쨌든 Lit 셰이더에서 불필요한 #ifdef와 기능을 다 제거하면서 만들어 보니 어렵지 않다. 근데 Normal이 안먹는다. 이유는 Normal을 샘플링하는 유틸 함수가 URP의 SurfaceInput.hlsl 안에 있는데 _NORMALMAP 키워드로 감싸져있기 때문. 간단한 함수이므로 복사해와서 고쳐서 쓰니까 잘된다.

다른 패스들은 ShadowCaster, DepthOnly, Meta, Universal2D 가 있는데, 이미 구현된 셰이더 패스를 재활용해주면 된다. 추가로 코딩할 것은 없고 #include만 잘 하면 된다. 근데, 그렇게 하고나니 이 지글지글한 패턴이 나온다. 어디서 나온거지.

ShadowCaster 패스 같은 곳에서 컴파일 에러가 나는 것이 원인. Alphatest를 쓸 일이 없어서 _Cutoff 변수를 지워버렸는데, ShadowCaster나 DepthOnly 패스에서는 구멍을 뚫어야 해서 _Cutoff를 참조한다. 문제는 _ALPHATEST_ON 키워드를 정의하지 않아도 참조를 한다는 것인데, 결국에 최종적으로 clip()을 호출하는 부분은 _ALPHATEST_ON으로 감싸져 있어서 실제로 부하는 없을 것 같다.

ShaderCasterPass.hlsl이나 DepthOlayPass.hlsl을 하나 복사해서 _Cutoff 참조를 안하게 고쳐주는 것도 방법이지만, 나중에 관리비용이 들 것 같으니 그냥 LitInput.hlsl에서 _Cutoff를 살려주는 것이 제일 나은 해결책.

음, 처음에 겁먹었는데 한 번 만들어보니 별 것 없다. 오늘은 여기까지.


NGUI 업데이트

NGUI의 최신 업데이트인 2019.3에서는 하나의 카메라를 사용해서 UI를 그릴 수 있는 기능이 추가되었습니다. 이후 포스트에서 NGUI 2019.3 적용기를 공유해보겠습니다~

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Stay connected

58FansLike
56FollowersFollow
156FollowersFollow
128FollowersFollow
- Advertisment -

Recipe of the day