본문 바로가기
튜터리얼_스터디

(HLSL)아티스트를 위한 URP 셰이더 Shader #13_2 - Custom Light2

by 대마왕J 2021. 12. 24.

후후 이번엔 지난 시간에 이어서 제대로 좀 완성해 볼거예요

뭐가 안됐더라.. 조명 연산만 겨우 했고, 옆의 공과 비교해서 한 번 생각해 볼께요

1. 그림자를 뿌리지 않습니다. 
2. 깊이값이 없습니다. 격자가 동상 위에 그냥 그려지는거 보세요. 품위없네요
3. 엠비언트가 없습니다. 음영이 새카맣네요. 
4. 그림자를 '받지' 않습니다. 공의 그림자가 동상에 씌워지지 않아요

뭐야 할 게 많잖아!!! 때려쳐! 안해!!! 귀찮아!!!! 

 여러분 오늘로써 셰이더 강의를 마치겠습니다. 

안녕 

 

블로그 주인장에게 커피값을 후원할 수 있습니다! 

donaricano-btn

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

...빼꼼 

갔나?

 

아  아직 안 가셨으면 그럼 뭐 ... ㅋㅋㅋ 해야지 
주섬주섬 ... 잠시만요 짤 좀 줍고...

그림자Shadow / 댑스Depth  패스 만들기 

자 그럼 그림자를 뿌려볼게요

일단 그림자를 뿌리려면 Pass 를 나눠야 해요. 
뭔 얘기냐면, 그림자는 그림자용 Pass에서 그리는거인데 , 우리가 만든건 라이팅 패스였던 거예요. 그러니 여기서 그림자 그리면 혼남 (아님) . 그런데 지금 뭔 패스에서 그려야 하는지 지정도 안해줬었죠?

 

그러니 일단 지금의 라이팅 패스에 확실하고 진지한 포워드 패스 태그를 달아줘서, 라이팅 패스때 그리도록 확실히 해 줍시다. 진지하게 진지.

hader "URPUnlitShaderBasic"
{
    Properties
    {
        _BaseMap("BaseMap",2D) = "white"{}
    }

    SubShader
    {
        Tags { "RenderType" = "Opaque" "RenderPipeline" = "UniversalPipeline" }

        Pass
        {
            Name "ForwardLit" //패스의 이름입니다. 다른데서 가져다 쓸 때 쓴다던가 등등 그럴때 이용
            Tags { "LightMode" = "UniversalForward" } //라이트모드 태그, 어느 렌더링에 그려질거냐를 결정해 줍니다 

            HLSLPROGRAM
            #pragma vertex vert
            #pragma fragment frag

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

            struct Attributes
            {
                float4 positionOS   : POSITION;
                float2 uv           : TEXCOORD0;
                float3 normalOS       : NORMAL; 
            };

            struct Varyings
            {
                float4 positionHCS  : SV_POSITION;
                float2 uv           : TEXCOORD0;
                float3 normalWS       : NORMAL; 
            };

            TEXTURE2D(_BaseMap);
            SAMPLER(sampler_BaseMap);

            CBUFFER_START(UnityPerMaterial)
                float4 _BaseMap_ST;
            CBUFFER_END

            Varyings vert(Attributes IN)
            {
                Varyings OUT;
                OUT.positionHCS = TransformObjectToHClip(IN.positionOS.xyz);
                OUT.uv = IN.uv;
                OUT.normalWS = TransformObjectToWorldNormal(IN.normalOS); 
                return OUT;
            }

            half4 frag(Varyings IN) : SV_Target
            {
               
                Light light = GetMainLight();
                float NdotL = saturate(dot(light.direction, IN.normalWS)); //1. Saturate 처리로 0 ~ 1로 제한 

                half4 color = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, TRANSFORM_TEX(IN.uv, _BaseMap));
                color.rgb *= NdotL; //2. NdotL을 칼라와 곱해주고 
                return color; //3. 확인
            }
            ENDHLSL
        }
    }
}

 Name "ForwardLit" 
 Tags { "LightMode" = "UniversalForward" } 

이렇게 이름과 태그를 달아줬습니다. 
이름은 .. 사실 그다지 중요하지 않아요. 저거야말로 저 패스를 다른데서 쓸 때 사용하는 이름. 언젠가 어디선가 쓰겠지... 

그러므로 사실 중요한건 태그에 라이트모드를 UniversalForward 로 해주는 겁니다. 저렇게 하면 저것들 그릴때 같이 그려요.  '나는 사실은.. 포워드 라이팅을 그리는 식구였던 것이야..... ' 라는 태그죠 출생의 비밀

그럼 넌 배트맨 동굴로 가든가

자 , 라이팅 태그를 달아줬으면, 
이번엔 '셰도우 캐스터' 라는 라이트 모드를 가지고 있는 다른 패스를 하나 추가해 주죠 

이건 다른데 것을 가져오는게 빨라요. 언제 일일히 설명하구 있누.

Pass
{
    Name "ShadowCaster"
    Tags{"LightMode" = "ShadowCaster"}

    ZWrite On
    ZTest LEqual
    ColorMask 0
    Cull Front

    HLSLPROGRAM

    #pragma vertex ShadowPassVertex
    #pragma fragment ShadowPassFragment

    #include "Packages/com.unity.render-pipelines.universal/Shaders/SimpleLitInput.hlsl"
    #include "Packages/com.unity.render-pipelines.universal/Shaders/ShadowCasterPass.hlsl"
    ENDHLSL
}

굉장히 짧아 보이죠? 사실 저 맨 뒤 include 두 개 안에 버텍스 셰이더와 픽셀셰이더가 또 들어가서 작성되어 있어요. 
덕분에 인클루드만 써주면 저 안의 코드가 여기 다 알아서 들어오는 격이죠. 

버텍스 셰이더  함수 이름은 ShadowPassVertex 
픽셀 셰이더 함수 이름은  ShadowPassFragment

이것도 전부 처음부터 짤 수 있는데요, 지금은 이렇게 simpleLit 셰이더에서 사용하는 셰도우 캐스터를 훔쳐와서 붙여 버리기로 하지요. 

그리고 동일한 방식으로 댑스(깊이값)도 그릴 수 있는데요. 

Pass
{
    Name "DepthOnly"
    Tags{"LightMode" = "DepthOnly"}

    ZWrite On
    ColorMask 0
    Cull Front

    HLSLPROGRAM
    #pragma exclude_renderers gles gles3 glcore
    #pragma target 4.5

    #pragma vertex DepthOnlyVertex
    #pragma fragment DepthOnlyFragment

    #include "Packages/com.unity.render-pipelines.universal/Shaders/SimpleLitInput.hlsl"
    #include "Packages/com.unity.render-pipelines.universal/Shaders/DepthOnlyPass.hlsl"
    ENDHLSL
}

이걸 또 뒤에 달아주면, 깊이값도 들어가게 됩니다. 이것도 가져온거예요. 아이 편해라. 

이걸 제대로 알아보는건 담에 하고요, 일단 이대로 써서 넣어둡시다. 

자 그렇게 하면 풀버전이 이렇게 됩니다. 
아무래도 좀 길어져요 

Shader "URPUnlitShaderBasic"
{
    Properties
    {
        _BaseMap("BaseMap",2D) = "white"{}
    }

    SubShader
    {
        Tags { "RenderType" = "Opaque" "RenderPipeline" = "UniversalPipeline" }

        Pass //포워드 패스입니다 
        {
            Name "ForwardLit" //패스의 이름입니다. 다른데서 가져다 쓸 때 쓴다던가 등등 그럴때 이용
            Tags { "LightMode" = "UniversalForward" } //라이트모드 태그, 어느 렌더링에 그려질거냐를 결정해 줍니다 

            HLSLPROGRAM
            #pragma vertex vert
            #pragma fragment frag

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

            struct Attributes
            {
                float4 positionOS   : POSITION;
                float2 uv           : TEXCOORD0;
                float3 normalOS       : NORMAL; 
            };

            struct Varyings
            {
                float4 positionHCS  : SV_POSITION;
                float2 uv           : TEXCOORD0;
                float3 normalWS       : NORMAL; 
            };

            TEXTURE2D(_BaseMap);
            SAMPLER(sampler_BaseMap);

            CBUFFER_START(UnityPerMaterial)
                float4 _BaseMap_ST;
            CBUFFER_END

            Varyings vert(Attributes IN)
            {
                Varyings OUT;
                OUT.positionHCS = TransformObjectToHClip(IN.positionOS.xyz);
                OUT.uv = IN.uv;
                OUT.normalWS = TransformObjectToWorldNormal(IN.normalOS); 
                return OUT;
            }

            half4 frag(Varyings IN) : SV_Target
            {
               
                Light light = GetMainLight();
                float NdotL = saturate(dot(light.direction, IN.normalWS)); 
                half4 color = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, TRANSFORM_TEX(IN.uv, _BaseMap));
                color.rgb *= NdotL; 
                return color; 
            }
            ENDHLSL
        }
        
        Pass //그림자 패스입니다.
        {
            Name "ShadowCaster"
            Tags{"LightMode" = "ShadowCaster"}  

            ZWrite On
            ZTest LEqual
            ColorMask 0
            Cull Front

            HLSLPROGRAM

            #pragma vertex ShadowPassVertex
            #pragma fragment ShadowPassFragment

            #include "Packages/com.unity.render-pipelines.universal/Shaders/SimpleLitInput.hlsl"
            #include "Packages/com.unity.render-pipelines.universal/Shaders/ShadowCasterPass.hlsl"
            ENDHLSL
        }

        Pass //깊이 패스입니다 .
        {
            Name "DepthOnly"
            Tags{"LightMode" = "DepthOnly"} 

            ZWrite On
            ColorMask 0
            Cull Front

            HLSLPROGRAM
            #pragma exclude_renderers gles gles3 glcore
            #pragma target 4.5

            #pragma vertex DepthOnlyVertex
            #pragma fragment DepthOnlyFragment

            #include "Packages/com.unity.render-pipelines.universal/Shaders/SimpleLitInput.hlsl"
            #include "Packages/com.unity.render-pipelines.universal/Shaders/DepthOnlyPass.hlsl"
            ENDHLSL
        }
    }
}

위에서 3개의 패스가 각각 씌여진걸 보셨나요? 

여기서 알게된게 있죠. 뭘 하나 그리려면 

그냥 그리는거 하나 , 그림자에 하나, 깊이값에 하나 총 3번의 렌더링을 해야 한다는 것을요. ( 경우에 따라 이건 더 늘어나요) 
그러니 그림자를 끄거나 깊이값을 꺼주면 그만큼 더 효율적이 되는거죠. 

와아 그럼 이제 동상의 그림자도 나오기 시작하고, 댑스값도 쓰기 시작해서 격자마크가 석상을 덮어 버리지도 않습니다. 

댑스값 프리뷰에도 나오고 

4개의 셰도우 캐스케이드에서도 잘 보입니다. 

자 이렇게 일단 그림자와 댑스는 얼렁뚱땅 끝. 

얼렁뚱땅 하니까 편하네

 

 

엠비언트 라이트 Ambient Light

 

이번엔 환경광인 엠비언트 라이트를 받아볼께요. 
에.. 이것도 제대로 받으려면 한도 끝도 없지만 ㅎ 종류가 많아서... 

심플하게 스카이박스 데이터만 받으려면 뭐 어렵지 않습니다. 
역시 픽셀셰이더 (프레그먼트 셰이더) 만 볼께요
한 줄만 추가하면 되네요. 

half4 frag(Varyings IN) : SV_Target
{
  Light light = GetMainLight();

  float3 lightDir = normalize(light.direction);
  float3 normalWS = normalize(IN.normalWS);

  float NdotL = saturate(dot(lightDir,normalWS)); 
  half4 color = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, TRANSFORM_TEX(IN.uv, _BaseMap));

  float3 Ambient = SampleSH(normalWS); //엠비언트를 가장 간단하게 받는 함수 
  color.rgb *= NdotL; 
  return float4(Ambient,1); //엠비언트를 출력해 봅시다.
}

이렇게 SampleSH 함수에 normalWS 값만 넣어주면 엠비언트를 받을 수 있습니다. 
그리고 출력해 보면요

 

짜잔 엠비언트가 잘 나오는걸 볼 수 있습니다. 

 

Shadow Attenuation (그림자 감쇄)

자 이번엔 '그림자를 받는' 걸 만들어 볼 거예요. 

내가 그림자를 '뿌리는' 건 Shadow Pass로 그리는 거구요 
내가 그림자를 '받는' 건 Shadow Attenuation, 줄여서 Shadow Atten 이라고 해요. 

이걸 받는건 조금 일이 되는군요. 작업할게 꽤 돼요... 후후후 그래도 설명해 볼께요. 
포워드 릿 패스만 보시면 됩니다. 

Pass //포워드 패스입니다 
        {
            Name "ForwardLit" 
            Tags { "LightMode" = "UniversalForward" }

            HLSLPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            //1.메인 라이트 셰도우 캐스케이드를 받을 수 있는 키워드를 선언합니다. 이후 TransformWorldToShadowCoord 함수에서 이걸 사용해서요 
            #pragma multi_compile _ _MAIN_LIGHT_SHADOWS_CASCADE 
            //2. 소프트 셰도우도 함께 넣어줍니다. 
            #pragma multi_compile_fragment _ _SHADOWS_SOFT 

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

            struct Attributes
            {
                float4 positionOS   : POSITION;
                float2 uv           : TEXCOORD0;
                float3 normalOS       : NORMAL; 
            };

            struct Varyings
            {
                float4 positionHCS  : SV_POSITION;
                float2 uv           : TEXCOORD0;
                float3 normalWS       : NORMAL;
                float3 positionWS       : TEXCOORD1; //4. 월드 포지션을 넘깁니다. 
            };

            TEXTURE2D(_BaseMap);
            SAMPLER(sampler_BaseMap);

            CBUFFER_START(UnityPerMaterial)
                float4 _BaseMap_ST;
            CBUFFER_END

            Varyings vert(Attributes IN)
            {
                Varyings OUT;
                OUT.positionHCS = TransformObjectToHClip(IN.positionOS.xyz);
                OUT.positionWS = TransformObjectToWorld(IN.positionOS.xyz);//3. 월드 포지션을 계산합니다.  
                OUT.uv = IN.uv;
                OUT.normalWS = TransformObjectToWorld(IN.normalOS); 
                return OUT; 
            }

            half4 frag(Varyings IN) : SV_Target
            {
               
                float4 shadowCoord = TransformWorldToShadowCoord(IN.positionWS);//5.월드 포지션을 이용해서 셰도우 좌표를 받아옵니다.   
                Light light = GetMainLight(shadowCoord); //6.라이트 함수에 셰도우 좌표를 넣어줍니다. 

                float3 lightDir = normalize(light.direction);
                float3 normalWS = normalize(IN.normalWS);

                float NdotL = saturate(dot(lightDir,normalWS)); 
                half4 color = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, TRANSFORM_TEX(IN.uv, _BaseMap));

                float3 Ambient = SampleSH(normalWS); 
                color.rgb *= NdotL; 
                return light.shadowAttenuation; //7.셰도우 감쇠만 출력해서 확인해 봅시다 
            }
            ENDHLSL
        }

 

1. 맨 처음에 #pragma multi_compile _MAIN_LIGHT_SHADOWS_CASCADE  라는 키워드를 선언했죠? 
이건 셰도우 캐스캐이드를 쓸 때 활성화 되는 키워드인데, TransformWorldToShadowCoord ( ) 함수 안에를 보면 이 키워드로 분기가 되어 있어요. 그래서 캐스캐이드 셰도우를 받으려면 이걸 써야 합니다. 


2. 그 다음에  #pragma multi_compile_fragment _ _SHADOWS_SOFT 라고 하는 키워드가 있는데요. 
이걸 해 줘야 이렇게 소프트 셰도우를 쓸 수 있게 됩니다. 이것도 넣어줘 봅시다. 

3. 버텍스 셰이더 안에서 월드 포지션을 구해줍니다. 
OUT.positionWS = TransformObjectToWorld(IN.positionOS.xyz);
4. 그리고 이걸 픽셀셰이더로 넘겨요. 남는 자리는 TEXCOORD 1번을 쓰죠. 
float3 positionWS       : TEXCOORD1;

그리고 픽셀셰이더로 갑니다. 

5. float4 shadowCoord = TransformWorldToShadowCoord(IN.positionWS);
여기서 월드 포지션을 이용해서 셰도우 좌표를 만들어 줍니다. 옛날엔 이게 버텍스에 있어서 경계면에 문제가 생겼데요 ㅎㅎ 
https://blog.naver.com/mnpshino/222154224409 

 

URP 셰이더 코딩 튜토리얼 A/S : 케스케이드 섀도우가 이상해요!

안녕하세요 마둠파입니다 전에 셰이더 코딩 튜토리얼 시리즈를 올려 뒀었는데요, 해당 셰이더로 케스케이드...

blog.naver.com

둠파님 블로그에 나와 있습니다. 뭐 지금은 별 문제 없습니다. 

 

6.  GetMainLight 함수에 shadowCoord 를 넣어주면, 드디어  light.shadowAttenuation 에 값이 들어오기 시작합니다. 

7. 이걸 출력해서 확인해 봅시다. 

 

예에입! 그림자를 제대로 받는군요. 이렇게 Atten 처리까지 완료되었습니다!!

 

 

결과 전부 더하고 정리하기 

휴우, 이제 필요한건 다 구한 것 같아요. 

그럼 이제 이렇게 구한 것들을 가지고 마지막 연산을 해 봅시다. 

의외로 여기서 좀 방황하는 분들이 있는데요, 편하게 공식으로 얘기해 보면 이렇습니다. 

(NdotL 라이팅 결과 * 그림자 감쇠 * 텍스쳐)   +  (엠비언트 * 텍스쳐)

엠비언트에 텍스쳐 곱해진다는거 잊지 마세요. 그냥 넣으면 이상하게 탁함. 

자 이걸 바탕으로 최종 공식을 만들어 볼까요?

half4 frag(Varyings IN) : SV_Target
{
  //라이트 받아오기 
  float4 shadowCoord = TransformWorldToShadowCoord(IN.positionWS);
  Light light = GetMainLight(shadowCoord); 

  float3 lightDir = normalize(light.direction);
  float3 normalWS = normalize(IN.normalWS);

  //텍스쳐 
  half4 color = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, TRANSFORM_TEX(IN.uv, _BaseMap));

  //램버트 연산
  float NdotL = saturate(dot(lightDir,normalWS)); 

  //엠비언트 
  float3 Ambient = SampleSH(normalWS); 

  //최종 연산 
  float4 finalColor = 1;
  finalColor.rgb = (NdotL * light.shadowAttenuation * color.rgb) + (Ambient * color.rgb);
  finalColor.a = color.a;

  return finalColor; 
}

예에이 완성입니다!!!

뭔가 되게 쉽게 한 것처럼 보인다.. 

굽신굽신

풀 코드는 다음과 같습니다. 

Shader "URPCustomLitShader"
{
    Properties
    {
        _BaseMap("BaseMap",2D) = "white"{}
    }

    SubShader
    {
        Tags { "RenderType" = "Opaque" "RenderPipeline" = "UniversalPipeline" }

        Pass //포워드 패스입니다 
        {
            Name "ForwardLit" 
            Tags { "LightMode" = "UniversalForward" }

            HLSLPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #pragma multi_compile _ _MAIN_LIGHT_SHADOWS_CASCADE 
            #pragma multi_compile_fragment _ _SHADOWS_SOFT 

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

            struct Attributes
            {
                float4 positionOS   : POSITION;
                float2 uv           : TEXCOORD0;
                float3 normalOS     : NORMAL; 
            };

            struct Varyings
            {
                float4 positionHCS  : SV_POSITION;
                float2 uv           : TEXCOORD0;
                float3 positionWS   : TEXCOORD1;  
                float3 normalWS     : NORMAL;
            };

            TEXTURE2D(_BaseMap);
            SAMPLER(sampler_BaseMap);

            CBUFFER_START(UnityPerMaterial)
                float4 _BaseMap_ST;
            CBUFFER_END

            Varyings vert(Attributes IN)
            {
                Varyings OUT;
                OUT.positionHCS = TransformObjectToHClip(IN.positionOS.xyz);
                OUT.positionWS = TransformObjectToWorld(IN.positionOS.xyz);
                OUT.uv = IN.uv;
                OUT.normalWS = TransformObjectToWorld(IN.normalOS); 
                return OUT; 
            }

            half4 frag(Varyings IN) : SV_Target
            {
                //라이트 받아오기 
                float4 shadowCoord = TransformWorldToShadowCoord(IN.positionWS);
                Light light = GetMainLight(shadowCoord); 
                
                float3 lightDir = normalize(light.direction);
                float3 normalWS = normalize(IN.normalWS);
                
                //텍스쳐 
                half4 color = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, TRANSFORM_TEX(IN.uv, _BaseMap));
               
                //램버트 연산
                float NdotL = saturate(dot(lightDir,normalWS)); 

                //엠비언트 
                float3 Ambient = SampleSH(normalWS); 
                
                //최종 연산 
                float4 finalColor = 1;
                finalColor.rgb = (NdotL * light.shadowAttenuation * color.rgb) + (Ambient * color.rgb);
                finalColor.a = color.a;

                return finalColor; 
            }
            ENDHLSL
        }
        
        Pass //그림자 패스입니다.
        {
            Name "ShadowCaster"
            Tags{"LightMode" = "ShadowCaster"}  

            ZWrite On
            ZTest LEqual
            ColorMask 0
            Cull Front

            HLSLPROGRAM

            #pragma vertex ShadowPassVertex
            #pragma fragment ShadowPassFragment

            #include "Packages/com.unity.render-pipelines.universal/Shaders/SimpleLitInput.hlsl"
            #include "Packages/com.unity.render-pipelines.universal/Shaders/ShadowCasterPass.hlsl"
            ENDHLSL
        }

        Pass //깊이 패스입니다 .
        {
            Name "DepthOnly"
            Tags{"LightMode" = "DepthOnly"} 

            ZWrite On
            ColorMask 0
            Cull Front

            HLSLPROGRAM
            #pragma exclude_renderers gles gles3 glcore
            #pragma target 4.5

            #pragma vertex DepthOnlyVertex
            #pragma fragment DepthOnlyFragment

            #include "Packages/com.unity.render-pipelines.universal/Shaders/SimpleLitInput.hlsl"
            #include "Packages/com.unity.render-pipelines.universal/Shaders/DepthOnlyPass.hlsl"
            ENDHLSL
        }
    }
}

CustomLit2.shader
0.00MB

 

 

블로그 주인장에게 커피값을 후원할 수 있습니다! 

 

반응형

댓글