금일은 요즘 유니티를 사용하면서 문제점에 부디친 URP 때문에
유니티 쉐이더를 다시 들여다 보려고 합니다.(역시 기본을 아는 것이 가장 중요한 것 같습니다.)

기존의 유니티 쉐이더는 3가지 방식으로 작성될 수 있었습니다.

ShaderLab, Surface Shader 그리고
전통적인 방식인 vertex, fragment 쉐이더 방식으로 작성되어오고 있습니다.

작성법도 편하고 빛계산도 비교적 수월한 Surface 쉐이더를 많이 사용하고 있는 것 같은데요 최근의 URP 파이프라인으로 넘어오면서는 전통적인 HLSL
(vertex, fragment )방식으로 쉐이더가 작성되고 있습니다. (물론 쉐이더그래프를 지원하며 더 직관적인것 같긴 하지만…. 그 원리를 모른다면 매번 문제에 직면 하는 것 같습니다.)
그래서 이전의 vertex, fragment 쉐이더방식을 처음부터 차근차근 되짚어 보려고 합니다.

그 중에서 모든 쉐이더 공부의 첫 시작은 빨갱이 쉐이더인 것 같아서
버텍스쉐이더와 프래그먼트쉐이더를 사용해서 빨강 쉐이더를 만들어 보려고 합니다. (URP이전의 쉐이더 입니다.)

정말 간결하고 아름답습니다.
왜 아름다운지 그 구조부터 들여다 보겠습니다. 유니티 쉐이더의 구조는

좌측 그림처럼 Shader 안에 Properties(속성) 안에 SubShader (여러개가 존재할수 있습니다.) 그리고 마지막에 fallback 이라는 구조로 구성되어 있습니다.(참조 https://jinhomang.tistory.com/43)

그런데 왜 서브쉐이더라는 것이 존재하며 여러개 존재할 수 있을까요?
바로 하드웨어의 성능 때문이라고 합니다.
하드웨어 마다 다양한 성능을 가지고 있기때문에 거기에 적합한 쉐이더들을 각각의 서브쉐이더에 작성을 해둔다고 합니다.

유니티가 렌더링을 할때 서브쉐이더를 들여다 보면서 가장 위의 서브쉐이더(가장 최상품질)부터 찾아본후 그에 적합한 서브쉐이더를 적용하게 되며
마지막까지 적합한 스펙이 없으때 fallBack 이라는 부분에 적혀 있는 쉐이더를 사용하게 된다고 합니다.

https://jinhomang.tistory.com/43 흑기사님의 글이 정말 주옥같습니다.!

그리고 서브쉐이더 안에는 여러개의 pass가 존재하는데요 오브젝트의 렌더링되는 단위라고 합니다. 즉 한번만 그리는 것이 아니라 pass를 통해 여러번 단계로 그릴수 있다는 것을 의미합니다.

자 이제 빨강쉐이더로 돌아가 보겠습니다.

Shader “CustomRout/CustomShader”{
// 쉐이더의 이름과 경로를 정해주게 됩니다. 쉐이더 선택창에 CustomRout 안에 CustomShader로 보여집니다.
Properties{
_Color(“MainColor”, Color) = (1,1,1,1)
}
// 속성을 적용할수 있는 부분이며 메터리얼 창에 나타나게 됩니다. 메터리얼 창에 MainColoe 로 보여지며 디폴트값은 1,1,1,1 즉 화이트로 보여지게 됩니다.

//서브쉐이더가 시작됩니다.
SubShader
{
Tags { “RenderType”=”Opaque” } // 렌더타입을 적어줍니다. 이후에 알아봅니다.

LOD 100

// 패스가 시작됩니다.
Pass
{

// 실직적인 쉐이더 프로그래밍은 CGPROGRAM ~ ENDCG 안에서 이루어 집니다.
CGPROGRAM
#pragma vertex vert
#pragma fragment frag

// 버텍스 함수는 vert라는 함수가 사용될것이다
// 프래그먼트 함수는 frag 라는 함수가 사용될것이다, 즉 vert 와 frag는 사용자가 이름을 바꾸어줄 수도 있다는 뜻입니다.

struct vertexInput
{
float4 vertex : POSITION;
};

//버텍스 입력을 받는 구조체입니다.
float4 형식의 vertex 가 POSITION 값을 가지고 있습니다.

struct vertexOutput
{
float4 pos : SV_POSITION;
};

//버텍스 출력을 하는 구조체입니다. float4 형식의 pos 가 SV_ POSITION 값을 가지고 있습니다. SV_POSITION 은 버텍스의 위치를 출력해준다라고 생각하시면 됩니다.

float4 _Color;

//프라퍼티에 적용하였던 값이며 꼭 한번은 선언을 해주어야 작동하게 됩니다.

vertexOutput vert (vertexInput v)

{
vertexOutput o;
o.pos = UnityObjectToClipPos(v.vertex);

// 버텍스함수입니다.

버텍스 인풋구조체 v의 값을 인자로 가집니다.
o의 오브젝트 공간의 위치를 v의 버텍스위치에서 클립공간으로 바꾸어줍니다.

오브젝트 공간의 한 점을 동질적인 좌표에 있는 카메라의 클립 공간으로 변환합니다. mul(UNITY_MATRIX_MVP, float4(pos, 1.0))과 같으며, 이를 대신하여 사용해야 합니다.
로컬 공간(오브젝트 공간) 의 버텍스를 클립공간으로 변환합니다. 기본적으로 공간변환은 로컬 — 월드 — 뷰 — 프러스텀인데, 이걸 모델-뷰-프러스텀이라고 부르면서 MVP라고 부릅니다. 그런데 프러스텀은 니어와 파로 잘라지죠? 그러면 절두체 모양이 됩니다. 일종의 찌그러진 큐브죠. 이 큐브 영역을 0~ 1사이로 변환한 것이 클립 포지션이라 합니다. 즉 최종 영역까지 한 번에 변환하는 함수입니다.

출처: https://chulin28ho.tistory.com/539 [대충 살아가는 게임개발자]

return o; // o구조체를 반환홥니다.
}

//프래그먼트 함수입니다.

fixed4 frag (vertexOutput i) : SV_Target

{

float4 col = float4(1.0,0.0,0.0,1.0);

return col;

//레드컬러를 반환합니다.

}

ENDCG

}

}

}

이상으로 기본적인 짧은 unlit 빨강 쉐이더를 알아보았습니다.
(참고 unlit 은 unlighting 즉 빛이 적용되지않는다! 라는 뜻입니다.

 

 

ref : https://medium.com/@p5js/unity-vertex-fragment-shader01-%EB%B9%A8%EA%B0%95-%EC%89%90%EC%9D%B4%EB%8D%94-a3fdebbc0c8f

반응형

+ Recent posts