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

(HLSL)아티스트를 위한 URP 셰이더 Shader #9 - LERP 함수의 등장

by 대마왕J 2021. 9. 10.

 

 lerp 함수는 개인적으로 가장 많이 쓰는 함수 중 하나라고 생각합니다.
또한 유용하고 쉬운만큼, 처음에 셰이더 함수를 배우기 좋은 함수라고 생각합니다. 
증거 있냐고요? 네 없습니다. 그럼 20000 

아 그냥 그런가보다 좀 해요

 

lerp (리니어 인터폴레이션: Linear Interpolation) 함수는 이런 구조로 되어 있습니다. 

lerp(A, B, T);

여기서 A 와 B의 단위는 같아야 합니다. 그리고 T의 단위는 float이어야 하고요. 

T 가 0~ 1 값의 범위를 가지고 있으면 A ~ B 의 값을 출력하지요. 


Linear Interpolation , 리니어 인터폴레이션 

 

일단 시작코드는 여기서 시작합시다. 텍스쳐 하나가 있는 코드예요.
흠 이정도면 이제 모든 셰이더를 만들때 시작형으로 둬도 무리 없을 코드네요 

이런거죠

Shader "Example/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 = TRANSFORM_TEX(IN.uv, _BaseMap);
                return OUT;
            }

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

저는 착하니까 코드도 다운로드 할 수 있게 드릴께요
아니 사실 도네 해주셔서 기분이가 좋아서 드림 ㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋ 여러분 보고 좀 배워요 

자 여러분 본받아 보세요

 

NewUnlitShader (1).shader
0.00MB

텍스쳐 두 개 입력받기 

자 이걸가지고 텍스쳐를 두 개 받을 수 있게 만들어 봅시다. 
이건 이제 안보고 해봐야죠. 하나 만들어 줬으면 또 하나는 그냥 카피만 하면 되잖아요 (과연?)
복습하는 샘 치고 한번 해보시지요.
출력은 지금처럼 그냥 첫 번째 텍스쳐만 일단 나오게 하고요. 

이건 해보시라고 일단 숨겨놓음. 안되시면 펼쳐보시고 하세요 사실 함정도 좀 있음. 

더보기
Shader "Example/URPUnlitShaderBasic"
{
    Properties
    {
        _BaseMap("BaseMap",2D) = "white"{}
        _BaseMap2("BaseMap2",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);
            TEXTURE2D(_BaseMap2);
            SAMPLER(sampler_BaseMap);
            SAMPLER(sampler_BaseMap2);

            CBUFFER_START(UnityPerMaterial)
                float4 _BaseMap_ST;
                float4 _BaseMap2_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 colorA = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, TRANSFORM_TEX(IN.uv, _BaseMap));
                half4 colorB = SAMPLE_TEXTURE2D(_BaseMap2, sampler_BaseMap2, TRANSFORM_TEX(IN.uv, _BaseMap2));
                //일단 첫 번째 텍스쳐만 출력해 봅니다.
                return colorA;
            }
            ENDHLSL
        }
    }
}

 

이렇게 해놓고 마지막을 
return colorA; 로 하면 이렇게 나오겠고 

 

return colorB; 로 하면 이렇게 나오겠지요 

 

 

엇, 이상한게 있죠?!??!   =====================================

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 colorA = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, TRANSFORM_TEX(IN.uv, _BaseMap));
                half4 colorB = SAMPLE_TEXTURE2D(_BaseMap2, sampler_BaseMap2, TRANSFORM_TEX(IN.uv, _BaseMap2));
                return colorA ;
            }

원래 코드에서는 버텍스 셰이더에서 TRANSFORM_TEX 함수로 ST 연산을 해주는 거였어요. 베이스맵의... 
                OUT.uv = TRANSFORM_TEX(IN.uv, _BaseMap);
그래서 이걸 픽셀셰이더에서 그대로 받아서  
                half4 color = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, IN.uv);
사용 해주는 방식이었어요. 

텍스쳐가 하나라면 아무 문제 없는 방법이예요. 오히려 버텍스 셰이더에서 연산을 하면 조금 가볍기까지 하죠. 

그런데 이게 텍스쳐가 두 개가 되면 골때리게 된단 말이죠. 

버텍스 셰이더에서 
OUT.uv = TRANSFORM_TEX(IN.uv, _BaseMap);
를 해버리면, 

픽셀셰이더에서  텍스쳐가 두 개일 때 , 두 번째 텍스쳐도 uv 자리에 IN.uv 를 쓴다면... 어..?  
 half4 colorA = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, IN.uv);
 half4 colorB = SAMPLE_TEXTURE2D(_BaseMap2, sampler_BaseMap2, IN.uv);

어... 이렇게 해버리면 두 번째 텍스쳐도 첫 번째 텍스쳐의  ST를 따라가게 돼요. 
버텍스 셰이더에서 _BaseMap의 ST 연산을 해 온걸 둘다 써버리게 되니까요 ... 
첫 번째 텍스쳐의 타일링을 조절하면 두 번째 텍스쳐까지 같이 움직이게 된다는 겁니다. 

이럼 곤란하죠. 

그래서 이렇게 바꿔줘야 합니다. 

버텍스 셰이더에서는 픽셀셰이더에 그냥 UV만 넘겨줘요 암 짓도 안해요. 
 OUT.uv = IN.uv;

그리고 픽셀셰이더에서 TRANSFORM_TEX 연산을 각각 텍스쳐마다 해 주는 거예요. 
버텍스 셰이더보다 살짝 무거워지겠지만 뭐 ... 어쩔 수 없죠 

 half4 colorA = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, TRANSFORM_TEX(IN.uv, _BaseMap));
 half4 colorB = SAMPLE_TEXTURE2D(_BaseMap2, sampler_BaseMap2, TRANSFORM_TEX(IN.uv, _BaseMap2);

즉 버텍스셰이더에서 하던 TRANSFORM_TEX  함수연산을 픽셀셰이더에서 해야 한다.. 그 말씀인거죠. 

대충 이런 식으로

 

Lerp 사용하기 

자 일단 텍스쳐 두 개를 입력받게까지는 해 봤습니다. 이제 픽셀셰이더에서 lerp를 이용해서 출력해보죠. 
finalColor라고 변수를 하나 만들어 줬는데요. 
이렇게 해 버릇하면 편합니다 (...)

half4 frag(Varyings IN) : SV_Target
{
    half4 colorA = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, IN.uv);
    half4 colorB = SAMPLE_TEXTURE2D(_BaseMap2, sampler_BaseMap2, IN.uv);

    //finalColor 변수를 만들어서 , 두 텍스쳐를 lerp 한 값을 넣는다. 
    // 맨 뒤 인자가 0일경우 colorA 가 나오고, 1일 경우 colorB가 나오고 0.5 인 경우 섞인다. 
    half4 finalColor;
    finalColor = lerp(colorA, colorB, 0);

    return finalColor;
}

자 이제 저장해서 결과를 본 후에, lerp의 마지막 인자 숫자를 바꾸면 어떻게 변하는지 확인해 봅시다. 

결과가 잘 나왔다면, 이제 외부에서 float입력값을 0~ 1까지 받아서 lerp를 제어해 보도록 합니다. 

요렇게요.

이것도 해 봤던거죠? 해 보시고 아래랑 비교해 보세요 

더보기

 

Shader "Example/URPUnlitShaderBasic"
{
    Properties
    {
        _BaseMap("BaseMap",2D) = "white"{}
        _BaseMap2("BaseMap2",2D) = "white"{}
        //Range로 0~1로 제한합시다. 
        _LerpControl("LerpControl", Range(0,1)) = 0
    }

    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);
            TEXTURE2D(_BaseMap2);
            SAMPLER(sampler_BaseMap);
            SAMPLER(sampler_BaseMap2);

            CBUFFER_START(UnityPerMaterial)
                float4 _BaseMap_ST;
                float4 _BaseMap2_ST;
                //변수는 C 버퍼 안에서 받는게 국률이지
                float _LerpControl;
            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 colorA = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, TRANSFORM_TEX(IN.uv, _BaseMap));
                half4 colorB = SAMPLE_TEXTURE2D(_BaseMap2, sampler_BaseMap2, TRANSFORM_TEX(IN.uv, _BaseMap2));
                
                half4 finalColor;
                finalColor = lerp(colorA, colorB, _LerpControl);//받은 변수로 제어합시다

                return finalColor;
            }
            ENDHLSL
        }
    }
}

 

 

 

 

 


텍스쳐 채널을 이용한 리니어 인터폴레이션

자 그럼 여기서 위에서 해봤던 Lerp에 대한 내용을 정리해 봅시다. 

Lerp란 첫 번째 요소 A 와  두 번째 요소 B가 T 값에 의해 블렌딩 되는 함수였죠 
T가 0이면 A가 나오고, T가 1이면 B가 나오는 방식이었습니다. 

흠.. .여기서 좀 더 생각해 보면, T 가 0~ 1 까지니까 
이걸 텍스쳐의 각 채널로 이용해도 작동되지 않을까요?

 

텍스쳐 준비하기 

귀찮지만 이번에는 그려야겠군여 에혀


다운로드도 준비할께요 . 여기에 . 귀찮은데 수상할 정도로 친절하다

Lerp.tga
1.00MB

 

이 텍스쳐는 이렇게 생긴 놈입니다. RGBA 각각 채널마다 다른 흑백 이미지가 들어가 있죠. 

칼라로 보면 이렇게 보이겠지만 이건 뭐 칼라가 중요한 이미지가 아니죠

자 이 텍스쳐를 엔진에 집어넣고, 셰이더에서 부를 수 있게 해 봅시다. 
이제 텍스쳐만 세 장 받는 코드가 되겠네요 
아래처럼 만들어 보세요 일단 출력은 첫 번째 텍스쳐만 하죠. 

더보기
Shader "Example/URPUnlitShaderBasic"
{
    Properties
    {
        _BaseMap("BaseMap",2D) = "white"{}
        _BaseMap2("BaseMap2",2D) = "white"{}
        //텍스쳐 추가입니다. 이름 잘 지으시고요 
        _LerpControlTex("LerpControlTexure",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);
            TEXTURE2D(_BaseMap2);
            TEXTURE2D(_LerpControlTex);
            SAMPLER(sampler_BaseMap);
            SAMPLER(sampler_BaseMap2);
            SAMPLER(sampler_LerpControlTex);

            CBUFFER_START(UnityPerMaterial)
                float4 _BaseMap_ST;
                float4 _BaseMap2_ST;
                //ST도 잊지말고요 
                float4 _LerpControlTex_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 colorA = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, TRANSFORM_TEX(IN.uv, _BaseMap));
                half4 colorB = SAMPLE_TEXTURE2D(_BaseMap2, sampler_BaseMap2, TRANSFORM_TEX(IN.uv, _BaseMap2));
                half4 LerpControlTex = SAMPLE_TEXTURE2D(_LerpControlTex, sampler_LerpControlTex, TRANSFORM_TEX(IN.uv, _LerpControlTex));                

                return colorA;
            }
            ENDHLSL
        }
    }
}

 

 

자 그럼 텍스쳐는 준비가 되었습니다.

 

텍스쳐 채널을 이용한 lerp

텍스쳐 채널을 이용한 lerp는 쉽습니다. 그냥 lerp 의 T에다가 rgba 채널을 하나씩 넣으면 돼요. 
텍스쳐의 각각의 채널은 0~1로 이루어진 픽셀덩어리이므로, 0인 부분은 A가 나오고 1인 부분은 B가 나오는것을 그냥 생각하면 됩니다. 

 half4 frag(Varyings IN) : SV_Target
 {
   half4 colorA = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, TRANSFORM_TEX(IN.uv, _BaseMap));
   half4 colorB = SAMPLE_TEXTURE2D(_BaseMap2, sampler_BaseMap2, TRANSFORM_TEX(IN.uv, _BaseMap2));
   half4 LerpControlTex = SAMPLE_TEXTURE2D(_LerpControlTex, sampler_LerpControlTex, TRANSFORM_TEX(IN.uv, _LerpControlTex));

  half4 finalColor;
  finalColor = lerp ( colorA, colorB, LerpControlTex.r); //텍스쳐의 r채널

  return finalColor;
}

이렇게 각 채널별로 바꿔보고 저장해 보면 결과를 볼 수 있습니다. 

이런건 잘 이용하면 벽에다가 그림을 붙인다던가.. 벽에서 흘러내리는 구정물 같은것을 유지할 때도 좋습니다. 

뿐만 아니라 ST를 독립시켰으므로, 각 텍스쳐는 서로 타일링이 다르도록 할 수도 있고 각각 위치를 옮길 수도 있죠 .

이것을 이용하여 디테일 맵을 만들어 주기도 합니다. 

 

혹은 아예 알파가 있는 그림이 준비되었다면 그 그림의 알파 채널을 lerp의 T값으로 사용할 수도 있답니다.

 

half4 frag(Varyings IN) : SV_Target
{
  half4 colorA = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, TRANSFORM_TEX(IN.uv, _BaseMap));
  half4 colorB = SAMPLE_TEXTURE2D(_BaseMap2, sampler_BaseMap2, TRANSFORM_TEX(IN.uv, _BaseMap2));
  //half4 LerpControlTex = SAMPLE_TEXTURE2D(_LerpControlTex, sampler_LerpControlTex, TRANSFORM_TEX(IN.uv, _LerpControlTex));

  half4 finalColor;
  finalColor = lerp ( colorA, colorB, colorB.a);

  return finalColor;
}

URP 기본 예제에 들어 있는 저 텍스쳐가 알파 채널을 가지고 있거든요. 현재 칼라 B가 가지고 있는 알파 채널을 lerp의 마지막 인자값으로 사용한 모습입니다. 


퀴즈

텍스쳐 채널을 통해 A ~ B가 섞이게 되었습니다. 
그러면 그 상태에서, B가 완전히 사라진다던가 A가 완전히 사라지게 조절자를 만들어서 해 보세요 

요렇게요

 

정답은 아래 있으니 한 번 고민해 보세요 

더보기
Shader "Example/URPUnlitShaderBasic"
{
    Properties
    {
        _BaseMap("BaseMap",2D) = "white"{}
        _BaseMap2("BaseMap2",2D) = "white"{}
        _LerpControlTex("LerpControlTexure",2D) = "white"{}
        //사라지기
        _Fade("사라지기", Range(-1,1)) = 0

    }

    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);
            TEXTURE2D(_BaseMap2);
            TEXTURE2D(_LerpControlTex);
            SAMPLER(sampler_BaseMap);
            SAMPLER(sampler_BaseMap2);
            SAMPLER(sampler_LerpControlTex);

            CBUFFER_START(UnityPerMaterial)
                float4 _BaseMap_ST;
                float4 _BaseMap2_ST;
                float4 _LerpControlTex_ST;
                float _Fade; //사라지기
            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 colorA = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, TRANSFORM_TEX(IN.uv, _BaseMap));
                half4 colorB = SAMPLE_TEXTURE2D(_BaseMap2, sampler_BaseMap2, TRANSFORM_TEX(IN.uv, _BaseMap2));
                half4 LerpControlTex = SAMPLE_TEXTURE2D(_LerpControlTex, sampler_LerpControlTex, TRANSFORM_TEX(IN.uv, _LerpControlTex));
                
                half4 finalColor;
                finalColor = lerp ( colorA, colorB, saturate(LerpControlTex.r +_Fade) ); //더해주고 saturate로 0과 1 사이를 못 벗어나게 강제로 만든다

                return finalColor;
            }
            ENDHLSL
        }
    }
}

자 오늘 마지막 코드의 다운로드는 여기입니다. 

HLSLShaderStudy.shader
0.00MB

 

 

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

 

반응형

댓글