Unity Shader - Texture의 UV 좌표에 대응하는 Pixel 좌표
간단하게 예를 들어서, 0 ~ 1 사이로 정규화되어 있는 UV 좌표계에서 0.1에 해당하는 texture의 pixel(x,y) 위치를 알고 싶다는 것입니다. 이것은 UV 좌표계의 의미를 알면 유추해 낼 수 있습니다.
가령, 가로 1024 * 세로 768 이미지의 texture를 (0,0) ~ (1,1) UV 좌표로 매핑한 경우 다음과 같은 의미를 갖게 됩니다.
u pixel
0 -> 0
1 -> 1024
v pixel
0 -> 0
1 -> 768
따라서, 다음과 같은 비율로 알아낼 수 있습니다.
u:x = 1:1024
v:y = 1:768
만약 그중에 (0.1, 0.7) uv 좌표 값을 가지고 있다면 이것을 pixel 위치로 환산하면 다음과 같이 계산할 수 있습니다.
0.1:x = 1:1024
x = 1024 * 0.1 = 102.4
= width of texture * u
0.7:y = 1:768
y = 768 * 0.7 = 537.6
= height of texture * v
uv(0.1, 0.7) == xy(102.4, 537.6) ≈ (102, 538)
실제로 그런지 Unity에서 지구본을 texture로 사용했던 예제를 보겠습니다.
Unity로 실습하는 Shader
; https://www.sysnet.pe.kr/2/0/11607
Shader "My/basicShader"
{
Properties
{
_MainTex("Texture", 2D) = "white" {}
}
SubShader
{
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
};
v2f vert(appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = v.uv;
return o;
}
sampler2D _MainTex;
fixed4 frag(v2f i) : SV_Target
{
fixed4 col = tex2D(_MainTex, i.uv);
return col;
}
ENDCG
}
}
}
그러니까, 위의 tex2D 함수는 2048 * 1024 크기의 지구 이미지를 texture로 사용했을 때, uv 좌표에 해당하는 texture의 컬러를 구해주고 있는 것입니다.
fixed4 frag(v2f i) : SV_Target
{
fixed4 col = tex2D(_MainTex, i.uv);
return col;
}
만약, 현재의 i.uv가 가리키고 있는 좌표보다 u 값으로 +10 pixel에 해당하는 컬러 값을 사용하고 싶다면 다음과 같이 하면 됩니다.
fixed4 frag(v2f i) : SV_Target
{
float uPerX = 1.0 / 2048; // 1 / width
float vPerY = 1.0 / 1024; // 1 / height
float uOffset = 10 * uPerX;
float vOffset = 0 * vPerY;
float2 nextUVOffset = float2(uOffset, vOffset);
fixed4 col = tex2D(_MainTex, i.uv + nextUVOffset);
return col;
}
저렇게 하면, 지구본이 +10 픽셀만큼 회전한 것처럼 보입니다. 또는, u 값으로 +2048 pixel을 준다면 어떻게 될까요?
float uOffset = 2048 * xPerU;
결국 제자리로 오기 때문에 화면에는 아무런 변화가 없습니다.
그런데, shader 코드에 2048, 1024이라고 하드 코딩을 하는 것이 좀 그렇군요. ^^ 이것을 없애려면 Properties 영역으로 옮겨 변수 처리를 하면 됩니다. 물론 그래도 되지만, Unity에서는 "_TexelSize"라는 접미사를 붙이면 해당 텍스처의 width, height를 담고 있는 값을 알아서 전달해 줍니다.
예를 들어, 위의 코드에서는 텍스처 변수 명이 "_MainTex"였으므로 다음과 같이 선언해 주면 됩니다.
float2 _MainTex_TexelSize;
그리고 그 변수의 값은 각각 다음과 같이 설정이 됩니다.
Accessing shader properties in Cg/HLSL
; https://docs.unity3d.com/Manual/SL-PropertiesInPrograms.html
x contains 1.0/width
y contains 1.0/height
z contains width
w contains height
결국 이를 반영하면 다음과 같이 하드 코딩 없이 작성할 수 있습니다.
fixed4 frag(v2f i) : SV_Target
{
float uPerX = _MainTex_TexelSize.x;
float vPerY = _MainTex_TexelSize.y;
float uOffset = 10 * uPerX; // x축으로 +10 pixel 위치
float vOffset = 10 * vPerY; // y축으로 +10 pixel 위치
float2 nextUVOffset = float2(uOffset, vOffset);
fixed4 col = tex2D(_MainTex, i.uv + nextUVOffset);
return col;
}
float2 UVtoXY(float2 uv, float2 texelSize)
{
return float2(uv.x / texelSize.x, uv.y / texelSize.y);
}
float2 XYtoUV(float2 pos, float2 texelSize)
{
return float2(pos.x * texelSize.x, pos.y * texelSize.y);
}
다음의 링크를 보면,
D3D이용 2D출력시 마법의 숫자 -0.5 에 대하여
; http://blog.daum.net/gamza-net/16
실수 보정을 하는데 아마 이 때문인지 다음의 답글을 보면,
How to get precise pixel values form a Texture2D using uv coordinates.
; https://answers.unity.com/questions/1106031/how-to-get-precise-pixel-values-for-a-texture2d-us.html
(0.5를 빼지 않고) 더하는 것이 나옵니다.
u = x / width + 0.5 / width;
v = y / height + 0.5 / height;
pixel shader에서 저 작업이 필요한지는... 혹시 아시는 분은 덧글 부탁드립니다. ^^
[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]