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

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

by 대마왕J 2021. 12. 23.

 

이번엔 책으로 따지면 흠 NdotL 이니까.. 301페이지쯤 되겠네요 

셰이더 그래프로  커스텀 라이트 Custom Light 를 만드는건 했었으니까, 이번엔 HLSL로 커스텀 라이트를 만들어 보겠습니다!! 

안내 
여기 나오는 내용은 유니티 쉐이더 스타트업에 나오는 예제를 최신 URP에 맞추어 예제를 번역한 내용입니다. 
때문에 이론적 내용이 상당히 간략하거나 불친절하며, 예제에 대한 설명도 축약되어 있기 때문에
책과 같이 보시는 것을 추천하는 바입니다.

뭐부터 하지..

뭐 일단 시작부터 해봐야지요?

당장 기본 코드부터 시작합니다. 

..마지막의 마지막이라는 느낌으로 또 올림 ㅋㅋㅋ

URPUnlitShaderBasic.shader
0.00MB

자 이 언릿 셰이더를 심플릿 셰이더 처럼 만들거예요 

여기서부터 시이작!

셰이더그래프랑 사실상 같으면서도 어쩔 수 없이 다른 구조로 만들거야요 홓홓홓 . 이건 그냥 전부 다 만드는 거니까!  

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

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

        Pass
        {
            HLSLPROGRAM
            #pragma vertex vert
            #pragma fragment frag

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

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

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

            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;
                return OUT;
            }

            half4 frag(Varyings IN) : SV_Target
            {
                half4 color = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, TRANSFORM_TEX(IN.uv, _BaseMap));
                return color;
            }
            ENDHLSL
        }
    }
}

자 일단 코드를 봅시다. 어디가 버텍스이고 어디가 픽셀인지 대충 감이 오죠? 

뭐여 안온다고 시방?

그럼 앞부터 다시 보시고 ...

 

노말 받아오기 

후후 일단 필요한게 NdotL 이죠

그럼.. 노말과 라이트 벡터가 있어야 겠네요?

노말부터 받아봅시다 그럼 . 

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

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

        Pass
        {
            HLSLPROGRAM
            #pragma vertex vert
            #pragma fragment frag

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

            struct Attributes
            {
                float4 positionOS   : POSITION;
                float2 uv           : TEXCOORD0;
                float3 normalOS       : NORMAL; //1. 노말을 받아옵니다. 
            };

            struct Varyings
            {
                float4 positionHCS  : SV_POSITION;
                float2 uv           : TEXCOORD0;
                float3 normalWS       : NORMAL; //3. 픽셀로 노말을 넘깁니다. 
            };

            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 = TransformObjectToWorld(IN.normalOS); //2.노말을 월드좌표계로 만들어서 픽셀로 넘깁니다. 
                return OUT;
            }

            half4 frag(Varyings IN) : SV_Target
            {
                half4 color = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, TRANSFORM_TEX(IN.uv, _BaseMap));
                return float4(IN.normalWS,1); //4. 잘 들어왔나 확인이나 해 봅시다 
            }
            ENDHLSL
        }
    }
}

자 위 코드를 보죠 

1. Attributes 에서 normal을 받았어요. 좌표는 오브젝트 스페이스. 

2. 그리고 버텍스 셰이더에서  OUT.normalWS = TransformObjectToWorld(IN.normalOS); 으로 오브젝트 좌표계를 월드 좌표계로 변환해 줬어요 그렇게 Varyings로 출력하는거죠

3. 그리고 Varyings 로 normal을 출력하죠. 좌표는 월드스페이스. 그래서 이름을 각각 normalOS, normaWS로 정했어요. 

이제 픽셀셰이더로 올 수 있겠죠 

그래서 픽셀셰이더에서 IN.normalWS를 출력해 보았습니다. 잘 나오는지 확인해 봐야 할 거 아냐!! 

잘 된다 

짜잔. 자 이렇게 노말은 됐고 이번엔 라이트 디렉션을 받아야 겠네요 

 

라이트 디렉션 받아오기 

좋아요 이번엔 라이트 디렉션을 받아봅시다. 

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

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

        Pass
        {
            HLSLPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
            //1. 라이트용 인클루드를 받아옵니다 
            #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 = TransformObjectToWorld(IN.normalOS);  
                return OUT;
            }

            half4 frag(Varyings IN) : SV_Target
            {
                //2.GetMainLight 함수로 라이트 구조체 값을 받아옵시다. 
                Light light = GetMainLight();
                
                half4 color = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, TRANSFORM_TEX(IN.uv, _BaseMap));
                return float4(light.direction,1); //3. 잘 들어왔나 확인이나 해 봅시다 
            }
            ENDHLSL
        }
    }
}

자 역시 위 코드를 보죠 

1. #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"  
로 저 Lighting.hlsl을 인클루드 시킵시다. 이제 저 코드는 이 안에 써 놓은거랑 마찬가지예요. 저 안에 라이팅 공식들이 와방 들어 있음. 사실 그냥 다 들어있는 느낌이예요 솔직히 저 안에 지금 하려는 라이팅도 다 함수로 되어 있는데 굳이 이렇게 해야 하나? 수준 ㅋㅋㅋ 

2. Light light = GetMainLight();
Lighting.hlsl 안에 인클루드로 들어가 있는 RealtimeLight.hlsl 파일 (....) 에 있는 라이팅 구조체 받아오는 함수를 실행시키죠. 이걸 실행하기 위해 위에 인클루드를 한거예요. 인클루드 없으면 이게 뭐냐고 유니티가 삐져서 분홍색 뱉음. 

3. 역시 잘 들어왔나 확인해 봅시다. 

잘 들어왔네요. 각도가 색상으로 나타나는 것을 볼 수 있습니다. 

이것도 좋아 

램버트 연산하기 

노말과 라이트 디렉션이 들어 왔으니 (모두 월드 좌표계) 이제 닷 연산으로 램버트 라이트 연산만 해주면 됩니다.

 

버텍스 라이팅을 할 게 아니라 픽셀라이팅을 할거니까 fragment만 봐도 좋겠죠 

half4 frag(Varyings IN) : SV_Target
{
  Light light = GetMainLight();
  
  float3 lightDir = normalize(light.direction); //1. 두 벡터를 노말라이트합시다 
  float3 normalWS = normalize(IN.normalWS); //1. 두 벡터를 노말라이트합시다 
  
  float NdotL = dot(lightDir, normalWS); //2. NdotL 처리합시다


  half4 color = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, TRANSFORM_TEX(IN.uv, _BaseMap));
  return NdotL; //3. 확인
}

이렇게 라이트와 노말을 dot 연산해줘서 확인부터 해 보겠습니다. 

1. 우선 벡터를 노말라이즈 해 줍시다. 음 .. 뭐 라이트 디렉션 같은건 길이가 1로 들어오니까 문제가 없어 보일수도 있고 노말도 그렇지만, 믿으면 안됩니다. 노말라이즈는 숨쉬듯이.

2. NdotL  처리합시다.  1에서 -1 까지 들어오니까 saturate 해주는것 잊지 말고요 (max 도 괜찮고)

3. 출력해서 확인해 봅시다. 

음 좋아요 일단 되는군요. 

다음에는 일단 dot연산을 saturate로 잘라주고, 

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)); //1. Saturate 처리로 0 ~ 1로 제한

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

Color와 곱해줍니다. 

그럼 결과가 똬뙇 

뭐가 안됐을까요?

뭐 많이 안됐죠 . ㅎㅎㅎㅎ 

일단 그림자가 없어요. 전 - 혀 없넹 

그리고 깊이값도 없어요. 어떻게 아냐고요? 저 격자무늬의 줄무늬를 보세요. 공은 있는걸 인식하고 격자무늬가 가려지는데, 동상은 안가려지잖아요 저거 깊이값이 없어서 그래요

그리고 엠비언트 칼라도 없네요? 

그리고 노말맵도 적용이 안되고 

 

헤에.. 뭐.. 된게 없네... ?

다음 시간에 그럼 이것들을 하나씩 하나씩 차근차근 해보죠 

 

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

 

반응형

댓글