Unity URP Shader Study (4) - HLSL : Toon Shader

KAY·2023년 6월 13일
0

Study

목록 보기
5/5


대장장이 모델링, skybox, ground texture 등등 자료 출처 : 아티스트를 위한 유니티 URP 셰이더 입문

좌측에 있는 오브젝트를 우측처럼 만들어주는 쉐이더를 제작해봤다.

요약

코드에 사용된 기능?들은 다음과 같다.

  • Lambert Light : 램버트 라이트 계산
  • Toon Shading : 툰 쉐이딩 (램버트 라이트 값을 smooth step값으로 조절하고, Ceil 함수 써서 명암 나눔)
  • Ambient : 툰 쉐이딩에 환경광 적용하기
  • Rim Light(Fresnel) : 애니메이션에서 빛을 받는 면의 외각에 빛이 들어오는 것에서 레퍼런스. 이 림라이트에도 환경광 적용해줘서 자연스럽게 표현해보려고함. 얘는 부드러운 게 자연스러운 것 같아 그냥 smooth step으로 자연스러워 보이는 값만 주고 빛 받는 영역에 마스킹 해줌
  • Cast Shadow 그림자 보내기
  • Depth Pass 작성 (안 되어 있으면 저 바닥에 그리드가 매쉬 위로 그려진다.)
  • OutlineShader 얘는 우선 당장 결과를 보는 게 목적이었어서 보고있는 책 자료 긁어왔다.ㅎ,,
    결과물이 아쉬워서 좀 찾아봤는데, 2Pass 구현 방식(오브젝트를 cull front 해주고, 노말 방향으로 버텍스 위치를 키워 구현)이다 보니, 살짝 끊기는 부분들이 있었다. 다른 방법들을 알아봤는데, Fresnel로 외각선을 구현하는 방법(얘는 1Pass다.)과 Edge-detection method 방법이 있다고 한다. Sobel filter 혹은 edge-detection 필터를 화면의 노말과 방향에 더해, 엣지 텍스쳐를 구하는 방법인데 이거 유튜브 따라하다가 어려워서 잠깐 멈춰뒀다. 결과물이 가장 좋아보였어서 나중에 2Pass가 아닌 저 Edge-Detection으로 해보려고 한다.

Cast Shadow와 Depth Pass 는 우선 테스트 해보려고 대마왕님의 블로그를 참고하여 가져왔다.

지금 당장은 좀 여력이 안 되어서, 아쉬웠던 부분은 나중에 추가로 작업 해볼 예정이다...
1. Recieve Shadow 구현하기
2. Edge-Detection 방법으로 Outline Shader 구현해보기
3. Point Light 받아오기

1. 프로퍼티

    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        _LightStep ("Light Step", Range(0.01, 10)) = 1
        _LightIntensity ("Light Intensity", Range(0.01, 2)) = 1
        _CelShade ("Cel Shade", Range(0, 1)) = 0.1
        _CelWidth ("Cel Width", Range(0, 1)) = 0.1

        // [HDR] _SpecularColor ("Spacular Color", Color) = (0.5, 1, 1, 1)
        // _SpecPower ("Spec Power", Float) = 32

        [HDR] _RimColor ("Rim Color", Color) = (0.5, 1, 1, 1)
        _RimPower ("Rim Power", Range(0, 5)) = 1

        _OutlineColor("Outline Color", Color) = (1, 0, 0, 1)
        _OutlineDistance("Outline Distance", Float) = 0.1
    }

중간에 Specular 넣어보려던 흔적... 막상 넣어서 셀식으로 표현하니까 생각보다 안 예뻐서 패스했다.

라이트를 써야 하니까 Lighting.hlsl도 추가해준다.

            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"

2. 버텍스 데이터와 프래그먼트 데이터

            struct appdata
            {
                float4 vertex       : POSITION;
                float2 uv           : TEXCOORD0;
                float3 normal       : NORMAL;                
            };

            struct v2f
            {
                float2 uv           : TEXCOORD0;
                float4 vertex       : SV_POSITION;
                float3 normal       : NORMAL;
                float3 lightDir     : TEXCOORD1;
                float3 viewDir      : TEXCOORD2;
            };

normal은 노말을 계산하기 위해, lightDir은 빛의 방향을 구하기 위해, viewDir은 카메라를 향하는 방향을 구하기 위해 선언되었다.

이거 자료를 여러 개 같이 보면서 쓰다가 깨달은 건데, 저 v2f에서 굳이 lightDir을 써줄 필요는 없었다. Gouroud Shading 방식인 버텍스에서 라이트를 만들 게 아니라면....

3. 버텍스 쉐이더

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = TransformObjectToHClip(v.vertex.xyz);
                o.normal = TransformObjectToWorldNormal(v.normal);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);

                float3 positionWS = TransformObjectToWorld(v.vertex.xyz);
                o.viewDir = normalize(_WorldSpaceCameraPos.xyz - positionWS);

                return o;
            }
  • o.vertex는 오브젝트 버텍스를 클립 공간에 보내주어 제대로 보이게 함
  • o.uv는 오브젝트의 uv에 MainTex를 사용
  • o.normal은 오브젝트 공간의 버텍스 좌표가 버텍스 쉐이더에 전달되고, 버텍스 쉐이더에서 월드 공간의 벡터 좌표로 변경된다. 라이팅에서 사용하는 방향 정보가 월드 공간에서 계산하기 때문에 월드로 보내줘야 한다.
  • float3 positionWS는 viewDir를 구하기 위해 선언되었다. 프레넬 효과를 만들어주기 위함이다.
    o.viewDir은 뷰 벡터인데, 원래 레거시에서 viewDir 라는 함수로 쉽게 처리할 수 있었지만 URP에서는 그런 기능이 없어서 그런지? 직접 World Space View를 구해줘야 한다고 한다..

솔직히 프레넬의 구조는 반 정도.. 이해가 됐다. 램버트 라이트의 라이트가 카메라 벡터라고 생각하고, 월드 방향 기준으로 dot 연산해주면 카메라 벡터와 오브젝트의 방향이 맞을 때 1값이 들어가고, 사이드로 갈 수록 0이 된다. 이거를 invert해주면 사이드가 1이고, 마주보는 면이 0값인 프레넬 구조가 완성된다.

다만 왜 뷰 벡터가 카메라 포지션에서 오브젝트의 포지션을 빼야 나오는지 아직도 잘 모르겠다. 이럴 땐 그냥 공식이구나 하고 넘어가는게 정신 건강에 좋은 것 같다.

참고한 자료에는 이렇게 적혀있다. 지정된 객체 공간 정점 위치에서 카메라 방향으로 월드 공간 방향을 계산하고 정규화 함(URP Shader Training 자료, 유튜브 영상) ... 어렵다. 우선 넘어가자.


4. 프래그먼트 쉐이더

half4 frag (v2f i) : SV_Target
            {
                i.normal = normalize(i.normal);

                // float3 reflectDir = reflect(-i.lightDir, i.normal);
                // half spec = saturate(dot(reflectDir, i.viewDir));
                // spec = pow(spec, _SpecPower);
                // half3 specColor = spec * _SpecularColor;

                // Light
                float3 lightDir = _MainLightPosition.xyz;
                float3 LightColor = _MainLightColor.rgb;

                // texture
                half4 color = tex2D(_MainTex, i.uv);

                // ambient color calculate
                half3 ambient = SampleSH(i.normal);

                // light calculate
                float NdotL = saturate(dot(i.normal, lightDir) * 0.5 + 0.5);
                float3 Light = NdotL + ambient;

                //toon calculate
                float3 toon = ceil(smoothstep(_CelShade, _CelShade + _CelWidth, NdotL) * _LightStep);

                // toonLight calculate
                float3 ToonLight = (toon + 0.5) * LightColor *_LightIntensity + ambient;

                // fresnel
                half fresnel = 1 - saturate(dot(i.normal, i.viewDir));
                fresnel = pow(fresnel, _RimPower);
                half4 fresnelColor = smoothstep(0.5, 0.8, fresnel) * _RimColor;
                fresnelColor.rgb *= toon * LightColor * ambient;

                color.rgb *= ToonLight;
                color.rgb += fresnelColor.rgb;

                return color;
            }

주석 처리한 것은 스페큘러 계산했던 거.. 안 예뻐서 지웠다. 잘 쓰려면 노말 텍스쳐 받아와서 써야할 것 같은데 아직 HLSL에서 노말 맵을 받아오는 방법을 모른다. 나중에 도전.. ㄱ

그냥 내 입맛대로 좀 밝은 느낌의 툰 쉐이딩을 구현했다.
막상 해보니, 저 ceil 함수를 써서 음영을 구분하는 게 좀 애매했던 것 같다. 밝음, 중간, 어두움 이렇게 세 단계만 내려고 한다면 굳이 ceil 함수는 안 써도 될 듯. 나중에는 그냥 if else 써서 프로퍼티에서 음영이 시작되는 곳과 더 어둡게 음영을 줄 곳을 설정해서 직접 값을 바꿔가면서 보는 게 좋을 하다.


나중에 링크들 자료 뜯어보면서 더 연구해봐야댐

https://docs.unity3d.com/Packages/com.unity.toonshader@0.6/manual/index.html#overview--what-is-unity-toon-shader

https://github.com/Unity-Technologies/com.unity.toonshader


참고 자료 링크
Cast Shadow, Depth Pass : https://chulin28ho.tistory.com/692
2Pass Outline : 아티스트를 위한 유니티 URP 셰이더 입문
림 라이트 : 유니티 코리아 URP Shader Training

0개의 댓글