정의
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
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은 모든 자리수하고 연산이 가능하다.
CatDarkGame's Game Dev Story :: Shader의 정의와 기본 문법 (tistory.com)
'게임엔진 > Unity' 카테고리의 다른 글
[Unity] 입력 바인딩 시스템 만들기 (0) | 2022.11.30 |
---|---|
[Unity] 프러스텀과 오클루전 컬링 (Frustum & Occlusion Culling) (0) | 2022.11.29 |
[Unity] DOTS란 무엇인가? Unity ECS 시스템 요약 (0) | 2022.10.26 |
[Unity] Sprite (UI, SpriteRenderer) 색상 관련 (0) | 2022.09.18 |
[Unity] 프리팹 (Prefab) (0) | 2022.08.18 |