본문 바로가기

유니티 쉐이더 스타트업

[ 유니티 쉐이더 스타트업 ] 05 - 1 . 기초적인 Surface Shader

[ 오브젝트에 마테리얼 적용해보기 ]

[ Surface 쉐이더를 Material에 적용하기 ]

Assets에서 Create - Shader - Standard Surface Shader로 새로운 Surface 쉐이더를 생성해준다 .

 

Assets에서 Create - Material을 통해 새로운 마테리얼을 생성해준다 .

 

1 - 2 순으로 드레그 앤 드롭하여 쉐이더를 마테리얼에 -> 마테리얼을 오브젝트에 적용해준다 .

 


 

[ 쉐이더 코드를 열어 이름을 바꾸기 ]

[ Surface 쉐이더 열어보기 ]

생성했던 Surface 쉐이더를 더블클릭하면 위와 같이 비주얼 스튜디오를 통해 열 수 있을 것이다 .

그 전에 배운 구조를 볼 수 있고 , 일반적으로 {} 를 통해 구분하고 있다 .


 

[ 쉐이더 이름 바꾸기 ]

상단의 쉐이더 코드 첫줄을 원하는 이름으로 변경한다 .

이때 "/"는 쉐이더의 위치를 일종의 트리구조로 만들 수 있다.

상단의 경우 Custom - Yeo - NewShader의 위치에 생성된다 . 

중요한건 " "로 문장이 닫혀 있어야 한다.

 


[ 쉐이더 Properties 제작하기 ]

[ 쉐이더 Properties ]

 

전체적으로 보면 쉐이더는 크게 세 부분으로 나눌 수 있다 .

이중에서 1번 영역이 Properties 영역이다 .

해당 부분은 Material에서 다음과 같은 인터페이스로 볼 수 있다.

이때 , 여기에 사용되는 인터페이스를 만드는 방법은 다음과 같다 .

 

  • Float을 받는 인터페이스
  1. _Name ( "display name", Range (min,max) ) = number
  2. _Name ( "display name", Float ) = number
  3. _Name ( "display name", int ) = number

 

  • Float4를 받는 인터페이스
  1. _Name ( "display name", Color ) = (number,number,number,number)
  2. _Name ( "display name", Vector ) = (number,number,number,number)

 

  • 기타 Sampler를 받는 인터페이스
  1. _Name ( "display name", 2D ) = "name"{ options }
  2. _Name ( "display name", Rect ) = "name"{ options }
  3. _Name ( "display name", Cube ) = "name"{ options }
  4. _Name ( "display name", 3D ) = "name"{ options }

이상의 프로퍼티들을 각자 알아보자 .


 

 [ Properties - Range ]

 

_Name ( "display name", Range (min,max) ) = number

해당 기능은 최소값 / 최대값으로 이루어진 슬라이더가 나오는 명령이다 .

각 부분을 알아보자 .

 

  • _Name : 해당 기능의 변수명 . _는 외부에서 입력 받았음을 표시한다 . 이때 , 한글/띄어쓰기/_외의 특수문자/숫자시작/예약여 (color - float 등)는 사용하면 안된다 . 또한 변수명은 고유해야 한다 .
  • "display name" : 해당 기능의화면에 나타나는 글자.사용처를 알 수 있게 이름을 짓는게 좋다 . +한글 가능
  • Range(min,max) : 최대 , 최소값이 있는 슬라이더를 만든다. 또한 쉐이더는 소수점을 가진 숫자를 쓰므로 입력되는 값은 소수점을 가진 숫자인 float이다 .
  • number : 쉐이더가 처음 만들어 질 때의 초기값이다 . 이후 조절된 값으로 변경된다 .

 

 [ Properties - Float ]

 

_Name ( "display name", Float) ) = number

해당 기능은 한자리의 소수점을 입력받는 인터페이스를 만들어준다 .

Range와 달리 범위제한이 없기에 아티스트의 자유도를 극도로 주고 싶거나 결과를 예측하기 어려울때 사용한다 .


 

[ Properties - Color ]

 

_Name ( "display name", Color) ) = (number,number,number,number)

해당 기능은 Color 인터페이스를 만들어준다 .

R/G/B/A 4가지 숫자를 입력 받으므로 float4를 받게 된다.


 

[ Properties - Vector ]

 

_Name ( "display name", Vector ) ) = (number,number,number,number)

float4를 직접 숫자로 입력받는 인터페이스를 만들어준다 .


 

[ Properties - tex2D ]

 

_Name ( "display name", 2D ) ) = "name" { options }

 

2d 텍스쳐를 받는 인터페이스를 만들어준다 .

float 계열로 분류되지 않는 sampler들이다 .

텍스쳐는 UV좌표와 함께 계산되어야 float4로 출력될 수 있기 때문에 , 아직 UV와 계산되지 않은 텍스쳐는

색상 (float4)으로 나타낼 수 없다 .그래서 이때까지는 sampler라고 부른다 .

 


[ 색상 출력하고 연산하기 ]

[ 쉐이더의 구조 ]

앞서 살펴봤던 영역을 다시 보자 .

1번은 프로퍼티 영역이었다.이번에는  2,3 번에 대해 알아보자 (3번은 2번 내부에 속해있다)

그중에서도 CGPROGRAM ~ ENDCG로 끝나는 부분은 유니티 자체 스크립트가 아닌

cg언어를 이용해서 쉐이더를 직접 짜는 부분이다 .

3번 영역을 다시 나누자면 다음과 같이 분류가 가능하다 .

  1. 1번은 설정 부분이다 . 전처리혹은 스니핏이라고 부른다 .쉐이더의 조명계산 설정 혹은 기타 세부적인 분기를 정한다
  2. 2번은Input이라는 이름의 구조체이다 . 엔진으로부터 받아와야 할 데이터가 들어간다 .
  3. surf라는 이름의 함수 영역이다.색상 이미지 출력되는 부분을 만들 수 있다 .
  4. 1,2,3 에 속하지 않은 부분이다 . 일종의 빈자리이지만 꽤 중요한 부분이다 .

 

[ Surf 함수 뜯어보기 ]

void Surf (Input IN , inout SurfaceOutputStandard 0) {}

출력을 담당하는 surf 함수의 구조를 살펴보자 .

void를 return 하고 인자로 Input In inout (받기도 하고 집어넣기도 한다 )으로 SurfaceOutputStandard o를 수행한다.

 

여기서 SurfaceOutputStandard는 무엇일까?

struct SurfaceOutputStandard
{
fixed3 Albedo;
    fixed3 Normal;
    fixed3 Emission;
    half Methalic;
    half Smoothness;
    half Occlusion;
    half Alpha;
}

이 구조체는 유니티 내부의 다른 인클루드 파일에 정의되어 있으며 , 보이지 않지만 자동 로딩된 상태라 이해하자 .

즉 , 7개의 변수를 불러 간단히 값을 집어 넣는것만으로 출력값을 만들 수 있다 .

 

cf ) float / half / fixed

  • float : 실수
  • half : float의 1/2 크기
  • fixed : half의 1/2크기

[ 색상 출력 - Albedo ]

일단  Metalic과 Smoothness는 지워준다 .

위의 o.Albedo의 뜻은 SurfaceOutputStandard안에 있는 Albedo변수라는 뜻이다 .

이후 , 다음과 같이 Albedo에 빨간색을 값으로 넣어준다 .

다음과 같이 출력됨을 알 수 있다.

이때 , 빛의 영향을 받음을 볼 수 있다 . 이는 조명연산을 추가로 받기 때문이다 .


 

[ 색상 출력 - Emission ]

이번에는 Albedo 대신 Emission을 넣어준다 .

빛의 영향을 받지 않음을 볼 수 있다. 

이는 조명연산을 받지 않아 조명과 상관없는 순수한 색만이 출력되기 때문이다 .

만약 Emission,Albedoi가 같이 쓰인다면 둘의 값은 최종적으로 더해지기에 필연적으로 밝아진다 .


 

[ 색상 연산하기 ]

위와 같이 float3를 더할 수 있다. (포토샵의 Linear Dodge (Add)와 같다)

 

위와 같이 float3를 곱할 수 도 있다 . (포토샵의 Multiply와 같다)

 

0~1의 범위를 넘어가는 연산의 결과 (2,0,0)이 일반적 빨강(1,0,0)과 같음을 볼 수 있다 .

모니터에서 1이상의 색은 1과 같이 보이기때문인데 내부적으로는 실제 데이터 (2,0,0)으로 저장됨을 주의해야 한다 .

 

이렇게 1보다 밝거나 0 보다 어두운 색이 있어서 그것이 존재하고 계산되는 상태를 HDR (High Dynamic Range)라 한다 .

 

cf ) 연산의 규칙

  • 연산은 같은 자리수 끼리 가능하다. ex ) float4와 float3는 연산이 불가하다 .
  • 예외적으로 한자리는 언제나 가능하다 ex) float3(1,0,0) + 1

[ 변수를 이용하기 ]

[ 변수 넣어보기 ]

myColor라는 float4 변수를 정의하고 Albedo에 해당 변수를 넣었다 .

변수를 통해 색이 적용됨을 볼 수 있다 .

그런데 float3인 Albedo에 float4가 들어갔다 .이는 잘못된 방식이지만 오류를 내지 않을 뿐이다.

이를 myColor.rgb를 넣어줌으로 해결하였다 .


[ RGB의 순서 바꾸기 ]

1 . grb로 순서를 바꿀 수도 있다 . 그결과로 (0,1,0)인 초록이 나왔다 .

2 . 이렇게 rrr과 같이 하나의 요소만 사용 할 수도 있다.

그 결과로 (1,1,1)인 흰색이 나왔다 .

3 . 또한 연산과 같이 한자리수인 1을 넣어도 (1,1,1)을 넣은것과 같이 동작한다 .

4 . 변수의 부분값을 사용해도 마찬가지이다. 이를 스위즐링이라고 한다.

5 . 또한 이렇게 float2를 내부에 넣어 사용 할 수도 있다.

위의 값은 결국 flaot(1,1,0)의 결과인 노랑이 나올 것이다 .

 


 

[ 외부의 입력값 출력해보기 ]

[ 색상을 입력받아 출력해보기 ]

Properties에 MyColor라는 인터페이스를 생성한다 . 이때 , Color가 float4라는 점을 기억하고 있자.

이때 , CGPROGRAM ~ ENDCG사이에 함수 혹은 구조체 안에 들어가있지 않은 빈공간인  1,2를 볼 수 있다 .

이 공간이 변수를 선언 할 수 있는 공간이다 .

해당 영역에 _MyColor를  선언해주자 . 이때 ,주의할 점은 해당 변수가 쓰이기전 선언을 해주어야 한다는 점이다 .

이제 사용은 간단하다 . 선언한 변수를 Albedo값으로 넣어준다 .

이때 , Albedo는 float3인 RGB만 사용하므로 Color의 rgb만 사용해준다 .

이와 같이 MyColor프로퍼티에서 내가 지정한 색으로 Spere의 색이 바뀜을 볼 수 있다 .


 

[ 외부입력값 조합하기 ]

Properties에 MyRed/ MyGreen / MyBlue라는 인터페이스를 생성해서 Albedo의 RGB를 각각 조절하자 .

먼저 인터페이스를 생성한다 .

변수를 선언한다 .

surf 함수에 다음과 같이 사용한다 .

위와 같이 RGB 프로퍼티를 각각 조절하면 조합하여 Spere의 색을 정할 수 있다 .


[ 색상의 밝기 조절하기 ]

Properties에 MyBrightDark라는 인터페이스를 생성해서 Albedo의 RGB를 각각 조절하자 .

먼저 인터페이스를 생성한다 .이때 , 범위가 -1 ~ 1인 이유는 밝음과 어두움을 표현하기 위해서이다 .

동일하게 변수를 생성한다 .

다음과 같이 Albedo값에 MyBrightDark를 더해줌으로 밝기를 조절한다 .

다음과 같이 사용 할 수 있다 .

 


[ 프로퍼티 깔끔하기 지우기 ]

[ 지워보기 ]

surf 함수에서 사용하지 않을 것들을 삭제해서 깔끔하게 정리해보자 .

다음은 사용하지 않는 _Color를 삭제해보자.

마지막으로 _MainTex를 삭제하자 .

다음과 같은 에러를 볼 수 있다 .

Input 구조체에 아무 멤버가 없다는 에러인데 , 이 말은 해당 구조체는 비어있으면 안된다 .

이 부분에 들어갈 문구는 지정되어 있고 , 아무거나 쓰면 안된다 .

일단 , 다음과 같이 써준다 .

다시 에러가 생성된다 . 아까 지운 c를 Alpha에서 참고하고 있기에 생긴 문제이다 .

1로 설정 , 불투명하게 설정한다.이제 정상 작동한다 .

마지막으로 최소한으로 남기기 위해 다음의 것들을 지운다 .

LOD는 이 쉐이더의 환경 설정에 따른 옵션 값에 대한 내용이며 ,

#pragam target 3.0은 쉐이더 모델 3.0 이상에서만 돌아가게 함으로 복잡한 코드를 쓸 수 있게 제한을 풀어주는 기능이다 .

 

위의 기능들은 초급단계에서 당장은 사용하지 않기에 일단 지워주자 .

최종적으로 다음의 코드로 깔끔해졌다