Shader의 정의와 기본 문법



1. Shader의 정의


- Shader란? 


렌더링 파이프라인 


그래픽 처리 장치(GPU)의 프로그래밍이 가능한 렌더링 파이프라인을 조작할 수 있는 프로그래밍 언어이다.

렌더링 파이프라인은 크게 Fixed Pipeline과 Programmable Pipeline이 존재하는에 이중에 Shader가 반영화된 파이프라인이 

Programmable Pipeline이다.


렌더링 파이프라인의 최종 목표는 컴퓨터 데이터를 모니터의 픽셀까지 뽑아내기 위한 것이다. 해당 파이프라인 안에서 데이터를 조작 할 수 있다.


위 사진은 렌더링 파이프라인의 요약 본이다.

Shader는 크게 두 가지가 존재한다.  

Vertex Shader     : 버텍스 파이프 라인에서 동작하는 쉐이더

Pixel Shader       : 픽셀 파이프 라인에서 동작하는 쉐이더


앞서 설명대로 Shader는 렌더링 파이프라인을 조종할 수 있는 프로그래밍 언어이다. 

버텍스 쉐이더의 주 프로그래밍 요소는 각 정점의 공간을 변환하는 것이고 

픽셀 쉐이더는 화면에 출력할 최종 색상을 계산하는 것이다.




2. 현재 사용되는 Shader 소개


-대표적인 Shader 언어


각 플랫폼 별로 지원하는 언어가 존재하듯 Shader도 하드웨어나 엔진에 따라 여러 종류가 존재한다. 하지만 거의 매우 흡사한 형태의 언어이다.


HLSL(High Level Shading Language) : DirectX API에서 동작하는 Shader 언어

GLSL(OpenGL Shading Language) : OpenGL API에서 동작하는 Shader 언어

CG(C for Graphics) : nVidia에서 제작한 Shader 언어


위 3개는 대표적인 Shader 언어이고 Unity나 Unreal과 같은 멀티플랫폼 대응이 가능한 게임 엔진에서는 위 3개의 언어 기반으로 자체 언어가 존재한다.



-Unity Shader



Unity의 Shader는 "ShaderLab"라는 자체언어를 기반으로 세 가지 방법으로 Shader를 만들 수 있다.

- Surface Shader

- Fragment Shader

- Fixed function Shader


위 중에서 현재 가장 일반적으로 사용되는 것은 Surface Shader이다.



- Unity Shader Graph

Unity 2018.1 부터 Unity Shader Graph라는 비주얼 노드 에디터 방식의 체계가 등장 했다.

SRP(Scriptable Render Pipeline)에서만 사용이 가능하다. 



-Unreal4 Shader



언리얼4는 기본 노드기반으로 쉐이더를 작성한다. 세부적으로 Custom Node를 이용하여 HLSL문법으로 쉐이더 작성이 가능하다.

그리고 글로벌 쉐이더라는 명칭으로 USH/USF  스크립트 방식 쉐이더가 존재한다.




-이 글에서 다룰 Shader

 이 글에서는 Unity의 Surface Shader를 이용하여 기본 문법을 시작으로 Shader의 용도를 익힐 것이다.




3. Unity Surface Shader


Unity 자체 쉐이더 언어인 ShaderLab과 CG(C for Graphics)를 함께 사용 하는 방식의 쉐이더이다.


기본적인 라이트, 버텍스 쉐이더의 복잡한 부분은 자동으로 처리된다. 즉 Matrix연산이 필요 없다.

스크립트 방식의 쉐이더지만 비주얼 쉐이더 에디터와 비슷한 개념을 가지고 있어 쉽다.



-Surface Shader 만들기


Project창에서 Create->Shader->Standard Surface Shader를 선택하여 쉐이더 파일 1개를 생성한다.



Unity에서 Shader파일을 만드는 메커니즘은

맨 좌측 사진처럼 최초 Shader파일을 만들때 설정한 이름이 중앙 사진처럼 Shader의 기본 이름으로 설정된다.(스크립트 수정으로 이름 바꾸기 가능)

그리고 우측에 실제 마테리얼에서 쉐이더 선택하는 메뉴의 이름은 맨 좌측 Shader파일의 이름 기준이 아닌 중앙 사진의 스크립트 파일 내에서 

설정한 이름 기준으로 설정 된다.





-Shader 기본 세팅 후 마테리얼 적용


최초 Surface Shader를 생성하면 위 처럼 기본 코드가 내장 되어 있다. 이 글에서는 기본 문법을 익히기 위해 필요 없는 코드를 모드 제외 하고

실제 마테리얼에 적용하는 것을 해본다.


Shader "ShaderTutorial/1st/ColorTest"

{

    Properties

    {

        _Color ("Color", Color) = (1,0,0,1)

 

    }

    SubShader

    {

        Tags { "RenderType"="Opaque" }

        LOD 200


        CGPROGRAM

        // 주석은 실제 프로그래밍 동작에 영향을 끼치지 않는 글자로서

// 이와 같이 코드에 코멘트를 달아 코드를 설명하는 용도로 사용 된다.

        #pragma surface surf Standard fullforwardshadows

        #pragma target 3.0


        struct Input

        {

            float2 uv_MainTex;

        };


        float4 _Color;


        void surf (Input IN, inout SurfaceOutputStandard o)

        {

            o.Albedo = _Color.rgb;

        }

        ENDCG

    }

    FallBack "Diffuse"

}



해당 쉐이더 파일을 위 코드로 전체 복사 붙여넣기로 수정한다.


아무 기본 오브젝트를 생성하여 마테리얼을 적용 시키면 위 사진과 같이 동작하는 것을 확인 할 수 있다.



-Shader 기본 문법 



Shader 이름


Surface Shader는 특수하게 ShaderLab과 CG  이 두 언어가 공존하는 쉐이더이다. 그 결과 같은 스크립트 파일 내에서도 조금 다른 문법이 2가지가 

존재한다. 이 파트에서는 두 언어를 구분하는법, 기본 문법 사용법을 다룬다.


먼저 최상단에 있는 이 부분은 쉐이더 파일 이름이다. ""으로 이름으로 정할 부분을 묶고 안에서 특수 문자 / 이것 으로 메뉴 구분을 할 수 있다.


  메뉴 구분 예시



코드 구분, 목차 개념


다음 전체 코드를 확인하면 일정 구간 마다 중괄호( { } ) 으로 구분이 지어져 있다. 큰 카테고리별로 구분하는 의미를 표현하기 위한 것이며

위 코드의 카테고리를 분류하면  

    - 1. Shader

      {

1-1. Properties

{

}


1-2. SubShader

{

2-1. Tags{}

2-2. struct Input{}

2-3. surf(){}

}

}

위 처럼 되어 있으며 책의 목차 같은 개념으로 생각하면 된다. 

주의 할점은 앞선 설명에 ShaderLab과 CG를 구분 하는 부분은 중괄호( { } )으로 되어 있지 않고  

CGPROGRAM

...

...

ENDCG 


위 처럼 되어 있으며  전체 기본으로는 ShaderLab코드이고 그 안에서 CGPROGRAM과 ENDCG사이에 있는 코드들은 CG언어라는 뜻이다.



주석 


코드 중간에 // 이후 프로그래밍 코드가 아닌 설명으로 된 글자가 있다. 이것을 주석이라고 하며 

프로그래밍 동작에는 영향끼치지 않는 글자이며   // 특수문자를 입력후 뒤에 텍스트 입력으로 사용한다.

이것을 이용하여 코드에 코멘트를 달아 코드를 설명하는 용도로 사용된다.



문법 참고 자료

ShaderLab : https://docs.unity3d.com/kr/530/Manual/SL-Shader.html

Surface Shader : https://docs.unity3d.com/kr/2018.1/Manual/SL-SurfaceShaders.html




변수(variable)



변수란? : 변수는 정보를 담는 통과 같다. 예를 들어 Color라는 유형의 변수에 빨강색 정보를 저장한다면 그 변수는 빨강색이라는 정보가 담긴 Color변수다.


이 기본적 개념이 변수의 전부이다. 나머지는 이 변수라는 개념을 활용하여 어떤 픽셀에 내가 어떤 변수(색)을 넣을 껀지 결정하는게 쉐이더에서의 

변수 활용이다.




Properties

스크립의 맨 앞부분 코드이다. 해당 쉐이더에서 사용할 변수(float3, int)를 만든 다는 것을 의미한다. 


Properties에는 여러 종류가 존재하며 각 종류마다 용도와, 표기 방법기 각각 있다.


 이름

설명 

 Float

소수점 숫자인 실수형 변수  

Range

Float과 동일하지만 최소값, 최대값을 설정할 수 있다. 

Int 

정수형 변수

Color 

Float이 4개가 하나가 되어 있는 형태의 변수 RGBA라고 불리며 색정보를 담는 변수 

Vector 

Float이 4개가 하나가 되어 있는 형태의 변수 XYZW라고 불리며 위치 정보를 담는 변수 

2D 

2D 텍스쳐를 저장할 변수 

Rect 

텍스쳐를 받는 것은 2D와 똑같지만 2의 배수가 아닌 텍스쳐도 받을 수 있다. 

3D 

3D 텍스쳐를 받을 떄 사용한다.(고급) 


https://docs.unity3d.com/Manual/SL-Properties.html 

위 링크를 참고하여 더 자세히 익힐 수 있다.



코드내에서 Properties를 작성하며 에디터 상에서도 위 사진과 같이 설정한 값에 맞게 조종할 수 있는 메뉴가 설정 된다.




SubShader, CG


Properties가 사전 준비라면 SubShader는 본격 쉐이더 작성 부분이다. 이 부분도 ShaderLab언어로 작성되지만 

CGPROGRAM~ENDCG코드를 이용하여 CG코드를 사용하여 스크립트를 작성할 수 있다.


위 CG코드 내부에서는 우선 크게 3가지가 있다.


기본 세팅 코드이다. 

#pragma surface.... <- 은 지시자라고 명칭하며 surface 쉐이더에서 어떠한 기능을 사용한다고 명칭하는 것이다.


Properties에서 만든 변수 연동하는 CG 변수(이름이 같아야 한다.)


surface shader의 핵심은 위 코드와 같이 surface function이라고 한다. 

주요 기능은 3D모델의 데이터를 SurfaceOutputStandard으로 가져와서 연산하는 것이다.


SurfaceOutputStandard는 구조체 형태의 변수이다.


구조체라는 것은 여러 변수가 모여 있는 변수 집합체이다.

그래서 안에 들어 있는 변수는 아래와 같다.

fixed3 Albedo

fixed3 Normal

half3 Emission

half Metalic

half Smoothness

half Occlusion

half Alpha


(fixed과 half는 float과 동일하지만 적은 용량의 비용을 소모한다, 그냥 당장은 float이라고 인식하면 된다.)


Surface쉐이더는 위 변수들을 조작하여 사용한다.



-Surface Shader 실습

 쉐이더 코드가 길게 있지만 실제로 동작하는 코드를 작성하는 부분인 void surf함수에서 변수를 활용하여 연산 등을 거처 실제로 엔진에서 어떻게 동작하는지 확인하여 쉐이더 사용 개념, 변수, 연산에 대해서 알아본다.


가장 단순한 확인을 위해서 SurfaceOutputStandard 구조체에서 기본 색을 설정할 수 있는 Albedo변수를 조작하여 시험을 하겠다.



점(.)

앞선 설명에서 나오듯 o.Albedo, _Color.rgb라는 부분의 점(.)이 나온다, 이 점(.)의 의미는 변수가 집합 되어있는 구조체형태의 변수에서 해당 구조체 변수안에 들어 있는 변수를 선택할떄 사용한다. 위 코드 예시로 들면 _Color내부의 rgb라는 변수를 사용할때 _Color.rgb 형태로 코드를 작성한다.




변수와 연산




  void surf (Input IN, inout SurfaceOutputStandard o)

        {

o.Albedo = float3(1, 1, 0);

        }


위와 같이 o.Albedo에 float(1, 1, 0) 값을 넣으니 노랑색이 됬다. 세부적 원리는

float3이후 들어가는 3개의 숫자는 일반적으로 R,G,B를 의미하여 빨강색 1과 초록색 1 파랑색 0이 섞여 노랑색으로 표현이 되었다.




void surf (Input IN, inout SurfaceOutputStandard o)

        {

o.Albedo = float3(0.5, 0.5, 0.5);

        }


다음 각 채널별로 0.5씩 값을 넣으니 같은 원리로 인해 회색이 표현되었다.

이로서 쉐이더를 코드로 조작하는 가장 쉬운것을 완료했다.


다음 앞 파트에서 설명한 변수를 활용하여 쉐이더를 조작해보자



    SubShader

    {

        Tags { "RenderType"="Opaque" }

        LOD 200


        CGPROGRAM

   

        #pragma surface surf Standard fullforwardshadows

        #pragma target 3.0


        struct Input

        {

            float2 uv_MainTex;

        };


       float3 _SomeColor;


        void surf (Input IN, inout SurfaceOutputStandard o)

        {

                        _SomeColor = float3(0, 0, 0);

o.Albedo = _SomeColor ;

        }

        ENDCG

    }

  


위 코드에서 빨간 줄이 그어진 부분이 주목해야될 코드이다.

void surf함수 밖에 float3형태의 _SomeColor라는 명칭을 가진 변수를 만들고 

함수 내부에서 float3(0, 0, 0)으로 모든 색값이 0으로 지정하고 Albedo에 집어 넣었다. 그 결과 이전과 똑같이 색상을 조종을 할 수 있다.

달라진 것은 이전과 같이 Albedo에 직접 생상값을 넣는게 아닌 변수에 저장해서 넣었다는 것이다.


이제 변수의 조합으로 좀더 활용해 보겠다.



  float3 _SomeColor;


float _Some_R;

float _Some_G;

float _Some_B;



        void surf (Input IN, inout SurfaceOutputStandard o)

        {

_Some_R = 1;

_Some_G = 0;

_Some_B = 1;


_SomeColor = float3(_Some_R, _Some_G, _Some_B);

o.Albedo = _SomeColor;

        }


이번에는 float3이 아닌 그냥 float을 3개 만들고 함수 내에서 값을 지정한 다음 float3변수인 _SomeColor에 넣어서 Albedo에 적용하니 

위 코드대로 공색깔이 변했다.



숫자가 있으면 연산도 존재한다.

사칙연산을 통해 변수의 값을 조작하는 것을 해보자





  float3 _SomeColor;


float _PlusValue;


float _Some_R;

float _Some_G;

float _Some_B;



        void surf (Input IN, inout SurfaceOutputStandard o)

        {

_PlusValue = 0.5;


_Some_R = 0.5 + _PlusValue;

_Some_G = 0.5 + _PlusValue;

_Some_B = 0.5 + _PlusValue;


_SomeColor = float3(_Some_R, _Some_G, _Some_B);

o.Albedo = _SomeColor;

        }


이번엔 _PlusValue라는 변수를 만들어서 0.5의 값을 넣어주고 _Some_R,G,B 변수에 0.5 + _PlusValue로 숫자 + 변수의 상황을 만들어

각 변수 값이 1이 되도록 만들어서 최종적으로 1,1,1값이 되었다.

그 결과 위 처럼 흰색 공이 되었다.


여기서 변수 연산의 응용을 한번 해보자.




  float3 _SomeColor;


float _PlusValue;


float _Some_R;

float _Some_G;

float _Some_B;



        void surf (Input IN, inout SurfaceOutputStandard o)

        {

_PlusValue = 0.5;


_Some_R = 0.5 + _PlusValue;

_Some_G = 0.5 + _PlusValue;

_Some_B = 0.5 + _PlusValue;


_SomeColor = float3(_Some_R, _Some_G, _Some_B);


_SomeColor = 1 - _SomeColor;


o.Albedo = _SomeColor;

        }


이전 코드하고 똑같은 코드에서 빨간줄 그어진 부분만 코드를 추가 했더니 흰색이 였던 공이 검정색으로 변했다. 색이 반전된 것이다.


원리는 간단하다 최종적으로 1,1,1이 였던 _SomeColor변수를  1을 - 연산자로 계산해서 값이 전부 0으로 변한 것이다.


여기서 알 수 있는 것은 float3처럼 다수의 float이 들어있는 변수는 float1 형태의 변수로 연산하면 float3 내부의 모든 float이 같이 계산이 되고


간단히 1- 연산자를 통해서 포토샵에서 Inverse효과를 구현해 낼 수 있다.



위 연산자 활용 부분을 통해서 쉐이더 코드내에서 변수, 숫자 놀이를 하는법을 배웠다.

위 예시에서는 덧셈 연산자만 활용했지만 사칙연산 전부 가능하다.




Properties를 이용한 쉐이더 작성


앞선 설명에서는 단순 변수를 사용하여 쉐이더를 조작하였다. 하지만 CG언어 이전에 ShaderLab에서 만든 Properties 변수와 연동하여

유동적인 쉐이더를 만들 수 있다.




Shader "ShaderTutorial/1st/ColorTest"

{

    Properties

    {

        _Color ("Color", Color) = (1,0,0,1)

    }

    SubShader

    {

        Tags { "RenderType"="Opaque" }

        LOD 200


        CGPROGRAM

   

        #pragma surface surf Standard fullforwardshadows

        #pragma target 3.0


        struct Input

        {

            float2 uv_MainTex;

        };


        float4 _Color;


        void surf (Input IN, inout SurfaceOutputStandard o)

        {

o.Albedo = _Color;

        }

        ENDCG

    }

    FallBack "Diffuse"

}


위 코드에서 Properties에서 변수를 만들고 그와 동일한 이름으로 CG언어 부분에서 똑같은 형의 변수를 만든다. 그러면 자동으로 연동이 된다.

연동된 _Color변수를 Albedo에 넣으니 위 gif처럼 엔진내에서 색상값을 조정이 가능한 형태로 만들어 졌다.



변수 활용 유의점


이 파트에서는 실제적으로 변수를 만들고 쉐이더에 적용하고 연산을 통해 변수를 조작하는 것을 설명했다.

본문에 앞서 스크립트 상에서 변수를 만들고 사용하는데에 몇가지 규칙이 존재한다.


변수 이름 규칙 

1. 변수이름이 숫자로 시작하면 안됨 (예 : 1RedColor)

2. 예약어는 안됨, 예약어란 엔진이나 시스템상에서 이미 사용하고 있는 변수이다.(예 : Vertex, float4)

3. 중복 안됨, 예약어와 같은 개념으로 이미 만든 변수는 또 다시 만들면 안된다.

4. 대소문자구별함(예 : _Color와 _color는 다른 변수이다)


연산의 규칙 

1. float을 한 자리수 라고 명칭하고 float3을 세 자리 수라고 명칭을 한다고 하면 같은 자리 수 끼리 연산은 문제 없으나

다른 자리 수 끼리 연산은 못한다. 예외로 float은 모든 자리수하고 연산이 가능하다.






이것으로 쉐이더의 정의 부터 시작해서 Unity Surface Shader 기본 문법과 활용법 포스팅을 마친다.







WRITTEN BY
CatDarkGame
Technical Artist dhwlgn12@gmail.com

,