유니티 셰이더 강좌 #6
출처 : http://jinhomang.tistory.com/entry/%EC%9C%A0%EB%8B%88%ED%8B%B0-%EC%85%B0%EC%9D%B4%EB%8D%94%EC%9D%98-%EA%B8%B0%EC%B4%88-6
---
안녕하세요, 흑기사입니다.
(개인휴가, 명절 연휴, 컨퍼런스가 연이어 발생하는 바람에 부득이하게 오랜 기간동안 글을 포스팅하지 못하게 되었네요…;)
이전까지 유니티에서 셰이더를 제작하는 방법 중에서 fixed function shader를 작성하는 법을 알아보았습니다. 물론 동시에 ShaderLab의 기본을 익히는 시간도 가졌습니다. 고정 파이프 라인을 사용하는 fixed function shader의 특징을 다시금 짚어보자면:
-버텍스 당 라이팅 연산이 이루어짐.
-커스터마이징 불가.
-하지만 구형 디바이스에서도 동작하는 장점이 있음.
이 fixed function shader은 잘 사용하면 의외로 많은 쓸모가 있는 셰이더 작성 방식입니다. 따라서 많은 여러가지 예제를 접해보면 도움이 많이 됩니다. 이러한 예제들은 다음에 기회가 된다면 알아보는 시간을 가지도록 하겠습니다.
이번 시간에는 유니티에서 셰이더를 작성하는 또 다른 방법인 vertex/fragment program에 대해서 알아보겠습니다.
앞서 고정 파이프 라인을 사용하는 fixed function shader의 특징을 짚어본 이유가 바로 이번에 다룰 셰이더는 프로그래머블 파이프라인을 이용하는 방식으로서 서로 대조적인 방식이기 때문입니다. 프로그래머블 파이프라인하면 가장 먼저 떠오르는 것은 '프로그래머블' 하다는 것입니다. 즉, 프로그래머가 그래픽 파이프라인에서의 버텍스 레벨과 프래그먼트 레벨(픽셀 레벨)에 각각 개입하여 자기 마음대로 커스텀한 함수들을 제작할 수 있다는 것입니다.
사실 이것은 이미 예전부터 게임개발을 하셨던 분들에게는 아주 익숙한 셰이더 작성법입니다. vertex/fragment program 라는 타이틀보다는 'vertex&pixel shader' 라고 부르는 것에 더 친숙할 수도 있지만, 그냥 명칭의 차이일 뿐이며 동일한 것으로 생각하면 됩니다.
이러한 셰이더를 작성하기 위해서는 우리는 별도의 셰이더 언어를 사용하여야 합니다. 그런데 셰이더 언어는 한가지가 아닙니다. 예를 들어 OpenGL에 포함된GLSL, DirectX에 포함된 HLSL, NVIDIA에서 개발한 Cg 등이 있습니다. 이처럼 그래픽API를 어떤 것을 사용하느냐에 따라서 다른 셰이딩 언어를 사용해야 하는 경우가 발생할 수도 있습니다. (Cg의 경우는 OpenGL과 DirectX 모두에 호환됩니다.)
자, 그러면 이러한 vertex/fragment program을 유니티에서 작성하기 위해서는 어떻게 해야 할지 알아보겠습니다. 그런데 그 전에 먼저 언급해야 할 것이 있습니다.
vertex/fragment program 자체의 제작 방식은 매우 일반적으로 언급되는 프로그래머블 커스텀 셰이더 작성법입니다. 이 글에서는 단지 유니티 엔진에서 이 셰이더 방식을 '적용'하려면 어떻게 해야 하느냐에 대한 것일 뿐, vertex/fragment program의 근본적인 개념과 작성법을 설명하지는 않을 것입니다. 따라서 이에 관련한 프로그래머블 파이프 라인의 개념이나 셰이딩 언어의 문법 등에 대해서는 (혹시 필요한 분들은) 직접 학습이나 리서치하시길 권장합니다.
그럼 이제 정말 유니티에서 vertex/fragment program 를 어떻게 작성하는지 알아보겠습니다.
(여기서 우리는 vertex/fragment program의 제작을 위해서 Cg를 사용하도록 합니다. 그 이유는 나중에 다시 언급하도록 하겠습니다.)
사실 우리가 실제로 필요한 것은 바로 버텍스 프로그램(버텍스 셰이더)와 프래그먼트 프로그램(픽셀 셰이더), 이렇게 두 개의 셰이더 함수를 Cg로 작성하는 것뿐입니다. 이 얘긴 결국 유니티 셰이더의 나머지 부분은 앞서 익혔던 ShaderLab 문법을 이용하여 그대로 작성할 수 있으며, 단지 버텍스/프래그먼트 셰이더 함수에 해당하는 부분만 Cg로 작성해서 끼워넣기만 하면 목표달성이라는 뜻입니다. 글로 표현하려니 뭔가 이상하군요.. 그냥 실제 코드를 보자면:
Shader "MyCustomShader" {
SubShader {
Pass {
// ...여러가지 pass관련 설정…
CGPROGRAM
// Cg code ...
ENDCG
// ...그밖의 pass관련 설정…
}
}
}
음, 이미 우리가 알고 있는 셰이더의 모습과 많은 차이는 없군요(다행입니다...).차이점이라면 CGPROGRAM과 ENDCG라는 키워드가 있는 부분이 새롭게 등장했다는 것입니다. 아마 당연히 이 부분이 vertex/fragment program에 대한 것이라고 느껴지실 것입니다.
즉, 우리는 CGPROGRAM 와 ENDCG 키워드 사이에서 vertex/fragment program을 Cg언어로 작성할 것입니다. 이렇게 작성된 Cg조각은 유니티 엔진에 의해서 각각의 플랫폼들에 대해 적절히 동작하도록 자동컴파일됩니다. 이전에 익혔던 fixed function shader의 외형과 비교하자면, 단지 CGPROGRAM~ENDCG 블럭이 추가되었다는 것 외에는 크게 변한 점이 없습니다. 절대 복잡하지 않다는 점을 다시 한번 강조합니다.
이 블럭부분을 한번 자세히 들어가 볼까요.
CGPROGRAM ~ ENDCG 블럭에는 순수 Cg코드 외에 여러 컴파일 지시자를 포함할 수 있습니다. 거의 기본적으로 포함되는 지시자는 바로 #pragma vertex 과 #pragma fragment 라는 지시자입니다:
CGPROGRAM
#pragma vertex vertfunc
#pragma fragment fragfunc
// 여기에 Cg code
ENDCG
이것을 해석해보자면, vertfunc 라는 이름을 가진 함수가 버텍스 프로그램 (셰이더 함수)이며, fragfunc 라는 이름을 가진 함수는 프래그먼트 프로그램 (셰이더 함수)라는 것을 가리키는 것입니다. 그리고 이 vertfuc와 fragfunc 함수들을 Cg코드로 작성될 것을 의미합니다. 아주 간단합니다.
그밖의 유용한 컴파일 지시자들 중에는 #pragma target, #pragma only_renderers, #pragma exclude_renderers등이 있습니다.
#pragma target 은 어떤 버전의 셰이더 모델로 컴파일할 지를 지정할 때 쓰입니다.
유니티는 기본적으로 셰이더 2.0 버전에 맞추어 컴파일합니다. 만약 다른 버전으로 컴파일을 원한다면 아래의 예와 같이 지정할 수 있습니다:
#pragma target 3.0
이것은 셰이더 3.0 버전으로 컴파일을 하라는 뜻이겠죠.
그리고 유니티는 지원가능한 렌더러들(d3d9, OpenGL 등등)에 대해서 모든 셰이더들을 컴파일하게 되는데요. #pragma only_renderers와 #pragma exclude_renderes를 이용하여 특정 렌더러들에 대해서만 컴파일을 하거나, 특정 렌더러들에 대해서는 컴파일하지 않도록 할 수 있습니다. 현재 지원되는 렌더러들은 d3d9, d3d11, opengl, gles, xbox360, ps3, flash 입니다.
한가지 예를 들면:
#pragma exclude_renderers xbox360 ps3
이러면 xbox360과 ps3 렌더러에 대해서는 해당 셰이더에 대한 컴파일을 하지 않도록 합니다.
그 밖에도 셰이더를 작성하다보면 여러 컴파일 지시자들을 사용하게 되는데요, 이들에 대해서는 그것들이 등장할 때마다 알아보도록 하겠습니다.
그럼 오늘 언급한 내용을 모두 포함한 셰이더의 모습을 살펴보겠습니다:
Shader "MyCustomShader" {
SubShader {
Pass {
// ...
CGPROGRAM
#pragma vertex vertfunc
#pragma fragment fragfunc
#pragma target 3.0
#pragma exclude_renderers xbox360 ps3
// add Cg code
ENDCG
// ...
}
}
}
오늘은 여기까지입니다~
다음에는 유니티에서의 실제 간단한 vertex/fragment program의 예를 알아보도록 하겠습니다.
감사합니다~