Ronja 님의 허락을 받고 번역한 튜토리얼입니다. 원문은 위 링크에서 확인하실 수 있습니다.
몇몇 부분은 생략·추가하였습니다.
의역과 오역이 넘쳐날 수 있으니 편하게 봐주시고 잘못된 부분은 알려주시면 감사하겠습니다!
목차
- 개요
- 색상 보간하기
- 텍스처 보간하기
- 텍스처에 기반한 보간
개요
화면에 그려지는 결과물에 하나 이상의 색상이 사용될 때가 있습니다. 두 색상을 섞는 간단한 방법은, 다른 파라미터를 통해 둘을 보간해주는 것입니다,
이번 튜토리얼에서는 텍스처만 지원하는 간단한 셰이더 를 기반으로 만들 예정이지만, 서피스 셰이더를 포함한 어떤 셰이더에서든 튜토리얼의 테크닉을 사용할 수 있습니다.
색상 보간하기
탐구해볼 첫 버전의 셰이더는 단순하게 값에 기반해 두 가지 기본 색상을 보간해줄 것입니다. 그렇기에 지금은 변수들을 UV 좌표나 텍스처와 연결해줄 필요는 없습니다. 대신 두 번째 색상 변수와, 메테리얼이 첫 번째 색상을 표시할지 두 번째 색상을 표시할지를 결정하는 간단한 값을 추가해줍니다. 이 값인 블렌딩 프로퍼티를 "Range" 로 선언해서 인스펙터 상에서 멋진 슬라이더로 값을 조작할 수 있도록 해줍니다.
// ···
// 인스펙터에서 조작할 수 있는 값들이 보여집니다.
Properties
{
_Color ("Color", Color) = (0, 0, 0, 1) // 베이스 색상
_SecondaryColor ("Secondary Color", Color) = (1,1,1,1) // 블렌드될 색상
_Blend ("Blend Value", Range(0,1)) = 0 // 값이 0일 때는 첫번째 색상, 1일 때는 두번째 색상
}
// ···
// 색상을 블렌드할 때 사용할 값
float _Blend;
// 블렌드될 색상들
fixed4 _Color;
fixed4 _SecondaryColor;
UV 좌표와 연결된 라인을 지우는 것 외에, 버텍스 셰이더는 건드리지 않아도 됩니다. 대신 프래그먼트 셰이더를 건드려볼 것입니다. 첫 번째 버전에서는 단순하게 블렌드 값에 기반해서 첫 번째 색상에 두 번째 색상을 더해줄 것입니다.
// 프래그먼트 셰이더
fixed4 frag(v2f i) : SV_TARGET{
fixed4 col = _Color + _SecondaryColor * _Blend;
return col;
}
블렌드 값을 조절했을 때 색이 변화하는 것을 볼 수 있지만, 오브젝트의 색상이 두번째 색으로 변하지는 않습니다. 두 번째 색상이 연산될 때에도, 첫 번째 색상이 남아있기 때문입니다. (한 장소에 다른 색상의 빛 두 개를 비추었을 때 보여지는 것과 유사합니다.)
이를 고치기 위해서 블렌드 값이 증가할수록 첫 번째 색상을 감소시킬 수 있습니다. 블렌드 값이 0 일 때는 두 번째 색상이 전혀 보이지 않고 첫 번째 색상으로만 보여지고, 블렌드 값이 1일 때엔 두 번째 색상만 보이게 하고 싶습니다. 이를 달성하기 위해선, 첫 번째 색상에 블렌드 값을 반전한 값 (1 - Blend Value) 을 곱해줍니다.
// 프래그먼트 셰이더
fixed4 frag(v2f i) : SV_TARGET{
fixed4 col = _Color * (1 - _Blend) + _SecondaryColor * _Blend;
return col;
}
이 과정은 선형 보간이라고도 하며, 이 연산을 수행하는 'lerp' 라는 HLSL 내장 함수가 있습니다. lerp() 함수는 보간할 시작 값, 보간할 끝 값, 보간 값을 매개변수로 가집니다.
// 프래그먼트 셰이더
fixed4 frag(v2f i) : SV_TARGET{
fixed4 col = lerp(_Color, _SecondaryColor, _Blend);
return col;
}
두 색상 사이를 보간해주는 완성된 셰이더는 아래와 같습니다.
Shader "Tutorial/009_Color_Blending/Plain"{
// 인스펙터에서 조작할 수 있는 값들이 보여집니다.
Properties{
_Color ("Color", Color) = (0, 0, 0, 1) // 베이스 색상
_SecondaryColor ("Secondary Color", Color) = (1,1,1,1) // 블렌드될 색상
_Blend ("Blend Value", Range(0,1)) = 0 // 값이 0일 때는 첫번째 색상, 1일 때는 두번째 색상
}
SubShader{
// 메테리얼은 완전히 불투명하고, 다른 불투명 지오메트리와 같은 타이밍에 렌더됩니다.
Tags{ "RenderType"="Opaque" "Queue"="Geometry"}
Pass{
CGPROGRAM
// 유용한 셰이더 기능들 포함
#include "UnityCG.cginc"
// 버텍스와 프래그먼트 셰이더 정의
#pragma vertex vert
#pragma fragment frag
// 색상을 블렌드 할 때 사용할 값
float _Blend;
// 블렌드될 색상들
fixed4 _Color;
fixed4 _SecondaryColor;
// 버텍스 셰이더에 전달되는 오브젝트 데이터
struct appdata{
float4 vertex : POSITION;
};
// 프래그먼트 셰이더에서 읽을 수 있는 프래그먼트를 생성하기 위해 사용되는 데이터
struct v2f{
float4 position : SV_POSITION;
};
// 버텍스 셰이더
v2f vert(appdata v){
v2f o;
// 렌더될 수 있도록 버텍스 좌표를 오브젝트 공간에서 클립 공간으로 변환
o.position = UnityObjectToClipPos(v.vertex);
return o;
}
// 프래그먼트 셰이더
fixed4 frag(v2f i) : SV_TARGET{
fixed4 col = lerp(_Color, _SecondaryColor, _Blend);
return col;
}
ENDCG
}
}
}
텍스처 보간하기
셰이더의 다음 버전에서는 텍스처에서 읽어온 색상 간의 보간을 포함할 것입니다. 이를 위해 우리는 색상 프로퍼티와 변수를 제거하고 대신 두 개의 텍스처에 데한 프로퍼티와 변수를 추가합니다. 또한 UV 좌표를 대한 변수를 다시 추가하지만, 텍스처 튜토리얼과 다르게 버텍스 셰이더에서 텍스처의 타일링과 오프셋을 적용하지 않을 것입니다. 여러 텍스처가 동일한 UV 좌표를 사용하고, 필요하지 않은 경우엔 모든 텍스처를 보간하고 싶지 않기 때문입니다.
// ···
// 인스펙터에서 조작할 수 있는 값들이 보여집니다.
Properties{
_MainTex ("Texture", 2D) = "white" {} // 베이스 텍스처
_SecondaryTex ("Secondary Texture", 2D) = "black" {} // 블렌드할 텍스처
_Blend ("Blend Value", Range(0,1)) = 0 // 값이 0일 때는 첫번째 색상, 1일 때는 두번째 색상
}
// ···
// 버텍스 셰이더에 전달되는 오브젝트 데이터
struct appdata{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
// 프래그먼트 셰이더에서 읽을 수 있는 프래그먼트를 생성하기 위해 사용되는 데이터
struct v2f{
float4 position : SV_POSITION;
float2 uv : TEXCOORD0;
};
// 버텍스 셰이더
v2f vert(appdata v){
v2f o;
// 렌더될 수 있도록 버텍스 좌표를 오브젝트 공간에서 클립 공간으로 변환
o.position = UnityObjectToClipPos(v.vertex);
o.uv = v.uv;
return o;
}
// ···
그런 다음 프래그먼트 셰이더에서, 익숙한 Transform Tex 라는 매크로를 사용해 두 텍스처에 각각 타일링과 오프셋을 적용할 수 있습니다. 다음엔 이 좌표들을 사용해서 텍스처를 읽습니다. 그런 후 텍스처에서 읽어온 색상을 사용해, 이전에 했던 것처럼 이들간의 보간을 수행할 수 있습니다.
// 프래그먼트 셰이더
fixed4 frag(v2f i) : SV_TARGET{
// 타일링과 오프셋을 포함한 UV 좌표 계산
float2 main_uv = TRANSFORM_TEX(i.uv, _MainTex);
float2 secondary_uv = TRANSFORM_TEX(i.uv, _SecondaryTex);
// 텍스처에서 색상 읽어오기
fixed4 main_color = tex2D(_MainTex, main_uv);
fixed4 secondary_color = tex2D(_SecondaryTex, secondary_uv);
// 색상 간 보간하기
fixed4 col = lerp(main_color, secondary_color, _Blend);
return col;
}
두 텍스처 사이에서 보간해주는 완성된 셰이더는 다음과 같습니다.
Shader "Tutorial/009_Color_Blending/Texture"{
// 인스펙터에서 조작할 수 있는 값들이 보여집니다.
Properties{
_MainTex ("Texture", 2D) = "white" {} // 베이스 텍스처
_SecondaryTex ("Secondary Texture", 2D) = "black" {} // 블렌드할 텍스처
_Blend ("Blend Value", Range(0,1)) = 0 // 값이 0일 때는 첫번째 색상, 1일 때는 두번째 색상
}
SubShader{
// 메테리얼은 완전히 불투명하고, 다른 불투명 지오메트리와 같은 타이밍에 렌더됩니다.
Tags{ "RenderType"="Opaque" "Queue"="Geometry"}
Pass{
CGPROGRAM
// 유용한 셰이더 기능들 포함
#include "UnityCG.cginc"
// 버텍스와 프래그먼트 셰이더 정의
#pragma vertex vert
#pragma fragment frag
// 색상을 블렌드할 때 사용할 값
float _Blend;
// 블렌드될 색상들
sampler2D _MainTex;
float4 _MainTex_ST;
sampler2D _SecondaryTex;
float4 _SecondaryTex_ST;
// 버텍스 셰이더에 전달되는 오브젝트 데이터
struct appdata{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
// 프래그먼트 셰이더에서 읽을 수 있는 프래그먼트를 생성하기 위해 사용되는 데이터
struct v2f{
float4 position : SV_POSITION;
float2 uv : TEXCOORD0;
};
// 버텍스 셰이더
v2f vert(appdata v){
v2f o;
// 렌더될 수 있도록 버텍스 좌표를 오브젝트 공간에서 클립 공간으로 변환
o.position = UnityObjectToClipPos(v.vertex);
o.uv = v.uv;
return o;
}
// 프래그먼트 셰이더
fixed4 frag(v2f i) : SV_TARGET{
// 타일링과 오프셋을 포함한 UV 좌표 계산
float2 main_uv = TRANSFORM_TEX(i.uv, _MainTex);
float2 secondary_uv = TRANSFORM_TEX(i.uv, _SecondaryTex);
// 텍스처에서 색상 읽어오기
fixed4 main_color = tex2D(_MainTex, main_uv);
fixed4 secondary_color = tex2D(_SecondaryTex, secondary_uv);
// 색상 간 보간하기
fixed4 col = lerp(main_color, secondary_color, _Blend);
return col;
}
ENDCG
}
}
}
텍스처에 기반한 보간
마지막으로 보여드릴 셰이더는, 하나의 텍스처간 블렌드를 위한 유니폼 변수를 사용하지 않고, 텍스처에서 블렌드 값을 가져오는 방식입니다.
이를 위해 블렌딩에 사용했던 변수와 프로퍼티를 삭제하고, 대신 또다른 텍스처를 추가합니다.
// ···
// 인스펙터에서 조작할 수 있는 값들이 보여집니다.
Properties{
_MainTex ("Texture", 2D) = "white" {} // 베이스 텍스처
_SecondaryTex ("Secondary Texture", 2D) = "black" {} // 블렌드할 텍스처
_BlendTex ("Blend Texture", 2D) = "grey" // 검정색은 첫번째 색상, 하얀색은 두번째 색상
}
// ···
// 색상을 블렌드할 때 사용될 텍스처
sampler2D _BlendTex;
float4 _BlendTex_ST;
// 블렌드될 색상들
sampler2D _MainTex;
float4 _MainTex_ST;
sampler2D _SecondaryTex;
float4 _SecondaryTex_ST;
// ···
그런 다음 해당 텍스처에 대한 변환된 UV 좌표를 생성해줍니다. 이를 통해 텍스처에서 색상값을 읽어옵니다. 이제 R, G, B, Alpha 의 전체 색상을 가지고 있지만, 0~1 의 단순한 스칼라 값을 원합니다. 색상을 float 값으로 변환하기 위해 텍스처가 흑백이라고 가정한 뒤 R 값을 그냥 사용합니다. 그리고 이 값을 이전에 했던 것처럼 두 텍스처 사이를 보간하는데에 사용하면 됩니다.
// 프래그먼트 셰이더
fixed4 frag(v2f i) : SV_TARGET{
// 타일링과 오프셋을 포함한 UV 좌표 계산
float2 main_uv = TRANSFORM_TEX(i.uv, _MainTex);
float2 secondary_uv = TRANSFORM_TEX(i.uv, _SecondaryTex);
float2 blend_uv = TRANSFORM_TEX(i.uv, _BlendTex);
// 텍스처에서 색상 읽어오기
fixed4 main_color = tex2D(_MainTex, main_uv);
fixed4 secondary_color = tex2D(_SecondaryTex, secondary_uv);
fixed4 blend_color = tex2D(_BlendTex, blend_uv);
// 블렌드 텍스처에서 R 값을 가져옵니다.
fixed blend_value = blend_color.r;
// 색상 간 보간하기
fixed4 col = lerp(main_color, secondary_color, blend_value);
return col;
}
텍스처를 기반으로 보간하는 완성된 셰이더는 다음과 같습니다.
Shader "Tutorial/009_Color_Blending/TextureBasedBlending"{
// 인스펙터에서 조작할 수 있는 값들이 보여집니다.
Properties{
_MainTex ("Texture", 2D) = "white" {} // 베이스 텍스처
_SecondaryTex ("Secondary Texture", 2D) = "black" {} // 블렌드할 텍스처
_BlendTex ("Blend Texture", 2D) = "grey" // 검정색은 첫번째 색상, 하얀색은 두번째 색상
}
SubShader{
// 메테리얼은 완전히 불투명하고, 다른 불투명 지오메트리와 같은 타이밍에 렌더됩니다.
Tags{ "RenderType"="Opaque" "Queue"="Geometry"}
Pass{
CGPROGRAM
// 유용한 셰이더 기능들 포함
#include "UnityCG.cginc"
// 버텍스와 프래그먼트 셰이더 정의
#pragma vertex vert
#pragma fragment frag
// 색상을 블렌드할 때 사용될 텍스처
sampler2D _BlendTex;
float4 _BlendTex_ST;
// 블렌드될 색상들
sampler2D _MainTex;
float4 _MainTex_ST;
sampler2D _SecondaryTex;
float4 _SecondaryTex_ST;
// 버텍스 셰이더에 전달되는 오브젝트 데이터
struct appdata{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
// 프래그먼트 셰이더에서 읽을 수 있는 프래그먼트를 생성하기 위해 사용되는 데이터
struct v2f{
float4 position : SV_POSITION;
float2 uv : TEXCOORD0;
};
// 버텍스 셰이더
v2f vert(appdata v){
v2f o;
// 렌더될 수 있도록 버텍스 좌표를 오브젝트 공간에서 클립 공간으로 변환
o.position = UnityObjectToClipPos(v.vertex);
o.uv = v.uv;
return o;
}
// 프래그먼트 셰이더
fixed4 frag(v2f i) : SV_TARGET{
// 타일링과 오프셋을 포함한 UV 좌표 계산
float2 main_uv = TRANSFORM_TEX(i.uv, _MainTex);
float2 secondary_uv = TRANSFORM_TEX(i.uv, _SecondaryTex);
float2 blend_uv = TRANSFORM_TEX(i.uv, _BlendTex);
// 텍스처에서 색상 읽어오기
fixed4 main_color = tex2D(_MainTex, main_uv);
fixed4 secondary_color = tex2D(_SecondaryTex, secondary_uv);
fixed4 blend_color = tex2D(_BlendTex, blend_uv);
// 블렌드 텍스처에서 R 값을 가져옵니다.
fixed blend_value = blend_color.r;
// 색상 간 보간하기
fixed4 col = lerp(main_color, secondary_color, blend_value);
return col;
}
ENDCG
}
}
}
이 튜토리얼이 셰이더에서 색상을 어떻게 다루는지와, 특히 보간에 대해서 이해하는데에 도움이 되었다면 좋겠습니다.
'Graphics > Ronja's Unity Shader tutorials' 카테고리의 다른 글
체크 무늬 만들기 (0) | 2024.08.19 |
---|---|
트라이플래너 매핑 (Triplanar Mapping) (0) | 2024.07.08 |
평면 매핑 (Planar Mapping) (0) | 2024.05.05 |
스프라이트 셰이더 (0) | 2024.03.03 |
기본 투명 셰이더 (0) | 2024.01.21 |