写在前面

之前遇到过一些朋友问怎么在材质面板里定义类似于bool这种变量,控制一些代码的执行。我们当然可以写一个C#文件来自定义材质面板,就像Unity为Standard Shader写材质面板一样(你可以在built-in shader包里找到这些文件),但这样有时候太麻烦了。实际上,有一种更简单的方法,不需要写额外的C#文件就可以直接在shader里定义类似bool、enum这种简单的变量,Unity为这些变量提供了内置的材质面板。而这就是通过MaterialPropertyDrawer(当然我们也可以重写内置的MaterialPropertyDrawer,但太麻烦我就不写了)。

示例

总体来说,MaterialPropertyDrawers内置了4种最常见的面板:ToggleDrawer,EnumDrawer,KeywordEnumDrawer,PowerSliderDrawer。

类别 描述
ToggleDrawer 把一个类型为float的属性显示为一个开关,它的值要么是0要么是1。当选中它时,Unity还会设置一个名为大写属性名_ON(可以自定义名字)的shader feature(需要在shader里使用”#pragma shader_feature”来定义它),我们可以在shader里用过#if、#ifdef或者#if defined关键词来判断它当前是否被开启。
EnumDrawer 把一个类型为float的属性显示为一个下拉列表。可以使用UnityEngine.Rendering命名空间下的各种状态来设置对应的渲染状态,例如ZWrite、ZTest、Blend等。据实验推测,这个值不可以随便定义,老老实实用UnityEngine.Rendering。
KeywordEnum 和EnumDrawer类似,也会把一个类型为float的属性显示为一个下拉列表,但不同的是它会定义一组shader keyword(需要在shader里使用”#pragma multi_compile”来定义它们)。这些keyword的名字是大写属性名_枚举名。同样,我们可以在shader里用过#if、#ifdef或者#if defined配合#elif来判断当前选择是哪个keyword。最多同时支持9个keywords。
PowerSliderDrawer 显示一个非线性响应的滑动条,其中PowerSliderDrawer中的参数指定了底数,然后我们再根据Range()来指定范围。其实简单说来就是滑动条上的数值不再是均匀变化了,而是xslider进行变化,但我们在shader里还是可以直接访问到一个float数值。

下面的代码没有任何实际意义,只是为了演示四种面板:

Shader "Custom/Material Property Drawer Example"
{
Properties
{
// Header creates a header text before the shader property.
[Header(Material Property Drawer Example)]
// Space creates vertical space before the shader property.
[Space] _MainTex ("Main Tex", 2D) = "white" {}
_SecondTex ("Second Tex", 2D) = "white" {} // Large amount of space
[Space(50)] // Toggle displays a **float** as a toggle.
// The property value will be 0 or 1, depending on the toggle state.
// When it is on, a shader keyword with the uppercase property name +"_ON" will be set,
// or an explicitly specified shader keyword.
[Toggle] _Invert ("Invert color?", Float) = 0 // Will set "ENABLE_FANCY" shader keyword when set
[Toggle(ENABLE_FANCY)] _Fancy ("Fancy?", Float) = 0 // Enum displays a popup menu for a **float** property.
// You can supply either an enum type name
// (preferably fully qualified with namespaces, in case there are multiple types),
// or explicit name/value pairs to display.
// Up to **7** name/value pairs can be specified
[Enum(UnityEngine.Rendering.BlendMode)] _SrcBlend ("Src Blend Mode", Float) = 1
[Enum(UnityEngine.Rendering.BlendMode)] _DstBlend ("Dst Blend Mode", Float) = 1
[Enum(Off, 0, On, 1)] _ZWrite ("ZWrite", Float) = 0
[Enum(UnityEngine.Rendering.CompareFunction)] _ZTest ("ZTest", Float) = 0
[Enum(UnityEngine.Rendering.CullMode)] _Cull ("Cull Mode", Float) = 1 // KeywordEnum displays a popup menu for a **float** property, and enables corresponding shader keyword.
// This is used with "#pragma multi_compile" in shaders, to enable or disable parts of shader code.
// Each name will enable "property name" + underscore + "enum name", uppercased, shader keyword.
// Up to **9** names can be provided.
[KeywordEnum(None, Add, Multiply)] _Overlay ("Overlay mode", Float) = 0 // PowerSlider displays a slider with a non-linear response for a Range shader property.
// A slider with 3.0 response curve
[PowerSlider(3.0)] _Shininess ("Shininess", Range (0.01, 1)) = 0.08
}
SubShader
{
Tags { "Queue"="Transparent" "RenderType"="Transparent" }
Blend [_SrcBlend] [_DstBlend]
ZWrite [_ZWrite]
ZTest [_ZTest]
Cull [_Cull] Pass
{
CGPROGRAM
// Need to define _INVERT_ON shader keyword
#pragma shader_feature _INVERT_ON
// Need to define _INVERT_ON shader keyword
#pragma shader_feature ENABLE_FANCY
// No comma between features
#pragma multi_compile _OVERLAY_NONE _OVERLAY_ADD _OVERLAY_MULTIPLY #pragma vertex vert
#pragma fragment frag #include "UnityCG.cginc" sampler2D _MainTex;
float4 _MainTex_ST;
sampler2D _SecondTex;
float4 _SecondTex_ST;
float _Shininess; struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
}; struct v2f
{
float4 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
}; v2f vert (appdata v)
{
v2f o;
o.vertex = mul(UNITY_MATRIX_MVP, v.vertex);
o.uv.xy = TRANSFORM_TEX(v.uv, _MainTex);
o.uv.zw = TRANSFORM_TEX(v.uv, _SecondTex);
return o;
} fixed4 frag (v2f i) : SV_Target
{
// sample the texture
fixed4 col = tex2D(_MainTex, i.uv.xy); // Use #if, #ifdef or #if defined
#if _INVERT_ON
col = 1 - col;
#endif // Use #if, #ifdef or #if defined
#if ENABLE_FANCY
col.r = 0.5;
#endif fixed4 secCol = tex2D(_SecondTex, i.uv.zw); #if _OVERLAY_ADD
col += secCol;
#elif _OVERLAY_MULTIPLY
col *= secCol;
#endif col *= _Shininess; return col;
}
ENDCG
}
}
}

得到的面板是这样的:

上面的代码基本显示了所有可能的例子。有一点需要注意的是使用#pragma multi_compile声明多个keyword时,每个keyword之间是没有逗号的,而官网里的有逗号,这是不对的。

除了上面的四种内置属性面板外,还有一些装饰性的内置drawer,例如Space和Header。在上面的例子里,我们在它们的后面直接写属性,像下面这样:

[Space] _Prop1 ("Prop1", Float) = 0

装饰性drawer的后面有没有紧跟属性都是可以的。

shader feature和shader keyword

从上面的内容,我们知道可以使用#pragma shader_feature和#pragma multi_compile来实现“只写一份代码但实现不同功能”的目的。但世界上没有免费的午餐,Unity实际上只是在背后生成了很多份有少许不同的shader变种(shader variants),每个变种包含了不同的keyword而已,没什么非常神奇的地方。

当我们在代码里写到:

#pragma multi_compile _OVERLAY_NONE _OVERLAY_ADD _OVERLAY_MULTIPLY

Unity就生成了3个shader变种,每个对应开启其中的一个keyword。

如果我们再添加新的定义:

#pragma shader_feature ENABLE_FANCY

Unity会再分别为是否开启ENABLE_FANCY来生成两种变种,也就是一共生成了3*2=6个shader变种。因此,当我们定义了大量的feature或者keyword的时候,就会出现有大量的变种!(而且Unity允许的keyword数目也是有限制的。)

#pragma shader_feature和#pragma multi_compile的区别

那么#pragma shader_feature和#pragma multi_compile有什么区别呢?实际上,#pragma shader_feature是#pragma multi_compile的子集,#pragma shader_feature生成的变种一个是不包含任何keyword的,一个是包含某个keyword的。我们可以使用#pragma multi_compile来实现同样的目的,只需要使用一个全是下划线的名字来表示不使用任何keyword即可。下面的两句话是在大多数情况下等价的:

#pragma shader_feature ENABLE_FANCY
#pragma multi_compile __ ENABLE_FANCY

但区别在于,使用multi_compile来定义keyword的话Unity是一定会生成所有变种的,。而如果使用的是shader_feature的话,如果有些keyword我们实际上并没有使用到,Unity也不会为这些生成shader变种,我们可以在shader的代入面板里看到到底实际生成了多少个变种:

因此(非常重要!!!),shader_feature适用于那些会在材质面板中直接设置的情况,而如果需要在脚本里通过DisableKeyword和EnableKeyword来开启或关闭keyword的话就应该使用multi_compile。(栽过坑啊!!!)并且不要在#pragma后面写注释!!!如果要在脚本里通过Shader.DisableKeyword来控制keyword的开关的话,不要在Properties里写KeywordEnum,这样可能会导致脚本里的设置失效(可以重新建一个材质解决)。但如果是使用material.DisableKeyword来设置的话,就不会有这个问题,原因暂时不明。

实际上,Unity的surface shader能够有那样强大的功能也是依靠了这样的机制。也包括我们在通过使用#pragma multi_compile_fwdbase或multi_compile_fwdadd这样的代码时,我们之所以需要使用这样的语句就是因为Unity为forward pass等内置了一些shader keyword,而我们通过这些语句来让unity为不同的光照pass生成不同的shader变种,否则的话我们的shader就一种类型,只能包含特定的任务,无法为多类型的光源等进行不同的服务。

当然,我们可以独立使用#pragma shader_feature和#pragma multi_compile,而不必一定要和MaterialPropertyDrawer配合使用。我们可以直接在代码里通过Material.EnableKeyword和Material.DisableKeyword来局部开启某些keyword,也可以通过Shader.EnableKeyword和Shader.DisableKeyword来全局开启。

参考链接:http://docs.unity3d.com/Manual/SL-MultipleProgramVariants.html

【Unity Shader】自定义材质面板的小技巧的更多相关文章

  1. Unity中容易被忽略的小技巧

    今天在游戏蛮牛上看到慕容小匹夫的一篇文章,感觉对自己现在的水平很实用,就给转载了过来,以便日后好温习一下. 这里还是要支持原创作者,原文地址在这里 一.编辑器染色 一个常见的工作情景是我们在调整场景内 ...

  2. Unity Shader 自定义纹理坐标变量写法

    Properties { _R(,)) = 1.0 _ColorTex("ColorTex (RGB)", 2D) = "red" {} struct Inpu ...

  3. 【Unity笔记】常用的优化小技巧

    一.当通过GetComponent获取一个组件时,不在Update中每帧进行查找,可在Start中查找一次并用一个私有变量去保存这个组件. public class Test : MonoBehavi ...

  4. Unity Shader入门精要学习笔记 - 第3章 Unity Shader 基础

    来源作者:candycat   http://blog.csdn.net/candycat1992/article/ 概述 总体来说,在Unity中我们需要配合使用材质和Unity Shader才能达 ...

  5. 第二章 Unity Shader基础

    [TOC] 1. Unity Shader 的基础: ShaderLab 学习和编写着色器的过程一直是一个学习曲线很陡峭的过程,通常情况下为了自定义渲染效果往往要和很多文件和设置打交道,这些设置很容易 ...

  6. 【转】《Unity Shader入门精要》冯乐乐著 书中彩图

    为方便个人手机学习时候查阅,从网上转来这些彩图. 如属过当行为,联系本人删除. 勘错表 http://candycat1992.github.io/unity_shaders_book/unity_s ...

  7. Unity Shader入门精要学习笔记 - 第16章 Unity中的渲染优化技术

    转自冯乐乐的 <Unity Shader 入门精要> 移动平台的特点 为了尽可能一处那些隐藏的表面,减少overdraw(即一个像素被绘制多次),PowerVR芯片(通常用于ios设备和某 ...

  8. Unity Shader概述

    一.概述 在Unity中需要配合使用材质和Unity Shader才能达到需要的效果.常见的流程:(1)创建一个材质:(2)创建一个Unity Shader,并把它赋给创建的材质:(3)把材质赋给要渲 ...

  9. 【Unity Shader】Unity Chan的卡通材质

    写在前面 时隔两个月我终于来更新博客了,之前一直在学东西,做一些项目,感觉没什么可以分享的就一直没写.本来之前打算写云彩渲染或是Compute Shader的,觉得时间比较长所以打算先写个简单的. 今 ...

随机推荐

  1. django 开发忘记密码通过邮箱找回功能

    一.流程分析: 1.点击忘记密码====>forget.html页面,输入邮箱和验证码,发送验证链接网址的邮件====>发送成功,跳到send_success.html提示 2.到邮箱里找 ...

  2. [LeetCode] My Calendar I 我的日历之一

    Implement a MyCalendar class to store your events. A new event can be added if adding the event will ...

  3. Java的递归、IO流

    .t1 { background-color: #ff8080; width: 1100px; height: 40px } 一.递归 [递归] 1. 在函数自身内部,调用函数本身的方式,称为递归. ...

  4. [JSOI2007]字符加密

    题目描述 喜欢钻研问题的JS 同学,最近又迷上了对加密方法的思考.一天,他突然想出了一种他认为是终极的加密办法:把需要加密的信息排成一圈,显然,它们有很多种不同的读法. 例如‘JSOI07’,可以读作 ...

  5. [HZOI 2015]疯狂的机器人

    [题目描述] 现在在二维平面内原点上有一只机器人 他每次操作可以选择向右走,向左走,向下走,向上走和不走(每次如果走只能走一格) 但是由于本蒟蒻施展的大魔法,机器人不能走到横坐标是负数或者纵坐标是负数 ...

  6. [SDOI2016]游戏

    Description Alice 和 Bob 在玩一个游戏. 游戏在一棵有 n 个点的树上进行.最初,每个点上都只有一个数字,那个数字是 123456789123456789. 有时,Alice 会 ...

  7. [HNOI2004]敲砖块

    题目描述 在一个凹槽中放置了 n 层砖块.最上面的一层有n 块砖,从上到下每层依次减少一块砖.每块砖 都有一个分值,敲掉这块砖就能得到相应的分值,如下图所示. 14 15 4 3 23 33 33 7 ...

  8. 计蒜客NOIP2017提高组模拟赛(三)day2-直线的交点

    传送门 简单几何+逆序对 发现当两条直线甲乙与平板的交点在上面甲在较左的位置,那么下面甲在较右的位置就可以相交 然后把上面的位置排下序,下面离散化+树状数组即可 #include<cstdio& ...

  9. SpringBoot多环境部署,在启动时动态设置相应的配置文件

    项目中,往往在测试环境和正式环境拥有不同的配置,例如数据库连接,第三方库的appkey等.这时候,我们就要在不同的环境启用不同的配置 下面新建三个文件,分别表示开发环境,生产环境和测试环境的配置文件 ...

  10. Python中模块json与pickle的功能介绍

    json & pickle & shelve 1. json的序列化与反序列化 json的使用需要导入该模块,一般使用import json即可. json的序列化 方法1:json. ...