Unity Material Property Drawer – 客制化材质编辑器
发表于2017-03-16
材质球 Material,是在 Unity 中调整画面呈现的一个必备元素,透过材质球可以相当轻易的达到可见即所得的效果,下面就给大家介绍下Unity中的客制化材质编辑器。
在 Unity 环境下撰写 Shader 后,会透过材质球选取我们所完成的 Shader 并且在 Inspector 中显示可调整的参数,但是这些参数往往无法满足我们的需求,这时候就会针对特定需求来进行调整。
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 | Shader "Custom/Normal" { Properties { _Int( "Int" , Int) = 1 _Range( "Range" , Range(0, 1)) = 1 _Color( "Color" , Color) = (1, 1, 1, 1) _Vector( "Vector" , Vector) = (1, 1, 1, 1) _Cube( "Cube" , Cube) = "white" {} _2D( "2D" , 2D) = "white" {} _3D( "3D" , 3D) = "white" {} } SubShader { Tags { "RenderType" = "Opaque" } Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" struct appdata { float4 vertex : POSITION; }; struct v2f { float4 vertex : SV_POSITION; }; float4 _Color; v2f vert (appdata v) { v2f o; o.vertex = UnityObjectToClipPos(v.vertex); return o; } fixed4 frag (v2f i) : SV_Target { return _Color; } ENDCG } } } |
MaterialPropertyDrawer
为 Unity ShaderLab 中的一种扩充语法(Syntax),用法相当的轻便且容易上手,可以透过这种语法方便的进行材质编辑器的扩充,也是这次要探讨的主题。在 MaterialPropertyDrawer 中,Unity 提供了以下几种扩充语法 Header、Space、Toggle、Enum、KeywordEnum、PowerSlider 以及 IntRange,下面就来依次说明。
PS: 本次的环境为 Unity 5.5.0f3,所有说明皆为用法范例,并无特殊功能。
Header
与 HeaderAttribute 一样,透过包装宣告参数,来达到编排版面的作用。
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 | Shader "Custom/Header" { Properties { [Header(This is the first Header)] _FirstColor( "First Color" , Color) = (1, 1, 1, 1) [Header(This is the second Header)] _SecondColor( "Second Color" , Color) = (1, 1, 1, 1) } SubShader { Tags { "RenderType" = "Opaque" } Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" struct appdata { float4 vertex : POSITION; }; struct v2f { float4 vertex : SV_POSITION; }; float4 _FirstColor; float4 _SecondColor; v2f vert (appdata v) { v2f o; o.vertex = UnityObjectToClipPos(v.vertex); return o; } fixed4 frag (v2f i) : SV_Target { fixed4 col = _FirstColor + _SecondColor; return col; } ENDCG } } } |
Space
与 SpaceAttribute 一样,在两参数之间安插任意垂直空间,来达到编排版面的作用。
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 | Shader "Custom/Space" { Properties { [Header(This is the first Header)] _FirstColor( "First Color" , Color) = (1, 1, 1, 1) [Space] [Header(This is the second Header)] _SecondColor( "Second Color" , Color) = (1, 1, 1, 1) [Space(50)] [Header(This is the third Header)] _ThirdColor( "Third Color" , Color) = (1, 1, 1, 1) } SubShader { Tags { "RenderType" = "Opaque" } Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" struct appdata { float4 vertex : POSITION; }; struct v2f { float4 vertex : SV_POSITION; }; float4 _FirstColor; float4 _SecondColor; v2f vert (appdata v) { v2f o; o.vertex = UnityObjectToClipPos(v.vertex); return o; } fixed4 frag (v2f i) : SV_Target { fixed4 col = _FirstColor + _SecondColor; return col; } ENDCG } } } |
Toggle
以触发器型态表示浮点数,可以用来处理开关变换的过程,需要注意的是,在宣告参数 _Invert 后,必须额外使用 #pragma shader_feature 定义 _INVERT_ON(大写参数名 + _ON)来作为配套使用,这边就用简单的负片效果当作示范。
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 | Shader "Custom/Toggle" { Properties { _Color ( "Color" , Color) = (1, 1, 1, 1) [Toggle] _Invert( "Invert?" , Float) = 0 } SubShader { Tags { "RenderType" = "Opaque" } Pass { CGPROGRAM #pragma shader_feature _INVERT_ON #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" struct appdata { float4 vertex : POSITION; }; struct v2f { float4 vertex : SV_POSITION; }; float4 _Color; v2f vert (appdata v) { v2f o; o.vertex = UnityObjectToClipPos(v.vertex); return o; } fixed4 frag (v2f i) : SV_Target { fixed4 col = _Color; #if _INVERT_ON col = 1 - col; #endif return col; } ENDCG } } } |
Enum
Enum 是一般在撰写 Scripts 时常常见到的功能,而在 ShaderLab 中也一样支援了这个语法,这边使用切换 Culling Mode 来当作范例。透过这种切换方式,就能够单纯地利用 Material 的生成,来切换各种不同型态的 Culling Mode.
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 | Shader "Custom/Enum" { Properties { _Color( "Color" , Color) = (1, 1, 1, 1) [Enum(UnityEngine.Rendering.CullMode)] _CullMode ( "Cull Mode" , Float) = 0 } SubShader { Tags { "RenderType" = "Opaque" } Pass { Cull [_CullMode] CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" struct appdata { float4 vertex : POSITION; }; struct v2f { float4 vertex : SV_POSITION; }; float4 _Color; v2f vert (appdata v) { v2f o; o.vertex = UnityObjectToClipPos(v.vertex); return o; } fixed4 frag (v2f i) : SV_Target { fixed4 col = _Color; return col; } ENDCG } } } |
KeywordEnum
与 Enum 相似,可以自定义枚举名称,需要注意在参数宣告后,需与 #pragma multi_compile 配套使用(大写参数名 + 大写枚举名)。
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 | Shader "Custom/KeywordEnum" { Properties { [KeywordEnum(Red, Green, Blue)] _ColorMode ( "Color Mode" , Float) = 0 } SubShader { Tags { "RenderType" = "Opaque" } Pass { CGPROGRAM #pragma multi_compile _COLORMODE_RED _COLORMODE_GREEN _COLORMODE_BLUE #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" struct appdata { float4 vertex : POSITION; }; struct v2f { float4 vertex : SV_POSITION; }; v2f vert (appdata v) { v2f o; o.vertex = UnityObjectToClipPos(v.vertex); return o; } fixed4 frag (v2f i) : SV_Target { fixed4 col = fixed4(0, 0, 0, 1); #if _COLORMODE_RED col.r = 1; #elif _COLORMODE_GREEN col.g = 1; #elif _COLORMODE_BLUE col.b = 1; #endif return col; } ENDCG } } } |
PowerSlider
使用上与原始的 Range(min, max) 大同小异,唯一的差别是滑动条上的数值不再是线性变化。
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 | Shader "Custom/PowerSlider" { Properties { [PowerSlider(3.0)] _Shininess ( "Shininess" , Range (0.01, 1)) = 0.08 } SubShader { Tags { "RenderType" = "Opaque" } Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" struct appdata { float4 vertex : POSITION; }; struct v2f { float4 vertex : SV_POSITION; }; float4 _Color; v2f vert (appdata v) { v2f o; o.vertex = UnityObjectToClipPos(v.vertex); return o; } fixed4 frag (v2f i) : SV_Target { fixed4 col = _Color; return col; } ENDCG } } } |
IntRange
使用上与原始的 Range(min, max) 大同小异,唯一的差别是滑动条上的数值为整数变化。
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 | Shader "Custom/IntRange" { Properties { [IntRange] _Alpha ( "Alpha" , Range (0, 255)) = 100 } SubShader { Tags { "RenderType" = "Opaque" } Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" struct appdata { float4 vertex : POSITION; }; struct v2f { float4 vertex : SV_POSITION; }; float4 _Color; v2f vert (appdata v) { v2f o; o.vertex = UnityObjectToClipPos(v.vertex); return o; } fixed4 frag (v2f i) : SV_Target { fixed4 col = _Color; return col; } ENDCG } } } |
结语
透过 MaterialPropertyDrawer 虽然可以相当简单的製作出属于自己的材质编辑器,但製作简单也就意味著扩充性的不足,单独依靠 MaterialPropertyDrawer 依然没有办法拥有完整的控制权,若是需求特殊时,仍然必须使用 MaterialEditor 来作为首要选择。但在无特别开发需求的状况下,仍然可以透过 MaterialPropertyDrawer 来节省大量开发时间
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 92 93 94 | Shader "Custom/Customize" { Properties { [Header(Toggle)] [Toggle] _Invert( "Invert?" , Float) = 0 [Toggle(ENABLE_FANCY)] _Fancy ( "Fancy?" , Float) = 0 [Header(KeywordEnum)] [KeywordEnum(None, Add, Multiply)] _Overlay ( "Overlay mode" , Float) = 0 [Space] [Header(PowerSlider)] [PowerSlider(3.0)] _Shininess ( "Shininess" , Range (0.01, 1)) = 0.08 [Space] [Header(IntRange)] [IntRange] _Alpha ( "Alpha" , Range (0, 255)) = 100 [Header(Cull)] [Enum(UnityEngine.Rendering.CullMode)] _CullMode ( "Cull Mode" , Float) = 0 [Space] [Header(Blend)] [Enum(UnityEngine.Rendering.BlendMode)] _BlendSrcFactor ( "Blend SrcFactor" , Float) = 0 [Enum(UnityEngine.Rendering.BlendMode)] _BlendDstFactor ( "Blend DstFactor" , Float) = 0 [Header(Stencil)] [IntRange] _StencilRef ( "Stencil Reference" , Range(0, 255)) = 0 [Enum(CompareFunction)] _StencilComp ( "Stencil Compare Function" , Float) = 0 [Enum(UnityEngine.Rendering.StencilOp)] _StencilOp ( "Stencil Operation" , Float) = 0 } SubShader { Tags { "RenderType" = "Opaque" } Stencil { Ref [_StencilRef] Comp [_StencilComp] Pass [_StencilOp] } Pass { Cull [_CullMode] Blend [_BlendSrcFactor] [_BlendDstFactor] CGPROGRAM #pragma shader_feature _INVERT_ON #pragma shader_feature ENABLE_FANCY #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" struct appdata { float4 vertex : POSITION; }; struct v2f { float4 vertex : SV_POSITION; }; float4 _Color; v2f vert (appdata v) { v2f o; o.vertex = UnityObjectToClipPos(v.vertex); return o; } fixed4 frag (v2f i) : SV_Target { fixed4 col = _Color; #if _INVERT_ON col = 1 - col; #endif #if ENABLE_FANCY col.r = 0.5; #endif return col; } ENDCG } } } |