유니티에서 다른 타입의 쉐이더를 멀티 패스로 통합하기 / Unity merging different type shaders using multi pass
유니티에서 Shader를 만드는 방식은 세 가지가 존재한다.
1) Fixed Function Shader
Fixed Function Shader는 Programmable Shader를 지원하지 않는 낮은 사양의 하드웨어를 대응하기 위해 주로 사용한다. 만들 때는 ShaderLab이라는 Unity 내부에서 정한 언어를 사용하게 된다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
Shader "MyShader" { Properties { _MyTexture ("My Texture", 2D) = "white" { } // other properties like colors or vectors // go here as well } SubShader { // here goes the 'meat' of your // - surface shader or // - vertex and fragment shader or // - fixed function shader } SubShader { // here goes a simpler version of the SubShader above that // can run on older graphics cards } } |
2) Vertex/Fragment Shader
Vertex/Fragment Shader는 주로 CG라는 nVidia에서 만든 언어로 만들어진다. 가장 Flexible한 쉐이더 프로그래밍 방식으로 하드웨어가 지원하는 한 다른 모든 쉐이더가 하는 것을 할 수 있는 쉐이더다. 다만 light 관련 함수를 다루게 될 때 Surface Shader로 만드는 것보다 난이도가 올라가고 코드가 길어진다.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
Pass { // ... the usual pass state setup ... CGPROGRAM // compilation directives for this snippet, e.g.: #pragma vertex vert #pragma fragment frag float4 vert(){} float4 frag(){} ENDCG // ... the rest of pass setup ... } |
3) Surface Shader
사실 Shader 프로그래밍의 대부분은 빛의 값을 적절하게 변경하거나 조정하는 목적으로 만들어지는 경우가 많다. 이런 경우 Surface Shader가 가장 적합하다. 빛을 계산하는 함수를 아예 따로 빼줘서 간단히 원하는 값들을 조정할 수 있다. BlinnPhong, Lambert 같은 유니티 내장 빛 연산 함수도 바로 사용할 수 있다. 빛을 크게 다루지 않는다면 vertex/fragment Shader로 만드는 것이 더 간결한 코딩이 나올 수 있다. Surface Shader는 vertex/fragment shader로 내부적으로 변환된다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
Shader "Example/Diffuse Simple" { SubShader { Tags { "RenderType" = "Opaque" } CGPROGRAM #pragma surface surf Lambert struct Input { float4 color : COLOR; }; void surf (Input IN, inout SurfaceOutput o) { o.Albedo = 1; } ENDCG } Fallback "Diffuse" } |
만약 Shader를 Multi-Pass로 그리게 된다면 하나의 쉐이더 파일에 여러개의 Shader Pass를 섞어야 하는 경우가 자주 발생한다. 가령 Shadow나, Outline 같은 것. 이럴 때 어떻게 쉐이더를 구성해야 하며 동작은 잘 보장될까 라는 생각이 든다.
1) Vertex/Fragment Shader는 Multi-Pass로 합칠 수 있다.
뭐 당연한 이야기지만 가능하다. Vertex/Fragment Shader를 여러 패스로 합칠 경우, 첫번째 Pass서 적용된 값들이 두번째 패스에서 그 상태값을 그대로 이용할 수 있고 여러 Pass로 붙일 수 있다. 아래 예제 Shader를 적용해 보면 앞쪽의 패스에서 R값이 뒷쪽 패스에 그대로 전달되는 것을 볼 수 있다.
Test Code >>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 |
//Multi-Pass Shader Test. Rapapa.net Shader "Test/Multi-Pass" { Properties { } SubShader { Tags {"Queue" = "Geometry" "RenderType" = "Opaque" } //////////////////////////////////////////////////////// //Vertex-Fragment Functionality shader - RED // //////////////////////////////////////////////////////// Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag struct vertexInput { float4 vertex : POSITION; float4 color : COLOR; }; struct vertexOutput { float4 pos : POSITION; float4 color : COLOR; }; vertexOutput vert(vertexInput v) { vertexOutput o; o.pos = mul(UNITY_MATRIX_MVP, v.vertex); o.color = v.color; return o; } fixed4 frag( vertexOutput i) : COLOR { return fixed4(1, 0, 0, 1); } ENDCG } //////////////////////////////////////////////////////// //Vertex-Fragment Functionality shader - GREEN // //////////////////////////////////////////////////////// Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag struct vertexInput { float4 vertex : POSITION; float4 color : COLOR; }; struct vertexOutput { float4 pos : POSITION; float4 color : COLOR; }; vertexOutput vert(vertexInput v) { vertexOutput o; o.pos = mul(UNITY_MATRIX_MVP, v.vertex); o.color = v.color; return o; } fixed4 frag( vertexOutput i) : COLOR { return fixed4(i.color.r, 1, 0, 1); } ENDCG } } Fallback "Diffuse" } |
2) Surface Shader와 Vertex/Fragment Shader는 Multi-Pass도 합칠 수 있다.
원래 4.x 버젼 Unity에선 지원되지 않았다. 최근 5.0으로 업그레이드 된 이후 테스트 해보니 잘 동작한다.
Test Code >>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 |
//Multi-Pass Shader Test. Rapapa.net Shader "Test/Multi-Pass" { Properties {} SubShader { Tags {"Queue" = "Geometry" "RenderType" = "Opaque" } //////////////////////////////////////////////////////// //Vertex-Fragment Functionality shader - RED // //////////////////////////////////////////////////////// Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag struct vertexInput { float4 vertex : POSITION; float4 color : COLOR; }; struct vertexOutput { float4 pos : POSITION; float4 color : COLOR; }; vertexOutput vert(vertexInput v) { vertexOutput o; o.pos = mul(UNITY_MATRIX_MVP, v.vertex); o.color = v.color; return o; } fixed4 frag( vertexOutput i) : COLOR { return fixed4(1, 0, 0, 1); } ENDCG } //////////////////////////////////////////////////////// //Surface Shader - GREEN // //////////////////////////////////////////////////////// CGPROGRAM #pragma target 3.0 #pragma surface surf BlinnPhong struct Input { float4 color : COLOR; }; void surf (Input IN, inout SurfaceOutput o) { o.Albedo = float3(IN.color.r, 0, 1); } ENDCG } Fallback "Diffuse" } |
3) Surface Shader와 Surface Shader의 Multi-Pass도 가능하다.
Surface Shader를 Multi Pass로 합치는 것도 가능하다. 다만, 특이하게도 Pass라는 Keyword를 제거해야 한다. 그냥 두 Surface를 한번에 연결되게 써주면 된다.
Test Code >>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
//Multi-Pass Shader Test. Rapapa.net Shader "Test/Multi-Pass" { Properties {} SubShader { Tags {"Queue" = "Geometry" "RenderType" = "Opaque" } //////////////////////////////////////////////////////// //Surface Shader - RED // //////////////////////////////////////////////////////// CGPROGRAM #pragma target 3.0 #pragma surface surf BlinnPhong struct Input { float4 color : COLOR; }; void surf (Input IN, inout SurfaceOutput o) { o.Albedo = float3(1, 0, 0); } ENDCG //////////////////////////////////////////////////////// //Surface Shader - GREEN // //////////////////////////////////////////////////////// CGPROGRAM #pragma target 3.0 #pragma surface surf BlinnPhong struct Input { float4 color : COLOR; }; void surf (Input IN, inout SurfaceOutput o) { o.Albedo = float3(IN.color.r, 1, 0); } ENDCG //The result is yellow! } Fallback "Diffuse" } |
4) Fixed Function Shader와 Surface, 혹은 Vertex/Fragment Shader의 조합은 제대로 동작하지 않는다. 각각 문법적으로 Compile 시 에러를 발생시키는 것은 아니지만, 조합한 결과 각각 독립적으로 적용되어 결과 값이 의도한 대로 나오지 않는 것을 확인할 수 있다.
Test Code >>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
//Multi-Pass Shader Test. Rapapa.net Shader "Test/Multi-Pass" { Properties {} SubShader { Tags {"Queue" = "Geometry" "RenderType" = "Opaque" } //////////////////////////////////////////////////////// //Fixed Functionality shader - RED // //////////////////////////////////////////////////////// Pass { Color (1, 0, 0, 1) } //////////////////////////////////////////////////////// //Surface Shader - GREEN // //////////////////////////////////////////////////////// CGPROGRAM #pragma target 3.0 #pragma surface surf BlinnPhong struct Input { float4 color : COLOR; }; void surf (Input IN, inout SurfaceOutput o) { o.Albedo = float3(0, 1, 0); } ENDCG } Fallback "Diffuse" } |
Sample Source : Link
Reference :
http://www.3dgep.com/an-introduction-to-unity-shaders/
http://albertshih.blogspot.kr/2014/11/rules-for-multi-pass-shaders-in-unity.html