이전 포스팅 요약

이전 포스팅에서는 Shader의 정의부터 시작하여 렌더링 파이프라인에서 버텍스 쉐이더와 픽셀쉐이더가 무슨 역할을

하는지 설명하고, 현재 사용되는 쉐이더들을 소개하고 이 블로그에서는 Unity Surface Shader를 이용하여 Shader에 대해 포스팅 한다고 했다. 그리고 Unity 상에서 Surface Shader 만드는 법, float를 이용한 기본 문법과 쉐이더 사용 법에 대해 포스팅을 했다. 

 

 

Unity Surface Shader 기초 1강

이번 포스팅에서는 Surface Shader를 만들었을때 필요한 코드를 제외한 백지코드로 만들고 Surface Shader에서

텍스처를 이용하여 기본 사용법, 이론에 대해 포스팅을 한다.

 

 

- Surface Shader 제작후 백지 코드 상태로 만들기 -

Shader "Custom/Suf_Test5"
{
    Properties
    {
        _Color ("Color", Color) = (1,1,1,1)
        _MainTex ("Albedo (RGB)", 2D) = "white" {}
        _Glossiness ("Smoothness", Range(0,1)) = 0.5
        _Metallic ("Metallic", Range(0,1)) = 0.0
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 200

        CGPROGRAM
        // Physically based Standard lighting model, and enable shadows on all light types
        #pragma surface surf Standard fullforwardshadows

        // Use shader model 3.0 target, to get nicer looking lighting
        #pragma target 3.0

        sampler2D _MainTex;

        struct Input
        {
            float2 uv_MainTex;
        };

        half _Glossiness;
        half _Metallic;
        fixed4 _Color;

        // Add instancing support for this shader. You need to check 'Enable Instancing' on materials that use the shader.
        // See https://docs.unity3d.com/Manual/GPUInstancing.html for more information about instancing.
        // #pragma instancing_options assumeuniformscaling
        UNITY_INSTANCING_BUFFER_START(Props)
            // put more per-instance properties here
        UNITY_INSTANCING_BUFFER_END(Props)

        void surf (Input IN, inout SurfaceOutputStandard o)
        {
            // Albedo comes from a texture tinted by color
            fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
            o.Albedo = c.rgb;
            // Metallic and smoothness come from slider variables
            o.Metallic = _Metallic;
            o.Smoothness = _Glossiness;
            o.Alpha = c.a;
        }
        ENDCG
    }
    FallBack "Diffuse"
}

위 코드는 Unity엔진에서 Surface Shader 제작시 기본으로 생성되는 코드이다. 

코드를 간단 요약하면 위 사진과 같이 Color, Texture2D, Smoothness, Metallic 인터페이스를 받아서 

텍스처를 설정하고 색조정이 가능하고 Smoothness, Metallic 이 두 수치를 조정 할수 있는 쉐이더이다.

 

이제 이 코드를 백지코드로 만들고 텍스처만 받아서 출력하는 쉐이더로 만들 것이다.

Shader "Custom/Suf_Test5"
{
    Properties
    {
	   _MainTex("Albedo (RGB)", 2D) = "white" {}
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
     
        CGPROGRAM
        
        #pragma surface surf Standard 

		sampler2D _MainTex;

        struct Input
        {
            float2 uv_MainTex;
        };

        void surf (Input IN, inout SurfaceOutputStandard o)
        {
			float4 c = tex2D (_MainTex, IN.uv_MainTex);
            o.Albedo = c.rgb;
        }
        ENDCG
    }
    FallBack "Diffuse"
}

위 코드가 단순히 2D 텍스처를 받아서 SurfaceOutputStandard에 Albedo에 입력하여 텍스처를 출력하는 쉐이더이다.

위 코드를 순차적으로 분석하여 텍스처를 출력하는 쉐이더에 대해서 설명하겠다.

 

- 텍스쳐 출력 쉐이더 설명 -

본 쉐이더 설명에 앞서 3D 오브젝트와 UV의 간단한 이해가 필요 하다.

위 사진 처럼 버텍스 데이터로 구성된 3D모델의 표면을 전개도 처럼 펼쳐서 평면으로 표현하는 것이 UV Map이고

또한 UV 좌표라고 표현되는 이유는 XYZ로 위치를 표현하듯 알파뱃을 잡아서 위치를 표현하기 위해 

UVW XYZ으로 표현하여 UV 좌표라고 표현된다. 

그래서 앞서 설명하려 했던 오브젝트에 텍스처를 씌워 표현하는 쉐이더에서 우리가 알아야 할 것은 아래와 같다.

   1. 어떤 텍스처(Sampling)을 입힐 것이냐?

   2. 어떤 좌표(UV좌표)에 입힐 것이냐?

 

 

1. 2D텍스처 인터페이스, 변수 입력 받기

    Properties
    {
          _MainTex("Albedo (RGB)", 2D) = "white" {} // 1 
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        
        CGPROGRAM
        #pragma surface surf Standard 

        sampler2D _MainTex;         // 2

        struct Input
        {
            float2 uv_MainTex;         // 3
        }; 


        void surf (Input IN, inout SurfaceOutputStandard o)
        {
            float4 c = tex2D (_MainTex, IN.uv_MainTex);  // 4
            o.Albedo = c.rgb;
        }

 

먼저 2D 형식의 _MainTex이름으로 인터페이스 변수를 만들고

아래에 sampler2D _MainTex;라고 인터페이스와 쉐이더 코드가 연동되는 변수를 만들었다.

인터페이스에서 2D라고 형식의 변수는 쉐이더에서는 sampler2D라고 한다

 

2. 2D텍스처 인터페이스, 변수 입력 받기

        struct Input 
        { 
            float2 uv_MainTex;       
        }; 

 

다음 중요한 부분 중 첫번째인 struct Input 부분이다.

Unity 공식 문서에서는 이것을 Surface Shader Input Struct라고 부르며 

일반 적으로 위 코드와 같이 쉐이더가 필요로하는 텍스처 UV좌표를 가져오는데 쓰인다.

변수 이름을 만드는데에도 규칙이 있다.

   uv텍스처 변수명 

앞서 sampler2D _MainTex; 변수를 만들었고 해당 텍스쳐 이름 앞에 "uv"가 붙는 형식으로 변수 명을 만들어야한다.

(만약 두 번째 텍스처 좌표 세트를 사용하려면 "uv2"라고 이름이 시작이 되어야 한다.)

 

그외 Input Struct에는 다른 Input 변수들이 존재한다.

Unity 메뉴얼

 void surf (Input IN, inout SurfaceOutputStandard o) 

위 struct Input에 있는 변수는 위 void surf함수의 Input IN 부분과 연동되어 

해당 함수내에서 IN. 으로 Input Sturct 변수로 접근하여 사용 할 수 있다.

 

3. tex2D 함수를 이용하여 텍스처 출력하기

        void surf (Input IN, inout SurfaceOutputStandard o) 
        { 
            float4 c = tex2D (_MainTex, IN.uv_MainTex);  
            o.Albedo = c.rgb; 
        }

 

 쉐이더 설명에 앞서 함수에 대해 간단히 설명하자면

함수 = 기능이다.

 

위 쉐이더에서는 tex2D라는 함수를 사용했고, 

이 뜻은 tex2D라는 이름의 어떤 기능을 사용한다는 의미이다.

 

 

tex2D는 해당 함수에 sampler2D변수와 해당 텍스처의 Input.UV변수를 넣으면 

이에 해당하는 float4(RGBA) 값을 얻을 수 있다. 

 

이것으로 기본 Surface 쉐이더에서 간단하게 2D 텍스처 1개만 받아서 출력하는 쉐이더로 수정했다.

이제 이 포스팅에서는 이 쉐이더를 기반으로 텍스처를 응용하는법,  간단 UV응용하는 주제로 설명을 이어나가 겠다.

 

 

- 텍스처 출력 쉐이더 응용하기 -

이번 주제에서는 이전에 텍스처출력하는 쉐이더에서 밝기 조정, 흑백만들기 등 쉐이더 코드를 조작하여 응용하겠다.

 

1. 텍스처 밝기 조정하는 기능 만들기

Shader "Custom/Suf_Test5"
{
    Properties
    {
	   _MainTex("Albedo (RGB)", 2D) = "white" {}
	   _Brightness("Brightness", Range(-1, 1)) = 0.0
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
     
        CGPROGRAM
        
        #pragma surface surf Standard 

		sampler2D _MainTex;
		float _Brightness;

        struct Input
        {
            float2 uv_MainTex;
        };

        void surf (Input IN, inout SurfaceOutputStandard o)
        {
			float4 c = tex2D (_MainTex, IN.uv_MainTex);
            o.Albedo = c.rgb + _Brightness;
        }
        ENDCG
    }
    FallBack "Diffuse"
}

위 코드로 인해 텍스처를 출력하고 Range인터페이스로 값을 조절하여 밝기를 조절하는 쉐이더를 만들었다.

간단한 분석을 해보겠다.

    Properties
    {
        _MainTex("Albedo (RGB)", 2D) = "white" {}
        _Brightness("Brightness", Range(-1, 1)) = 0.0
    }

먼저 Properties 인터페이스에 Range자료형을 -1~1 범위 값으로 변수를 만들었다.

 

     #pragma surface surf Standard 

     sampler2D _MainTex;
     float _Brightness;

다음 실제 쉐이더내에서 변수를 사용해야 하기에 인터페이스와 같은 이름으로 _Brightness라는 변수를 만들었다.

 

        void surf (Input IN, inout SurfaceOutputStandard o)
        {
            float4 c = tex2D (_MainTex, IN.uv_MainTex);
            o.Albedo = c.rgb + _Brightness;
        }

다음 실제 적으로 텍스처의 밝기를 조절하는 코드이다.

 

원리는 간단하다 숫자 값이 1에 가까울 수록 흰색이 되고 0에 가까울 수록 검정색이 되니 

-1~1까지 조정이 가능한 float 변수를 만들어서 c.rgb라는 float3 텍스처 변수에 +계산을 한것이다.

 

 

2. 흑백 텍스처 만들기

Shader "Custom/Suf_Test5"
{
    Properties
    {
	   _MainTex("Albedo (RGB)", 2D) = "white" {}
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
     
        CGPROGRAM
        
        #pragma surface surf Standard 

		sampler2D _MainTex;

        struct Input
        {
            float2 uv_MainTex;
        };

        void surf (Input IN, inout SurfaceOutputStandard o)
        {
			float4 c = tex2D (_MainTex, IN.uv_MainTex);
			o.Albedo = (c.r + c.g + c.b) / 3.0f;
        }
        ENDCG
    }
    FallBack "Diffuse"
}

위 코드로 텍스처를 흑백으로 만들었다. 핵심 코드는 아래와 같다. 

o.Albedo = (c.r + c.g + c.b) / 3.0f;

위 식의 원리는 3개의 채널의 평균값으로 색을 출력하는 것이다.

사실 가장 간단한 방법으로 흑백을 만들었고 이 방법 외에 다른 방식의 흑백 이미지 만드는 방법이 존재한다.

 

다른 공식으로 아래와 같은 공식이 있다.

YCrCb
Result = R * 0.2126 + G * 0.7152 + B * 0.0722

YPrPb
Result = R * 0.299 + G * 0.587  + B * 0.114

 

하지만 거의 차이가 많이 나지 않는다.

 

3. Lerp(Linear Interpolation) 함수

유니티에서 사용하는 Shader언어에는 다양한 수학함수들이 존재한다. 대부분의 Shader언어에서 공통되는 사항이고 

이 수학 함수를 이용하여 숫자(칼라) 값을 조정하여 다양한 쉐이더를 만들 수 있다.

그 중에서 Lerp함수는 가장 기본적이면서 많이 사용되는 함수 중 하나이다.

 

Linear Interpolation은 끝점의 값이 주어졌을 때 그 사이에 위치한 값을 추정하기 위하여 직선 거리에 따라 선형적으로 계산하는 방법이다.

이 함수는 쉐이더에서 보통 칼라나 벡터 값을 부드럽게 전환 시켜줄 떄 사용 한다.

 

이 함수를 이용하여 간단하게 두개의 텍스처를 수치값에 따라 변하게 하는 쉐이더를 만들어보았다.

Shader "Custom/Suf_Test5"
{
    Properties
    {
	   _MainTex("Albedo (RGB)", 2D) = "white" {}
	   _MainTex2("Albedo2 (RGB)", 2D) = "white" {}
	   _LerpPower("LerpPower", Range(0, 1)) = 0
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
     
        CGPROGRAM
        
        #pragma surface surf Standard 

		sampler2D _MainTex;
		sampler2D _MainTex2;

		float _LerpPower;

        struct Input
        {
            float2 uv_MainTex;
			float2 uv_MainTex2;
        };

        void surf (Input IN, inout SurfaceOutputStandard o)
        {
			float4 c = tex2D (_MainTex, IN.uv_MainTex);
			float4 c2 = tex2D(_MainTex2, IN.uv_MainTex2);
			o.Albedo = lerp(c, c2, _LerpPower);
        
			
		}
        ENDCG
    }
    FallBack "Diffuse"
}

위 코드는 텍스처2D를 2개를 입력하여 Range변수값에 따라 Lerp함수를 이용해 2개의 텍스처를 변환하는 쉐이더이다.

 

        void surf (Input IN, inout SurfaceOutputStandard o)
        {
              float4 c = tex2D (_MainTex, IN.uv_MainTex);
              float4 c2 = tex2D(_MainTex2, IN.uv_MainTex2);
              o.Albedo = lerp(c, c2, _LerpPower);
        }

위 핵심 코드에서 lerp함수가 쓰여진다.

 

lerp(1값, 2값, Value)

간단하게 함수를 설명하면 1번 2번값에는 float나 벡터같은 숫자 값이 들어가고 마지막 Value값에는 0~1 값이 들어간다.

이로 인해 반환되는 값은 Value값이 0에 가까울 수록 1번 값이 나오고 1에 가까울 수록 2번 값이 반환된다.

하지만 Value가 0.5와 같이 중간 값이면 두 값이 섞이는 형태로 반환이 된다.

 

여기서 더 고급지게 응용을 해보자.

 

위에 2가지 이미지가 있다. 1번 이미지는 풀모양으로 알파채널이 있는 이미지이고,

2번 이미지는 단순한 백그라운드 이미지이다.

이것을 위와 같이 두 이미지를 알파채널에 맞게 잘라서 합치는 쉐이더를 만들었다.

 

        void surf (Input IN, inout SurfaceOutputStandard o)
        {
                float4 c = tex2D (_MainTex, IN.uv_MainTex);
                float4 c2 = tex2D(_MainTex2, IN.uv_MainTex2);
                o.Albedo = lerp(c2, c, c.a);
        }

위는 핵심 코드이다.

float4 c는 풀 이미지고 float c2는 벽 이미지이다.

세번째 인자가 0값이면 벽이 출력되고 1값이면 풀이 출력 될 것이다.

해당 풀의 알파 채널에서 흰색 부분은 1값이고 검은색 부분은 0값이다 .

그래서 Lerp의 세번째 인자에 c.a와 같이 알파채널 값을 넣어 

두 이미지가 합성되는 쉐이더를 만들 었다.

 

 

-UV Control-

앞선 장에서는 텍스처를 출력하기 위해 필요한 UV기본 지식에 대해서 설명 했었다.

이번에는 이 UV 데이터를 조작하여 쉐이더에서 응용을 해보겠다.

 

        void surf (Input IN, inout SurfaceOutputStandard o)
        {
              o.Albedo = float3(IN.uv_MainTex.x, IN.uv_MainTex.y, 0);
        }

 

우선 UV 데이터를 float3형태로 출력해보면 위 사진과 같은 이미지가 나온다. 원리를 설명하자면 

숫자가 각 가로 세로 별로 0~1로 변하는 값이 표현된 이미지이면서 해당 이미지의 좌표를 뜻한다.

 

1. UV Offset과 Tiling

2D Textrue 인터페이스를 만들면 메뉴에 위 와같이 Tiling과 Offset 값을 조절할 수 있는 메뉴가 보인다.

 

Tiling을 1,1 -> 2,2로 변경 했을때 위 사진과 같이 이미지가 출력된다. 이미지가 반복되어 출력된것을 확인 할 수 있다.

기본 원리는 UV범위가 늘어나느 것이고 이미지가 반복되는 이유는 

위 사진과 같이 유니티 상에서 이미지세팅에 Wrap Mode가 기본으로 Repeat(반복)으로 설정 되어 있기 떄문이다.

만약 Warp Mode를 Clamp로 설정하면 위와 같이 UV가 넓어졌지만 이미지가 반복되지 않는다.

 

 

다음 개념으로 Offset을 0.5, 0.5로 변경했다. 이번에는 중심 점이 변경 된것을 확인 할 수 있다.

 

 

첫 번째 예시는 UV Tiling으로 

float4 c = tex2D(_MainTex, IN.uv_MainTex * 2);

tex2D함수에 UV를 입력하는 부분에 기본 UV값에 2를 곱하여 만든 것이다.

 

 

다음 예시는 UV Offset으로

float4 c = tex2D(_MainTex, IN.uv_MainTex + 0.5); 

tex2D함수에 UV를 입력하는 부분에 0.5를 더하여 만든 것이다 .

 

위 Tiling과 Offset의 차이점은 Tiling은 곱셈 공식이 들어가기 때문에 0,0부터 시작하여 늘어난 2값까지 UV가 늘어나서 출력이 되고 있고

Offset은 현재 값에 덧셈공식을 하기 때문에 0.5에서 시작하여 1.5로 끝나는 UV가 출력되고 있다.

 

 

 

 

 

 


WRITTEN BY
CatDarkGame
Technical Artist dhwlgn12@gmail.com

,