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
}
}
}
저는 착하니까 코드도 다운로드 할 수 있게 드릴께요
아니 사실 도네 해주셔서 기분이가 좋아서 드림 ㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋ 여러분 보고 좀 배워요
텍스쳐 두 개 입력받기
자 이걸가지고 텍스쳐를 두 개 받을 수 있게 만들어 봅시다.
이건 이제 안보고 해봐야죠. 하나 만들어 줬으면 또 하나는 그냥 카피만 하면 되잖아요 (과연?)
복습하는 샘 치고 한번 해보시지요.
출력은 지금처럼 그냥 첫 번째 텍스쳐만 일단 나오게 하고요.
이건 해보시라고 일단 숨겨놓음. 안되시면 펼쳐보시고 하세요 사실 함정도 좀 있음.
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 까지니까
이걸 텍스쳐의 각 채널로 이용해도 작동되지 않을까요?
텍스쳐 준비하기
귀찮지만 이번에는 그려야겠군여 에혀
다운로드도 준비할께요 . 여기에 . 귀찮은데 수상할 정도로 친절하다
이 텍스쳐는 이렇게 생긴 놈입니다. 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
}
}
}
자 오늘 마지막 코드의 다운로드는 여기입니다.
블로그 주인장에게 커피값을 후원할 수 있습니다!
'튜터리얼_스터디' 카테고리의 다른 글
(셰이더그래프)아티스트를 위한 URP 셰이더 Shader #10 - UV 를 이용한 불 만들기 실습 (11) | 2021.09.27 |
---|---|
(셰이더그래프)아티스트를 위한 URP 셰이더 Shader #10 - UV제어 (0) | 2021.09.11 |
(셰이더그래프)아티스트를 위한 URP 셰이더 Shader #9 - LERP 함수의 등장 (0) | 2021.09.05 |
(HLSL)아티스트를 위한 URP 셰이더 Shader #8 - 텍스쳐 출력 / 조작하기 (9) | 2021.09.03 |
(셰이더그래프)아티스트를 위한 URP 셰이더 Shader #8 - 텍스쳐 출력 / 조작하기 (2) | 2021.08.31 |
댓글