본 포스팅은 청강문화산업대학교의 게임 제작 동아리 L.A.B.S의
TA STUDIO에서 공부한 내용을 바탕으로 작성되었습니다.
#쉐이더? 그게 뭔데?
익숙하지만 생소한 단어. 쉐이더.
많이 들어봤지만, 정확히 뭐다- 라며 정의내리기는 쉽지 않죠.
"그거 그... 게임 때깔 이뻐지는... 그런 거 아냐?""마인크래프트 쉐이더?"
...이대로 넘어가면 문제가 있겠죠?
게임 개발자들의 신적 존재이자 영원한 친구. Google에게 물어봅시다.
헤이 구글, 쉐이더가 뭔지 알려줘!
Shader[셰이더]
컴퓨터 그래픽스 분야에서 셰이더(shader)는 소프트웨어 명령의 집합으로 주로 그래픽 하드웨어의 렌더링 효과를 계산하는 데 쓰인다. 셰이더는 그래픽 처리 장치(GPU)의 프로그래밍이 가능한 렌더링 파이프라인을 프로그래밍하는 데 쓰인다.
··· ··· ··· ···.
음, 농담은 그만하고. 조금 더 간단하게 생각해봅시다.
Shader은 'Shade'와 '-er'의 합성어죠.
Shade: 명사 | 그늘, 색조, 음영
-er: (명사에서) | '무엇을 하는 사람이나 도구, 기계 등'을 나타냄
즉, 그늘·색조·음영 등의 효과를 주는 주체(도구, 기계 등의 무언가)
정도로 해석할 수 있겠네요.
#렌더링 파이프라인
다시 구글님의 설명으로 돌아와볼까요.
··· (중략) ···
셰이더는 그래픽 처리 장치(GPU)의 프로그래밍이 가능한 렌더링 파이프라인을 프로그래밍하는 데 쓰인다.
생소한 단어가 나왔네요. '렌더링 파이프라인'.
다시 한 번 영어사전을 펴 볼까요?
rendering: 명사 | 아직 제품화되지 않고 계획 단계에 있는 공업 제품을, 누구나 그 외관을 이해할 수 있도록 실물 그대로 그린 완성 예상도. 주로 디자인 용어로 쓴다.
pipeline: 명사 | 석유나 천연가스 따위를 수송하기 위하여 매설한 관로(管路).
아직 제품화 되지 않고 계획 단계에 있는 공업 제품 → 모니터에 출력되기 전, 3D 오브젝트로 존재하는 물체들
석유나 천연가스 따위 → CPU, GPU에게 넘겨줄 명령들.
이 정도로 하면 이해가 되려나요?
3D 오브젝트가 우리가 보는 모니터에 띄워지는 과정.
가상의 3차원 공간(게임의 공간)을 2차원(모니터)으로 출력하기 위해
원근 개념을 도입하여 화면에 출력하는 일련의 과정.
정도로 렌더링 파이프라인을 정의내릴 수 있습니다.
그리고 이 과정에 개입할 수 있는 것이 쉐이더구요.
렌더링 파이프라인의 과정은 매우매우매우매우x100 복잡한데,
아주 간략하게 정리해 보면 이렇게 됩니다.
여기서, 버텍스(정점) 쉐이더는 '버텍스의 좌표 값'을 제어.
픽셀 쉐이더는 오브젝트의 '조명, 색, 텍스처'를 제어하게 됩니다.
사실 저도 자세하게는 몰라요! 3학년 때 자세하게 배운대요!
아무튼 여기서는 픽셀 쉐이더를 다룰 겁니다.
#'픽셀 쉐이더' 가지고 놀기! 빛나는 원 만들기.
드디어 실습시간이에요! 실습이 역시 재밌죠. 안 그래요?
이론을 알아야 실습도 잘 하지. 이 사람아...
일단 유니티를 켜 봅시다.
먼저 빛을 내게 할 물체를 먼저 꺼내야 하겠죠.
Hierarchy창에서 마우스 우 클릭 - 3D object - Ouad
로 오브젝트를 만들어줍시다.
※Quad 오브젝트는 3dsMax에서의 plane 오브젝트처럼 뒷면이 렌더링 되지 않으니 주의할 것!
이렇게 해서, 평면인 오브젝트를 하나 꺼냈습니다.
그런데... 우리는 빛나는 '원'을 만들 거잖아요? 이건 사각형인데.
없으면 만드는게 게임 개발자의 자질이죠.
동그란 텍스쳐를 만들어 줍시다.
포토샵에서 크기 128*128에 해상도 72의 캔버스를 하나 만들어 줍시다.
Q. 왜 128*128 사이즈 인가요?
A. 첫 번째 이유로는, 텍스쳐의 크기는 2의 n승을 지켜야 합니다.
(...라고 하더라구요. 검색해보니 이유가 꽤 많던데... 일단 너무 많고, 다 이해한 것도 아니라 패스. 압축할 때 자동으로 맞춰 주기도 한다는데, 일단 안전하게 2의 n승 사이즈로 만들어 줍시다!)
두 번째 이유로는, 이펙트는 그래픽 중에서 부하가 가장 심하기 때문입니다.
이펙트의 질과 프로그램의 최적화 사이에서 타협을 잘 해야 한다는 의미겠죠?
Q. 왜 해상도는 72인가요?
A. 우리는 '모니터'에서 이를 볼 것이기 때문입니다. 출력물이 아닐 경우 72 이상의 해상도는 큰 의미가 없습니다.
뭐, 그럼 이제 뻔할 뻔자죠.
검은색 배경을 깔아주고, 원을 하나 그려줍니다.
그리고 채널로 가서.
Alpha 채널을 만들어주고, RGB모양 선택 후 페인트.
TGA 파일로 저장해줍니다.
이 때, 32bit로 저장해야지 알파가 적용되니 꼭 확인!
만든 텍스쳐파일을 유니티로 불러왔습니다.
이제 적용시킬 재질을 만들어 줘야 겠죠?
Material을 생성 해, Quad 오브젝트에 적용해주고,
이미지를 넣기 위한 쉐이더도 만들어 줍시다.
(Asset창 - 마우스 우클릭 - Material
Asset창 - 마우스 우클릭 - Shader - New Surface Shader)
Quad 오브젝트에 Material을 적용한 후에,
우측의 Inspector 창에서 Shader를 [Custom > 방금 만든 쉐이더 이름]으로 설정해주고,
바로 아래의 None(texture)이라고 적혀있는 곳에 이미지를 넣어줍시다.
어... 여전히 동그랗지 않은데요. 그냥 무늬만 생겼는걸요?
아직 설정할게 남아서 그렇습니다!
불러온 TGA 이미지 파일의 Inspector창을 보면,
우리는 여기서 딱 3개만 설정하면 됩니다.
Alpha Is Transparency: 투명도(알파)를 적용 시켜준다는 옵션. 체크 해 줍시다.
Generate Mip Map: Mip Map* 을 생성 한다는 옵션. 체크 해제 해 줍시다.
*MipMap: 렌더링 속도를 향상시키기 위한 목적으로 기본 텍스처와 이를 연속적으로 미리 축소시킨 텍스처들로 이루어진 비트맵 이미지의 집합. 시야에서 멀어질수록 낮은 텍스쳐로 이미지를 대체해서 보여주는데, 이펙트는 해상도가 중요합니다. 안 그래도 최대한 이미지를 작게 쓰고 있는데 말이에요!
Default의 Compression: 이미지의 압축률을 정하는 옵션. None으로 바꿔 줍시다.
이렇게 체크해주면, 짜잔!
... 더 이상해 진 것 같다구요? 정상입니다.
왜냐하면, 텍스처에서 투명도를 적용해줬지만, 쉐이더에서는 투명도를 받으라고 설정해주지 않았기 때문이에요.
그럼 이제, 쉐이더를 열어봅시다.
코딩을 해본 사람들은 그래도 익숙할 수도 있고, 아니라면 오... 이게 뭐여... 할 수도 있겠네요.
자, 하나하나씩 살펴봅시다.
Shader "Custom/NewSurfaceShader"
{
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
유니티 쉐이더의 자체 스크립트 부분입니다.
이곳에 쉐이더의 이름과 트리구조를 정할 수 있고,
특정 구문을 적으면 Material의 Inspector에 인터페이스가 생깁니다.
Shader "Custom/NewSurfaceShader"
쉐이더의 이름입니다. /를 쓰면 트리구조가 생기며,
제일 마지막에 쓴 이름이 쉐이더의 이름이 됩니다.
※쉐이더명과 파일명은 별개이니, 주의할 것!
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
}
Inspector에 직접적으로 인터페이스를 만들어 줍니다.
예를 들어, 위 코드의 _Color ("Color", Color) = (1, 1, 1, 1) 부분은
Material의 Inspector에 컬러피커를 만들어 줍니다.
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 200
렌더링 타입을 설정하는 부분입니다.
현재 설정되어 있는 것은 "Opaque". 즉 <불투명> 입니다.
위에서 투명도 설정을 해주었는데도 제대로 보이지 않았던 이유가 이것!
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"
}
CGPROGRAM 영역입니다.
유니티 서피스 쉐이더는 자체 스크립트와 CGPROGRAM 언어를 함께 사용하고 있습니다.
이 부분이 우리가 주 된 쉐이더 코딩을 하는 부분입니다.
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
Snippet[전처리] 부분입니다.
쉐이더의 조명계산이나, 세부적인 분기를 지정해줍니다.
struct Input
{
float2 uv_MainTex;
};
Input이라는 이름을 가진 구조체(Structure)입니다.
중괄호{} 안에는 엔진으로부터 받아야 할 데이터들이 들어갑니다.
코딩을 해보신 분들에게는 익숙할 수도 있겠지만, 중괄호 끝에 ;(세미콜론)이 붙어야 한다는 점을 주의하세요!
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
surf라는 이름을 가진 함수 영역 입니다.
색상이나 이미지가 출력되게 만들 수 있는 부분입니다.
사용할 수 있는 프로퍼티 중 일부입니다.
fixed3 Albedo: 기본색상
fixed3 Normal: 반사각을 결정하는 면의 방향
fixed3 Emission: 이 오브젝트가 스스로 생성하는 빛의 양
half Specular: 머티리얼이 빛을 반사하는 정도(0~1)
fixed Gloss: 스펙큘러 반사가 퍼지는 정도
fixed Alpha: 머티리얼의 투명한 정도
역시나 코딩을 해보신 분들에게는 익숙할 수도 있겠지만,
여기서는 중괄호 끝에 ;(세미콜론)이 없다는 점을 주의하세요!
대충 설명은 끝났으니, 지금 우리에게 필요 없는 부분을 지워봅시다!
먼저 주석들을 전부 지우고, (붉은색)
거칠기나 금속 여부도 현재 사용하지 않으니 지워줍시다. (노란색)
빛 추가 연산을 하는 코드도 현재는 필요 없으니 제거해줍시다.
(fullforwardshadows: 그림자 및 공간 분할 처리 방법을 제어하기 위해 추가 지시문. 포워드 렌더링 경로 에서 모든 라이트 섀도우 유형을 지원합니다.)
Shader "Custom/NewSurfaceShader"
{
Properties
{
_Color ("Color", Color) = (1,1,1,1)
_MainTex ("Albedo (RGB)", 2D) = "white" {}
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 200
CGPROGRAM
#pragma surface surf Standard
#pragma target 3.0
sampler2D _MainTex;
struct Input
{
float2 uv_MainTex;
};
fixed4 _Color;
void surf (Input IN, inout SurfaceOutputStandard o)
{
fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
o.Albedo = c.rgb;
o.Alpha = c.a;
}
ENDCG
}
FallBack "Diffuse"
}
다 하고 나면, 깔끔해졌네요!
자, 그럼 이제 투명도를 적용해볼까요?
아까의 이상한 현상이 나타난 이유는 렌더링 타입이 "Opaque". 즉, 불투명으로 되어 있어서 였습니다.
그렇다면...
SubShader
{
Tags { "RenderType"="Transparent" }
LOD 200
이렇게 렌더링 타입을 "Transparent"(투명한) 으로 바꿔주면 되겠네요!
아, 추가로 그 뒤에 더 적어 볼게요!
SubShader
{
Tags { "RenderType"="Transparent" "Queue" = "Transparent" }
LOD 200
뒤의 "Queue" = "Transparent"는 렌더링 순서를 정해주는 코드입니다.
지금은 큰 상관 없으니 넘어가셔도 괜찮아요! 안 적어도 문제는 없습니다!
CGPROGRAM
#pragma surface surf Standard alpha:fade
#pragma target 3.0
이 부분에도 alpha:fade를 추가해주세요!
여기까지 하신 후 저장하시고 유니티를 보면,
투명도 적용이 끝났습니다!
이제 빛을 내는 방법만 알면 되겠네요.
다시 쉐이더로 돌아옵시다.
void surf (Input IN, inout SurfaceOutputStandard o)
{
fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
o.Emission = float3(1, 1, 1);
//o.Albedo = c.rgb;
o.Alpha = c.a;
}
o.Albedo를 주석처리하거나 지우고, o.Emission을 추가해줍시다.
두 속성은 둘 다 RGB값을 사용하기 위해 float3 이상을 사용합니다.
float3은 (R, G, B) float4는 (R, G, B, 투명도) 입니다.
그럼 뭐가 다르냐!
Albedo는 조명 연산을 받으나
Emission은 조명 연산을 받지 않습니다.
정확히는 Albedo는 기본 색상을 나타내지만, Emission은 이 물체가 빛을 발하는 정도를 나타냅니다.
Albedo를 지우고 Emission만 사용할 경우, 조명 연산의 적용을 받는 기본 색은 존재하지 않으나,
빛은 발하고 있는 상태가 됩니다.(정말로 빛 그 자체가 되어버리는)
자, 그럼 여기서 우리는 색상을 어떻게 나타낼 수 있을까요?
네, 당연하게도 빛의 3원색을 사용합니다.
o.Emission의 float에서, 0~1 사이의 소수점 값은 빛의 세기를 나타냅니다.
(0%~100%를 0과 1, 그리고 사이의 소수로 표시한다고 보면 되겠네요.)
빨간색 빛이 50%, 초록색 빛이 50%, 파란색 빛이 50% 비춰진다면?
당연하게도 회색이 되겠죠!
그렇다면 현재 코드에서의 o.Emission의 색상은?
네, 당연하게도 흰색입니다.
R 100%, G 100%, B 100%니까요.
그렇다면, 여기서 1보다 더 큰 수치를 가지면 어떻게 될까요?
해보면 알겠죠.
void surf (Input IN, inout SurfaceOutputStandard o)
{
fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
o.Emission = float3(2, 1, 1);
//o.Albedo = c.rgb;
o.Alpha = c.a;
}
어떻게 될까요? R이 200%니, 빨간색이 나오려나요?
그냥 흰색이 나옵니다.
(묘-하게 테두리에 빨간 빛이 돌긴 하는데. 이는 텍스쳐를 만들 때 테두리 부분이 완벽한 흰색이 아니었기 때문.)
왜 그냥 흰색이 나올까요? 빨간색 값이 더 높으니, 빨간색이 나와야 하는 게 아닐까요?
이유는 모니터가 200%라는 색을 출력하지 못하기 때문입니다.
이런 현상을 바로 HDR이라고 부릅니다.
High Dynamic Range [ HDR ]
카메라 용어에서 나온 것으로,
카메라에서 과노출로 사진을 찍었을 때, 하얗게 날아간 부분이 그냥 하얀색이 아닌
그저 표현하지 못하는 굉장히 밝은 색이라는 것.
이를 컴퓨터 그래픽스에서는 1보다 높은 값으로 구현해두었으며,
이렇게 날아간 밝은 색을 표현하는 방법, 혹은 그 기술을
High Dynamic Range[HDR] 이라고 부른다.
정확한 내용은 아닐 수 있습니다. 이해한 것을 그대로 썼을 뿐...!
그럼 우리는 이 과도한 밝음을 어떻게 표현할까요?
여러 방법이 존재하겠지만, 우리는 Bloom을 통해 표현하기로 합시다.
"Bloom이 뭔데요...?"
쉽게 말해서, 빛 번짐 효과 입니다.
후처리 기능인 Post-Processing을 이용하여 Bloom을 넣어보도록 합시다.
일단 Post-Processing을 유니티에서 설치 해 줍시다.
설치 하셨나요?
이제 와다다다 하기만 하면 됩니다.
* is Global?
체크하지 않을 시, Volume 오브젝트의 박스 영역 안쪽에서만 적용됨.
전체적으로 적용하고 싶다면 is Global을 체크하자!
짠. bloom 효과 완성이다.
o.Emission = float3(2, 1, 1); 이었으니 빛번짐에서 붉은 빛이 감돈다. (R이 200%이므로.)
o.Emission = float3(1, 2, 1); 를 한다면?
당연하게도 초록빛이 빤딱!
(만족)
응용하면 별모양으로 반짝이게도 바꿀 수 있겠다. 텍스쳐만 바꾸면 되니.
'그래픽 > TA STUDIO (동아리 활동)' 카테고리의 다른 글
[TA STUDIO] #4. 텍스쳐 2개를 섞어 보자. lerp 함수 (0) | 2019.10.21 |
---|---|
[TA STUDIO] #3. MainTex와 SubTex. (깜빡이는 원 만들기) (0) | 2019.10.13 |
[TA STUDIO] #2. _Time함수와 텍스처 움직이기. (+UV Map으로 속도 변화 주기) (1) | 2019.10.05 |
댓글