开启辅助访问
 找回密码
 注册帐号

扫一扫,访问微社区

教程分享

关注:580

当前位置:游戏蛮牛 技术专区 教程分享

查看: 263|回复: 3

[基础知识] 【Unity Shader】自定义材质面板的小技巧

[复制链接]  [移动端链接]
7日久生情
3606/5000
排名
2622
昨日变化

1409

主题

1414

帖子

3606

积分

Rank: 7Rank: 7Rank: 7Rank: 7

UID
132510
好友
17
蛮牛币
27270
威望
0
注册时间
2015-12-24
在线时间
282 小时
最后登录
2017-2-28
发表于 2016-12-14 16:11:43 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有帐号?注册帐号

x
写在前面
之前遇到过一些朋友问怎么在材质面板里定义类似于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数值。

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

[AppleScript] 纯文本查看 复制代码
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。在上面的例子里,我们在它们的后面直接写属性,像下面这样:

[AppleScript] 纯文本查看 复制代码
[Space] _Prop1 ("Prop1", Float) = 0

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

shader feature和shader keyword

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

当我们在代码里写到:

[AppleScript] 纯文本查看 复制代码
#pragma multi_compile _OVERLAY_NONE _OVERLAY_ADD _OVERLAY_MULTIPLY

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

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

[AppleScript] 纯文本查看 复制代码
#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即可。下面的两句话是在大多数情况下等价的:

[AppleScript] 纯文本查看 复制代码
#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来全局开启。

来源于网络


[发帖际遇]: BobbyKim 发帖时在路边捡到 2 蛮牛币,偷偷放进了口袋. 幸运榜 / 衰神榜

回复

使用道具 举报

排名
32714
昨日变化
22

0

主题

34

帖子

44

积分

Rank: 1

UID
128419
好友
0
蛮牛币
3
威望
0
注册时间
2015-11-9
在线时间
6 小时
最后登录
2016-12-14
发表于 2016-12-14 17:28:49 | 显示全部楼层
很有帮助啊66666

回复 支持 反对

使用道具 举报

4四处流浪
372/500
排名
6234
昨日变化
63

4

主题

135

帖子

372

积分

Rank: 4

UID
158249
好友
1
蛮牛币
784
威望
0
注册时间
2016-7-22
在线时间
99 小时
最后登录
2017-2-28
发表于 2016-12-15 16:01:58 | 显示全部楼层
是滴是滴  支持一下

回复 支持 反对

使用道具 举报

4四处流浪
324/500
排名
7361
昨日变化
4

0

主题

175

帖子

324

积分

Rank: 4

UID
188049
好友
0
蛮牛币
63
威望
0
注册时间
2016-11-29
在线时间
47 小时
最后登录
2017-2-27
发表于 2016-12-18 21:28:07 | 显示全部楼层
666666666666666666666666666
[发帖际遇]: kang372700718 在论坛发帖时没有注意,被小偷偷去了 1 蛮牛币. 幸运榜 / 衰神榜

回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 注册帐号

本版积分规则

快速回复 返回顶部 返回列表