炸殺
카테고리
작성일
2020. 4. 13. 06:18
작성자
炸殺

 

24bit 컬러에서 흰색을 만들려면 R 255 G255 B255의 값을 주어야 합니다.최솟값인 0, 0, 0은 당연히 검정이겠죠?

색상 슬라이더나 색상 피커를 이용해보신다면 좀 더 빠르게 이해하실 수 있을 겁니다. 

 

채널당 8bit 흰색의 RGB 값은 255,255,255이다.

 

하지만 각 채널이 5,6,5bit가 되는 16bit 컬러에서는 R32 G64 B32가 흰색입니다.

즉 압축 포맷에 따라 범위가 모두 다르다는 뜻인데요.

 

 

 

이 다양한 압축 포멧에 따라 프로그래머가 일일이 색상 값을 입력한다? 머리가 터져버릴 것입니다.

 

 

여기서 사용하는 개념이 바로 '백분률'입니다.

 

 

백분율을 사용하게 되면 최댓값이 다르더라도 0~100%의 비율을 이용하여 색상을 가져오기 때문에

파일 포맷이 달라도 비슷한 결과가 나오게 되죠.

 

백분율이라 하면 0에서 100에 숫자를 떠올리기 쉽지만 0%를 0, 100%를 1로 표기 표기하기도 합니다.

이렇게 되면 20%는 0.2, 50%는 0.5가 되겠지요?

즉, 0~1 사이의 숫자로 모든 포맷의 컬러를 표현할 수 있게 되는 것.

 

 

여기서 사용되는 것이 바로 부동 소수점 float 입니다.

 

 

 

float

 

float은 프로그래밍에서 계산할 때의 단위 중 하나로 32bit(약 42억)의 숫자를 나타낼 수 있으며,

소수점 아래 6번째까지 유효한 값입니다. (대충 소수점이 있는 숫자라고 생각하면 됩니다)

 

이 밖에도 half(16bit), fixed(11bit)도 있지만 일단은 신경 쓰지 맙시다. 큰 그릇 작은 그릇.. 정도로 생각하시면 됨.

 

 

 

 

어? 개당 32bit? 감이 잡히시나요?

코드 상에서 컬러를 나타낼 때 이 float 3개를 이용하여 한 채널당 하나의 플롯을 부여합니다. (32bit, 32bit, 32bit)

숫자 1이 100%였으니 즉 흰색은 float3 ( 1, 1, 1 )이 되는 것입니다. 

 

알파 채널 역시 표현이 가능합니다. float하나를 더 넣어줘서 float4(R, G, B, A)를 이용하면 됩니다.

이 밖에도 float을 하나, 혹은 2개만 사용하는 경우도 다양하게 있습니다.

 

다시 색상으로 돌아와서... float3 (1, 0, 0)은 빨강, (0, 1, 0)은 초록, (0, 0, 1)은 파랑이라면?

빛의 삼원색을 공부해보신 분은 아시겠지만 빛은 섞을수록 밝아집니다. 

float3 (1,1,0)은 노랑, (1, 0, 1)은 마젠타..... 굉장히 익숙합니다.  

 

빛의 3원색...

 

이 밖에도 어떤 색을 얼마만큼의 비율로 섞냐에 따라 다양한 컬러를 나타낼 수가 있겠죠?

프로그래밍에서는 색상을 표현할 때 float 3개(알파까지 4개)로

파일 포맷에 상관없이 백분율을 이용하여 다양한 색을 도출해낸다.라는 것이 이해가 되셨다면 성공입니다.

 

이제 색상이 전부 숫자로.... 보이실 겁니다.... 

 

 

 

 

 

 

 

Shader

 

쉐이더(Shader)란 3D 컴퓨터 그래픽에서 물체의 3차원 위치를 나타내는 x, y, z 좌표나, 색상, 텍스처, 조명 등 다양한 시각적 효과를 계산하여 최종적으로 화면에 출력할 픽셀의 위치와 색상을 계산하는 함수입니다.

 

Shader의 언어는 몇 가지의 종류가 존재하는데요,

그중 가장 유명하고 보편적으로 넓게 쓰이는 것이 HLSL (High Level Shading Language)

OpenGL에서 사용하는 언어인 GLSL (OpenGL Shading Language),

엔비디아가 마이크로 소프트와 협력하여 만든 언어인 CG(C for Graphics)가 있습니다.

직관적인 이름이네요 그래픽을 위한 C언어....

 

 

이 중 유니티가 사용하는 언어는 CG입니다.

하지만 URP부터는 HLSL을 사용하고 있죠. (언리얼도 HLSL)

뭐야 그럼 언어를 세 개나 배워야 해!?라고 생각하실 수 있지만 걱정하지 마세요.

이 세 가지 언어는 3D MAX 2016 버전과 3D MAX 2019 버전의 차이만큼 굉장히 유사하기 때문에

하나만 배우면 나머지는 쉽게 터득하실 수 있습니다. 

 

 

 

그리고 유니티 이 친구들은

자체 언어를 하나 더 만들었습니다.

 

 

 

 

유니티 셰이더

 

 

유니티 셰이더는 정통 코드가 아닌 스크립트로 이루어져 있습니다.

유니티에는 셰이더를 짜는 방법이 세 가지가 있는데요,

 

1. Shader Lab

2. Surface Shader

3. Vertex & Fragment Shader

 

입니다.

 

Shader Lab으로만 셰이더를 짤 경우 호환성은 짱이지만 할 수 있는 게 없습니다. 텍스쳐가 나오면.. 다행임

Vertex & Fragment Shader은 Surface Shader의 상위 버전으로 CG를 좀 더 디테일하게 다룹니다.

Surface Shader가 오토 모드라면Vertex & Fragment Shader은 수동이라는 느낌.

 

 

아티스트의 레벨에서 배울 건 Surface Shader입니다.

 

Surface Shader의 구조

 

 

Surface Shader는 위의 이미지와 같이 Shader Lab이라는 스크립트 안에 CG코드가 들어가 있는 형식입니다. 

이거 하나만 배워두면  Vertex & Fragment Shader도 이해할 수 있고 랜더 몽키로도 갈 수 있고 노드로도 갈 수 있습니다. 공부는... 끝이 없는 법........

 

 

 

 

Surface Shader

 

가장 쉽고 멀티 플랫폼에 잘 대응되는 셰이더로 프로그래머가 아니더라도 배우기 쉬운 개념의 셰이더입니다.

 

Matrix 연산 등을 필요로 하지 않는데, 프로그래밍 책에 초반 부분을 죄다 건너뛰고 뒷페이지부터 시작하는 느낌인 샘.

Visual Shader Editor와 개념적으로 상당히 비슷하며, 고급 기술은 아쉽지만 자동이라는 점이 큰 장점입니다.

 

그래도 '매우 조금'의 C문법은 필요합니다. (기초문법, 변수, 함수, 조건문.. 정도)

 

 

 

 

자 이제 유니티로 가서 직접 만들어봅시다.

버전은 2019.3.9f1을 이용하였습니다.

 

가장 먼저 Create / Shader / Standard Surface Shader를 눌러 새로운 쉐이더를 생성해줍니다.

 

 

 

여기서 주의할 점은 바로 '이름'

처음 생성했을 때 이렇게 이름을 수정할 수 있게 표시된다.

처음 생성했을 때 이름을 수정해주지 않으면 Visual Studio에서 기본 이름으로 들어가게 되는데

이미 생성된 후라면 유니티 엔진에서 이름을 바꿔주어도 코드 안에서는 바뀌지 않습니다.

 

 

아무것도 수정하지 않고 생성했을 경우 다음과 같이 표시된다. 이 때 엔진 안에서 이름을 바꿔줘도 해당 경로와 이름은 바뀌지 않는다

 

생성시 이름을 바꿔줬을 경우 변경한 이름으로 적용되어있는 것을 확인할 수 있다.

물론 직접 코드를 수정해줄 수 있지만 처음 생성했을 때 꼬박꼬박 이름을 바꿔주는 습관을 가져봅시다.

 

 

셰이더를 더블클릭하고 코드를 확인하면..... 뭐라고 말하는 건지 하나도 모르겠습니다.

갑자기 눈이 침침해지는 감각이지만 차근차근 알아보도록 합시다!

 

우선 코드를 전체적으로 보았을 때 위와 같이 3가지 영역으로 나눠볼 수 있습니다.

 

 

 

Shader "Custom/NewSurfaceShader"

가장 상단의 이 문단은 Shrder가 시작한다는 것을 알려주며,

" "안에 문장은 위에서 언급했던 것처럼 셰이더의 경로와 이름을 나타냅니다.

그리고 이어지는 {} 중괄호는 하나의 큰 방으로 이해하시면 편합니다.

 

Shader라는 이름에 큰 방 안에 코드들이 들어가 구성을 이루고 있는 것이죠.

 

" "안의 경로와 이름은 여러분의 입맛에 맞게 커스텀이 가능합니다.

만약 내가 짜고 있는 게 몬스터에 관한 셰이더라면?

 

 

내가 작성한 대로 경로가 생성된 것을 확인할 수 있다.

이런 식으로 경로를 지정해주면 보다 깔끔하게 셰이더를 정리해줄 수 있습니다.

 

 

 

 

다음은 프로퍼티(Property) 방으로 들어가 봅시다~ 똑똑~~

 

유니티 쉐이더 그래프로 치면 블랙보드와 같은 공간입니다.

Unity 엔진 내부에서 변수들을 조작할 수 있는 인터페이스를 정해주는 곳이죠.

 

바로 여기!

 

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

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

 

다음은 프로퍼티스 블록 안에 들어가는 명령어의 몇 가지 예시입니다. 첫 줄을 대입해보면

 

 

모두 다음과 같은 구조를 갖고 있다.

 

가장 처음 보이는 '_Color'는 변수의 이름입니다.

겉으로 드러나는 이름이 아닌 프로그래밍 상에서 이 친구를 불러줄 이름이죠. 이러한 변수명은 대소문자를 구별하고, 숫자로 시작할 수 없으며 특수문자, 이미 존재하는 변수명 또한 사용할 수 없습니다. 한글도 안됩니다!

또한 변수명은 언더바(underbar) ' _ '로 시작해주는 것이 정석입니다. (무언의 규칙 같은 거)

 

 

 

괄호 안에 들어있는 "Color"는 프로퍼티에 표시되는 이름입니다.

 

이 친구들임

유일하게 한글(!)이 가능하지만... 플머들을 위해 영어로 작성해줍시다...

 

 

 

그다음으로는 Color, Range, 2D 등등 상황에 맞는 프로퍼티를 지정해줄 수 있습니다.

 

Range는 min 값과 max값을 지정하여 해당 범위 안에서만 값을 설정할 수 있도록 슬라이드 바로 표시됩니다.

Float은 소수점을 포함한 음수 양수를 지정할 수 있으며,

int는 원래 정수만 표시되어야 하는데.....

 

안됩니다.(ㅆㄹ...ㄱ...)

 

 

위의 Properties 방 안에서 작성한 내용은 '인터페이스'일뿐 작동하지 않습니다.

 

 

 

 

 

 

셰이더의 내용을 결정해주는 것이 바로 다음 방, SubShader입니다. 

 

// 로 시작하는 초록색 문장들은 모두 주석입니다. 시원하게 날려줍시다.

 

16번째 줄에 보면 CGPROGAM라는 단어가 눈에 들어옵니다.

이는 지금부터 CG코드를 이용하겠다는 선언문으로 CGPROGAM부터 ENDCG라고 쓰여있는 곳까지는 CG언어로 작성된다는 것을 의미합니다.

세미콜론이 붙어 있는 것이 보이시나요? 이제부터 '코드'입니다. 대소문자가 겁나게 중요하죠.

 

 

 

SubShader의 구조는 크게 3가지로 나눌 수 있는데요,

 

 

#pragma로 시작하는 첫 번째 박스는 '설정'으로 스니핏(snippet)이라고도 불립니다.

셰이더의 조명 계산 설정, 기타 세부적인 분기를 정해주며 전처리(Preprocessing)라고 할 수 있습니다.

 

예시로 #pragma target 3.0은 셰이더 3.0 버전으로 컴파일하라는 뜻.

 

 

struct Input은 'Input'이라는 이름을 가진 구조체로, '엔진으로부터 받아와야 할 데이터'들을 이 안에 넣습니다.

 

 

void '함수'입니다. 이곳이 지금 저희가 자세히 들여다볼 안방.

색상이나 이미지가 출력되는 부분을 만들어내는 곳으로 위에서 만들어 줬던 프로퍼티스가 정말 작동하도록 해주는 곳이 바로 여기입니다.

 

함수는 괄호 안에 들어가 있는 구조체를 안방 안으로 가지고 들어옵니다.

SurfaceOutputStandard라는 이름의 가방(구조체) 안에 여러 가지 변수가 들어있고, 그중 필요한 변수를 꺼내서 사용한다.라고 이해하시면 되겠습니다.

 

 

 

가방을 가져왔습니다.

 

 

C언어에서 void를 선언한 함수는 반환 값(return)을 가지지 않습니다. 뒤에 이어지는 surf는 함수의 이름.

 

inout.

SurfaceOutputStandard라는 가방이 있는데 그 안에 있는 것을 가지고 들어올 수도, 나갈 수도 있다는 선언문입니다.

 

하지만 일일이 SurfaceOutputStandard라는 명칭을 선언하기에는 너무 길죠.

마지막에 적혀있는 o는 저 길고 긴 이름 대신 o라고 부르겠습니다.라고 말하는 것.

 

SurfaceOutputStandard라는 이름의 구조체를 o라는 이름으로 입출력(inout)하겠습니다.라는 의미입니다.

 

 

 

해당 구조체는 유니티 내부에 자체적으로 들어있는 구조체로

이 밖에도 이미 정해져 있는 변수나 구조체들이 존재합니다. (유니티 매뉴얼 참고)

 

 

SurfaceOutputStandard 안에 들어있는 구성은 다음과 같습니다.

 

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

 

'B를 A에서 꺼내어 쓴다'는 온점을 이용하여 'A.B'로 표기합니다. '가방.꺼낼 물건'으로 기억합시다!

SurfaceOutputStandard에서 Albedo를 꺼내 쓰려면 SurfaceOutputStandard.Albedo로 표기해야 하는 건데...

이름이 너무 길죠.

아까 저 기다란 이름 대신 o라고 부르겠습니다!라고 선언한 것이 기억나시나요?

 

즉 저 길고 긴 단어 대신 o.Albedo로 짧고 간단하게 작성이 가능합니다. (WOW~)

 

 

 

 

셰이더 연산

 

그럼 이제 Albedo를 꺼내어 빨간 공을 만들어봅시다.

 

세미콜론(;)을 잊지 말자!

 

그림자가 있는 이유는 Albedo는 그림자를 연산하기 때문입니다.

이번에는 Emission을 넣어보도록 합시다.

 

알베도에 //붙여 주석으로 만들었다.

 

Emission은 빛이 난다는 것보다 '그림자를 연산하지 않는다'라는 표현이 더 맞습니다.

(빛은 그림자가 지지 않으니까 빛나는 것처럼 보인다!)

 

엔진에서 빛 연산은 정확하게 말하자면 '그림자를 만들어주는' 연산입니다.

깜깜한 어둠 속에서 빛을 만들어내는 것이 아닌,

아무런 그림자도 없는 공간에서 광원에 따른 그림자를 계산해 입혀주는 것.

 

 

 

float+float

 

여기서 float과 float을 더하면 어떤 일이 벌어질까요?

 

 

 

float3(0,1,0) + float3(0,0,1)을 하자 float3(0,1,1)이 되어 시안(Cyan)이 된 것을 확인할 수 있습니다.

이처럼 곱셈, 덧셈, 뺄셈, 나눗셈 등의 사칙연산이 모두 가능하다는 것을 알 수 있죠.

 

나눗셈의 경우 필요에 따라 사용하기도 하지만 연산이 무거워 대부분 곱셈을 대신 이용합니다.

 

 

이렇게 연산할 경우 반드시 float2는 float2, float3은 float3. 같은 자릿수끼리 연산을 해야 하는데요,

만약 다른 자릿 수의 float끼리 연산을 하게 되면 에러가 뜨고 맙니다.

 

 

마젠타 컬러가 아닌 '흰색 오류'가 뜬다.

 

위와 같은 흰색 오류는 유니티가 '이거... 원래 안되는데 일단 노력은 해 봤어.....'라는 의미입니다.

 

 

예외로 한 자릿수 float의 경우엔 어디에도 붙일 수 있습니다. 모든 자릿수에 연산이 적용되기 때문이죠.

예를 들어 float3( 1, 0, 0 ) + 1의 연산 결과는 float3( 2, 1 , 1 )로,

모니터로 확인할 수는 없지만 빨간색이 더 많이 들어가 있습니다.

 

이 상태에서 포스트 프로세싱(Post Processing)의 블룸(Bloom) 효과를 적용하면

붉은색으로 빛나는 걸 확인할 수 있습니다.

 

반대로 검정보다 더 어두운 색(-1)도 가능하다. HDR

 

 

그렇다면 float에 float3을 뺀다면 어떻게 될까요?

1 - float3(1, 0, 0)는 float3(1, 1, 1) - float3(1, 0, 0)과 같습니다.

계산을 해보면 결과는 float3(0, 1, 1). 값이 반전되었습니다.

 

 

잠깐, 빨간색에서 시안.... 많이 익숙합니다. 

 

 

포토샵에서 빨간색을 반전하면 시안컬러가 된다.

 

 

빨간색의 보색은 초록색인데 왜 색상을 반전하면 하늘색이 나올까?라고 생각해보신 적 있으신가요?

사실 포토샵은 커다란 쉐이더 덩어리였습니다. (1,0,0)을 뒤집으면 (0,1,1)이 되니 하늘색이 되었던 것!

 

이는 쉐이더에서 많이 쓰이는 기법으로 'one minus' 바로 '반전(invert)'입니다.

 

 

 

변수

 

색상을 불러올 때마다 일일이 float3로 불러온다면 솔직히 좀(많이) 귀찮을 겁니다.

이럴 때 사용할 수 있는 것이 바로 변수 선언입니다.

 

특정 코드에 이름표를 붙여주어 일일히 코드를 작성하지 않아도 그 이름만 불러주면 해당하는 코드의 정보가 들어가게 되는 것이죠. 이는 색상뿐만 아니라 반복하여 사용하는 다른 코드에도 부여하여 보다 빠르고 깔끔하게 코드를 정리할 수 있게 해 줍니다.

 

음식을 주문할 때 달걀 세 개 풀어서 소금 넣고 우유 넣은 다음 포슬포슬하게 구워 밥 위에 얻은 뒤 파슬리 총총 뿌린 요리 주세요.라고 일일이 말하지 않고 오므라이스 주세요! 하면 저 길고 긴 문장과 같은 요리가 나오는 것과 같습니다. 

 

 

이제 본격적으로 변수 선언을 해볼까요?

 

각각 R, G, B라는 이름이 붙었다!

 

정말 이름표가 잘 붙었는지 확인해 봅시다.

o.Emission에 float3을 넣지 않고 R을 넣었을 때 빨간색이 나온다면 성공입니다.

 

성공~

이뿐 만 아니라 변수의 이름만으로 사칙연산을 하는 것도 가능합니다.

 

float3(1,0,0)+float3(0,1,0) = float3(1,1,0) 노란색이 잘 나왔다.

 

이때 R = G, R은 G라고 선언해버린다면 어떻게 될까요?

 

o.Emission = R을 넣었지만 초록색이 나왔다! R=G 이라고 선언했기 때문.

 

 

코드는 위에서부터 천천히 계산해 내려갑니다.

때문에 위 쪽에서 R은 빨간색이라고 선언하였어도 아래에서 R = G라고 말해버렸기 때문에 결과적으론 초록색이 출력되는 거죠.

 

때문에 o.Emission = R; 아래에 R = G;라고 작성한다면 빨간색이 출력됩니다.

 

 

 

 

 

스위즐링(Swizzling) 기법

 

float3를 예로 들어서,

float의 각 자릿수 (number, number, number)는 각각 r, g, b / x, y, z와 동일합니다.

플롯의 단위가 바뀌면 여기서 빠지거나 추가(a / w) 되는 것.

 

위에서 설명했던 온점의 기능을 기억하시나요?  ' 가방.꺼낼물건 '

 

R에서 r, g, b를 한 번 꺼내봅시다.

 

 

빨간색이 아주 잘 출력되고 있다.

 

여기서 만약 rgb를 gbr로 섞어 버리면?

 

분명 R(1,0,0)가방에서 꺼내왔는데 파란색이 됐다.

 

그렇다면 R.rrr은 흰색이 되고 R. bgg는 검은색이 됩니다.

 

이렇게 각 채널을 섞어 다른 결과를 만들어 내는 것을 스위즐링(Swizzing) 기법이라고 합니다. 

이러한 방법을 사용할 때 반드시 전체의 자리를 rgb로 표현하지 않아도 되는데.

즉,

 

이 것도 되고
이것도 되고
이런 것도 된다

 

 

위에서 언급했듯이 이는 x, y, z도 똑같이 적용이 되는데.....

r, g, b = x, y, z 우리는 유니티 엔진 안에서 수도 없이 많이 봐왔습니다.

 

 

바로 이렇게