unity material 怎么换unity rendering modee

12815人阅读
Unity Shaders(55)
之前遇到过一些朋友问怎么在材质面板里定义类似于bool这种变量,控制一些代码的执行。我们当然可以写一个C#文件来,就像Unity为Standard Shader写材质面板一样(你可以在built-in shader包里找到这些文件),但这样有时候太麻烦了。实际上,有一种更简单的方法,不需要写额外的C#文件就可以直接在shader里定义类似bool、enum这种简单的变量,Unity为这些变量提供了内置的材质面板。而这就是通过(当然我们也可以重写内置的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(Material Property Drawer Example)]
_MainTex ("Main Tex", 2D) = "white" {}
_SecondTex ("Second Tex", 2D) = "white" {}
[Space(50)]
[Toggle] _Invert ("Invert color?", Float) = 0
[Toggle(ENABLE_FANCY)] _Fancy ("Fancy?", Float) = 0
[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(pareFunction)] _ZTest ("ZTest", Float) = 0
[Enum(UnityEngine.Rendering.CullMode)] _Cull ("Cull Mode", Float) = 1
[KeywordEnum(None, Add, Multiply)] _Overlay ("Overlay mode", Float) = 0
[PowerSlider(3.0)] _Shininess ("Shininess", Range (0.01, 1)) = 0.08
Tags { "Queue"="Transparent" "RenderType"="Transparent" }
Blend [_SrcBlend] [_DstBlend]
ZWrite [_ZWrite]
ZTest [_ZTest]
Cull [_Cull]
#pragma shader_feature _INVERT_ON
#pragma shader_feature ENABLE_FANCY
#pragma multi_compile _OVERLAY_NONE _OVERLAY_ADD _OVERLAY_MULTIPLY
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
sampler2D _MainT
float4 _MainTex_ST;
sampler2D _SecondT
float4 _SecondTex_ST;
struct appdata
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
struct v2f
float4 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
v2f vert (appdata v)
o.vertex = mul(UNITY_MATRIX_MVP, v.vertex);
o.uv.xy = TRANSFORM_TEX(v.uv, _MainTex);
o.uv.zw = TRANSFORM_TEX(v.uv, _SecondTex);
fixed4 frag (v2f i) : SV_Target
fixed4 col = tex2D(_MainTex, i.uv.xy);
#if _INVERT_ON
#if ENABLE_FANCY
col.r = 0.5;
fixed4 secCol = tex2D(_SecondTex, i.uv.zw);
#if _OVERLAY_ADD
col += secC
#elif _OVERLAY_MULTIPLY
col *= secC
得到的面板是这样的:
上面的代码基本显示了所有可能的例子。有一点需要注意的是使用#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来全局开启。
参考链接:
&&相关文章推荐
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:2085911次
积分:15081
积分:15081
排名:第772名
原创:112篇
转载:20篇
译文:21篇
评论:2033条
我叫乐乐,程序媛一枚,就读于上海交通大学软件学院,研究生,数字媒体方向。喜欢用计算机来绘制各种五彩缤纷的画面~欢迎访问我的和 :)
邮件:lelefeng1992 # gmail DOT com
PS:为防止垃圾邮件,请自行转换为正确格式哦~
---------------------
阅读:83939
阅读:52971
文章:44篇
阅读:738841
(2)(3)(1)(2)(2)(1)(1)(2)(2)(4)(2)(3)(6)(3)(8)(5)(5)(3)(6)(5)(7)(1)(2)(8)(1)(4)(5)(4)(3)(15)(2)(3)(27)(5)
(window.slotbydup = window.slotbydup || []).push({
id: '4740887',
container: s,
size: '250,250',
display: 'inlay-fix'关于《Thinking in Unity3D》
笔者在研究和使用Unity3D的过程中,获得了一些Unity3D方面的信息,同时也感叹Unity3D设计之精妙。不得不说,笔者最近几年的引擎研发工作中,早已习惯性的从Unity3D中寻找解决方案。
Unity3D虽比不上UE那么老练沉稳,气势磅礴。也比不上CE那样炫丽多姿,盛气凌人。但它的发展势如破竹,早已遍地生花!故而在此记录一些自己的心得体会,供大家参详交流。若有欠妥之处,还望各位及时指正。
由一系列文章组成,文章列表地址:
PBS(Physically Based Shading)在有一些地方也叫PBR(Physically Based Rendering),是一个基于物体表面材质属性的着色方法。与之前的Blinn-Phong等算法不同。PBS通过对物体表面的材质属性与周围光照信息来进行着色计算。PBS着色系统中,一个物体不仅受到光源的影响,还会受到周围环境的影响。 这会使得整个场景更加真实。
PBS有一个大前提,就是它能够满足光能传播过程中的能量守衡。能量守衡体现在三个方面。
1、一个对象反射出来的光照信息,不可能超过它接受到的信息。也就是说,全反射是一个物体的极限。
2、一个物体越光亮,那么它的颜色信息应该越少。(可以看出,refection 和 diffuse 应该是一个插值关系)
3、一个物体越平滑,那么它的高亮点会越小,越亮。
下图展示了以下事实
1、下图中所有的点,光照计算的方式一模一样
2、粗糙的表面,受到的光照影响后,亮的面积会多些。
3、越平滑的表面,高亮点越小,越亮。
4、点A的光照信息来自摄像机方向。
5、点B的光照信息主要来自天空的蓝色。
6、点C的光照信息主要来自地面。
PBS在Unity3D中的体现
在Unity3D 5.X中,默认的材质均被Standard Shader和Standard (Specular setup) Shader替代。 我们可以认为,Standard系列的Shader就是Unity3D中的PBS实现。下图便是使用Standard Shader渲染出来的场景效果。
若我们在Unity3D 5.x中新建一个空场景,你会发现有一个默认的天空盒。此时,我们再新建一个Sphere放在原点。 默认情况下,这个球的材质使用的就是Standard Shader.如下图所示,它受天空盒影响,上下呈现出不同的颜色。假如你更改天空盒的材质,那么这个球也会跟着变化。
Unity3D中PBS具体内容
PBS或者说PBR的原理只是一种指导思想,不同的引擎的实现还是有细微的区别。在此,我们先来看看Unity3D中,PBS的实现细节。请先看一张Standard Shader的材质参数图
左:Standard Shader&&&& 右:Standard(Specular steup)
第一眼看过去,熟悉PBR算法的朋友应该很容易看清楚Albedo、Specular、NormalMap、Occlusion、Emission等参数的意思。其余的就多半只能靠猜了。在此我们逐一说明每一个参数的含义。
1、Rendering Mode
Rendering Mode用于在非透明和多种透明效果之间切换。
Opaque:默认值,实体渲染。
Cutout:即Alpha test,通过贴图的Alpha值缕空模型表面。
Transparent:透明效果,如玻璃,半透明塑料等等。
Fade:在这个模式下,一个对象可以实现淡入淡出效果。
Transparent与Fade模式的区别
Transparent模式下,反射和高光叠加的颜色不会进行Alpha效果,也就是说,对于玻璃等效果,反射足够强的时候,只能隐约看到后面的内容,这也是符合能量守衡的,反射越多,吸收就越少。我们可以忽略细节,假设计算公式像这样:DiffuseColor * Alpha + RefColor * (1 - Alpha)。
Fade模式下则不会进行这样的运算,它会简单粗爆的进行Alpha计算。我们同样可以用伪代码表示:DiffuseColor * Alpha + RefColor * Alpha。
由此可以看出,Fade模式下,有较强反射的半透明物体,效果不会特别好,需要慎用。
更多细节请看官方文档:
Albedo与Diffuse的区别是,Albedo我们认为,它是一个只拥有颜色信息的&平面&贴图,即不带任何AO,SHADOW等光影信息。一个物体的Alpha信息,也是由这个提供。Albedo + Tint Color构成了物体的Base Color颜色值。
下图是常见的材质亮度表
更多细节请看官方文档:
3、Metallic & Specluar & Smoothness
前面提到Unity3D提供了Standard和Standard(Specular steup)两种着色方式。 无论是哪种着色方式,都是控制高光反射结果的。
Specular通过直接赋与颜色的方式来决定高光反射强度。而Metallic则通过一个0~1的值,来决定金属的反射强度。
不管是在metallic还是Specular模式下,一但使用了贴图来决定高光反射效果。引擎将会采用贴图中的值,设置的Metallic因子,或者Specular Color均会无效。在这种情况下,二者是等价的。但是,二者的效果是不一样的。根据实则结果,Metallic的反射比Specular要多一些。
Metallic为1的情况下,左:Smoothness=0.0、中:Smoothness=0.5、 Smoothness=1.0
常见物体的Specular+Smoothness一起变化时的情况表
常见物体的Metallic表
Smoothness则决定了一个物体的光滑呈度。 即使一个物体表面高光很强烈。若它是一个不光滑的物体,那么其反射会呈不规则的方向分布,会将光能分散到i同的地方。那么到达人眼中的反射光就会少。整体的反射效果就会变得很弱。
物体的由粗糙到光滑的光线反射情况
常见物体Smoothness表
注:当Metallic或者Specular被赋与贴图后。Smoothness值会失效。 转而采用Matallic或者Specular贴中的Alpha通道作为Smoothness值。
更多细节请看官方文档:
4、Normal Map
Normal Map是Bump Mapping的一种特例化。 它主要是通过贴图为光照计算时提供更细节的法线信息。使物体低模物体表面具有高度的细节效果。如下图所示
更多细节请看官方文档:
5、Heightmap
Heightmap比NormalMap更上一层楼,NormalMap是通过赋与物体表面更多的法线信息,来造成光照上的细节增强。 Normal Map有一个缺点就是,当视线与表面平行时,就失去的法线效果。而Heightmap则直接对某些顶点进行位移。由此也可以看出来,Heightmap的效率开销会比Normalmap高,要更据情况选择使用。
高度图是一张灰度图,白色表示突出,黑色表示凹陷。如下图所示
更多细节请看官方文档:
下面的图展示了 左:普通效果&& 中:NormalMap效果 右:Heightmap效果
更多细节请看官方文档:
6、Occlusion Map
Occlusion Map用于决定一个模型各部位受到间隔光照的影响情况。 间隔光照一般来自于Ambient和环境反射。
Occlusion Map示例
左:无Occlusion Map& 右:有Occlusion Map
更多细节请看官方文档:
7、Emission
Emission表示一个物体的自发光程度。默认是纯黑,如果为这个物体赋值了非黑色的材质。 那么这个物体即使在黑暗的场景下,也会有亮光。 我们可以简单的认为,计算公式像这样:FinalColor = Color + Emission
它一般用来做一些显示器等会进行自发光的物体材质,如下图所示。
更多细节请看官方文档:
8、Detail Mask & Secondary Maps
Secondary Maps用于在物体的表面增加细节。我们可以为一个物体再赋值一套Albedo和NormalMap. 经测试,第一套Albedo和第二套Albedo的叠加算法如下& AlbedoFinal = Albedo0.rgb& * Albedo1.a + Albedo0.rgb * Albedo1.rgb
Detail Mask则用于第一套贴图和第二套贴图的混合。具体公式如下。
Albedo0.rgb = Albedo0.rgb * (1 - DetailMask.a);
Albedo1.rgb = Albedo1.rgb * DetailMask.a;
AlbedoFinal = Albedo0.rgb& * Albedo1.a + Albedo0.rgb * Albedo1.rgb
更多细节请看官方文档:
9、Fresnel
Fresnel即为菲涅尔效果。物体的表面与视线的夹角的不同,会导致眼睛看到的从物体反射出来的光线的反射量不同。
同一视角下,不同光滑程度的物体菲涅尔效果的差别
注:目前的Standard Shader并没有直接为大家提供控制菲涅尔反射效果的因子。 而是通过Smoothness因子间接控制。
更多细节请看官方文档:
Unity3D 5.x中对材质系统的改进,进一步促进了Unity3D画质效果的提升。 而基于PBS的着色系统,也让美术人员在实现某些高级效果的时候,不用纠结选用哪个材质更好了。
但是有以下几点需要注意。
1、虽然Standard提供的参数中,如果不进行设置,Unity3D会进行优化处理,避免额外开销。 但Standard Shader基于PBS的着色方式,在计算间接光照的时候有大量的开销。 并且像素着色器的复杂度,也远远高于传统的着色模式。因此,在手机上要慎用。 在PC上,游戏项目也需要进行一定的优化或者根据主次关系进行材质LOD。
2、Standard Shader仅提供了渲染层级的效果展示。 面对游戏中的若干需求,还是需要进行一些定制化。 这就要求我们需要将Builltin Shaders下载下来,在Standard Shader基础上进行修改。
3、对于手机项目,建议自已根据项目需求进行材质编写,去除不必要的间接光照计算。
整体参考文档地址:
阅读(...) 评论()Unity3D(11)
不能通过只设置Mode来改变,需要如下八行代码
Material m = new Material(Shader.Find("Standard"));
m.SetFloat("_Mode", 2);
m.SetInt("_SrcBlend", (int)UnityEngine.Rendering.BlendMode.SrcAlpha);
m.SetInt("_DstBlend", (int)UnityEngine.Rendering.BlendMode.OneMinusSrcAlpha);
m.SetInt("_ZWrite", 0);
m.DisableKeyword("_ALPHATEST_ON");
m.EnableKeyword("_ALPHABLEND_ON");
m.DisableKeyword("_ALPHAPREMULTIPLY_ON");
m.renderQueue = 3000;
&&相关文章推荐
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:43287次
排名:千里之外
原创:22篇
转载:11篇
评论:11条
(1)(1)(2)(5)(4)(1)(1)(1)(1)(1)(6)(4)(5)
(window.slotbydup = window.slotbydup || []).push({
id: '4740887',
container: s,
size: '250,250',
display: 'inlay-fix'努力加载中,稍等...
暂无新消息
努力加载中,稍等...
已无更多消息...
这些人最近关注了你
努力加载中,稍等...
已无更多消息
努力加载中,稍等...
已无更多消息
& Unity渲染教程(九):复杂材质
Rendering 9:Complex Materials
征集热心朋友翻译文章,奖励规则:每100汉字奖励10QB,30天内互动奖励0 - 50QB.
翻译请求:请求翻译
该文章来自用户转载
Create a custom shader GUI.Mix metals and nonmetals.Use nonuniform smoothness.Support emissive surfaces.This is the ninth part of a tutorialseries about rendering. , we added support forenvironmental maps. In this part we'll combine multiple textures to createcomplex materials. But before we get to that, we need a better GUI for ourshader.This tutorial was made with Unity5.4.1f1.Same shader, different maps.User InterfaceUp to this points, we've been using Unity's defaultmaterial inspector for our material. It is serviceable, but Unity's standardshader has quite a different look. Let's create a custom inspector for our ownshader, mimicking the standard shader.Our default inspector vs. the standardshader inspector. ShaderGUIWe can create a custom inspector by adding a class thatextends UnityEditor..As it is an editor class, place its script file in an Editor folder.usingUnityEusingUnityE publicclass MyLightingShaderGUI :
{} Don't we need to extend ?Unity 4.1 added support for custom material inspectors, viaextending .You can still do this, but
was added as an alternative in 5.0. Its creation has something to do withSubstance materials. Unity uses
for the standard shader, so we'll use it as well.Under the hood, Unity uses the default material editor forshaders that have a custom
associated with them. This editor instantiates the GUI and invokes its methods.To use a custom GUI, you have to add the CustomEditor directive to a shader, followed by a string containing the name ofthe GUI class to use.Shader "Custom/My First Lighting Shader" {
CustomEditor"MyLightingShaderGUI"} Can
classes be putinside namespaces?Yes. You have to specify the fully-qualified class name inthe shader.CustomEditor "MyNamespace.MyShaderGUI"To replace the default inspector, we haveto override the .OnGUI method. This method has two parameters. First, a reference to a .This object manages the inspector of the currently selected material. Second,an array containing that material's properties.public class MyLightingShaderGUI :
publicoverride void OnGUI (
editor, [] properties
}}Inside this method, we can create our own GUI. As we're notdoing so yet, the inspector has become empty.Creating a LabelThe standard shader GUI is split into two sections, one forthe main maps, and another for the secondary maps. We'll use the same layout inour GUI. To keep the code clean, we'll use separate methods for distinct partsof the GUI. We start with the main section and its label.public override void OnGUI (
editor, [] properties
voidDoMain() {
.Label("Main Maps");
}Main maps label.How does
work?The Unity Editor is created with Unity's immediate-mode UI.This is Unity's old UI system, which was also used for in-game UIs before thecurrent canvas-based system.The basis of the immediate-mode UI is the
class. It contains methods which create UI widgets. You have to use rectanglesto position each element explicitly. The
class provides the same functionality, while automatically positioning thewidgets using a simple layout system.Besides that, the
classes provide access to widgets and features for editor UIs.The standard shader has a bold label, so wewant a bold label as well. This is done by adding a GUI style to the label, inthis case .boldLabel..Label("Main Maps", .boldLabel);Bold label.Showing AlbedoTo show the properties of our material, we have to accessthem in our methods. We could pass the parameters of OnGUI on toall other methods, but this would lead to a lot of repeated code. Instead,let's put them in fields.
public override void OnGUI (
editor, [] properties
this.editor =
this.properties =
}Do we need to copy the references each time OnGUI is invoked? decides when a new
instance is created. This currently happens when a material is selected, as youmight expect. But it also happens when an undo or redo action is performed.This means that you cannot rely on a
instance sticking around. Each time, it could be a new object instance. Youcould think of OnGUI as if it were a static method, even though itisn't.The albedo map is shown first in thestandard shader. This is the main texture. Its property sits somewhere insidethe properties array. Its array index depends on the order in which theproperties are defined in our shader. But it is more robust to search for it byname.
contains the FindProperty method, which does exactly that, given a name and a property array.void DoMain () {
.Label("Main Maps", .boldLabel);
mainTex = FindProperty("_MainTex", properties);
}Besides the texture property, we also needto define the contents of a label. This is done with ,which is a simple container class. mainTex = FindProperty("_MainTex", properties);
albedoLabel = new ("Albedo"); But we've already named the main texture Albedo in our shader. Wecan just use that name, which we can access via the property. albedoLabel = new (mainTex.displayName);To create one of those small texturewidgets, we have to rely on the editor that we've been given a reference to. Ithas a collection of methods to draw such widgets. mainTex = FindProperty("_MainTex", properties);
albedoLabel = new (mainTex.displayName);
editor.TexturePropertySingleLine(albedoLabel, mainTex);Albedo map. This is beginning to look like thestandard shader! But that inspector also has tooltips, when you hover over theproperty labels. In the case of the albedo map, it says Albedo (RGB) andTransparency (A).We can add a tooltip as well, by simplyadding it to the label content. As we don't support transparency yet, let'sjust use Albedo (RGB). albedoLabel =
new (mainTex.displayName, "Albedo (RGB)");Albedo with tooltip. The TexturePropertySingleLine methodhas variants that work with more than one property, up to three. The firstshould be a texture, but the others can be something else. They will all be puton the same line. We can use this to display the tint next to the texture. tint = FindProperty("_Tint", properties);
editor.TexturePropertySingleLine(albedoLabel, mainTex, tint);Albedo map and tint. Let's skip ahead to the bottom of themain section. That's where the tiling and offset values of the main texture areshown. This is done with the .TextureScaleOffsetPropertymethod.editor.TexturePropertySingleLine(albedoLabel, mainTex, tint);
editor.TextureScaleOffsetProperty(mainTex); Convenience MethodsInstead of using the exiting FindProperty method, let's create one that only requires a name parameter, taking advantageof our properties field. This will make our code more legible. FindProperty (string name) {
returnFindProperty(name, properties);}Switch to using this method in DoMain.Also, we can directly pass the tint property to the TexturePropertySingleLine method. We're not using it anywhere else.void DoMain () {
.Label("Main Maps", .boldLabel);
mainTex = FindProperty("_MainTex");
albedoLabel =
new (mainTex.displayName, "Albedo (RGB)");
editor.TexturePropertySingleLine(
albedoLabel, mainTex, FindProperty("_Tint")
editor.TextureScaleOffsetProperty(mainTex);
} Let's also create a method to configure thecontents of a label. We only need to use a single static
instance for this. We'll just replace its text and its tooltip. As we might notneed a tooltip all the time, let's make it optional, with a default parametervalue.
static staticLabel = new ();
static MakeLabel (string text, string tooltip = null) {
staticLabel.text =
staticLabel.tooltip =
returnstaticL
} It's even more convenient if we don't haveto bother with extracting the display name from properties all the time. Socreate a MakeLabel variant that does this as well.
static MakeLabel (
property, string tooltip = null
staticLabel.text = property.displayN
staticLabel.tooltip =
returnstaticL
} Now DoMain can become evensmaller. The same goes for all our future methods.
void DoMain () {
.Label("Main Maps", .boldLabel);
mainTex = FindProperty("_MainTex");
editor.TexturePropertySingleLine(
MakeLabel(mainTex, "Albedo (RGB)"), mainTex, FindProperty("_Tint")
editor.TextureScaleOffsetProperty(mainTex);
} Showing NormalsThe next texture to be displayed is the normal map. Insteadof putting all the code in DoMain, delegate it to a separate DoNormals method. Invoke it after the albedo line, before the tiling and offset.
DoNormals();
editor.TextureScaleOffsetProperty(mainTex); The new DoNormals method simplyretrieves the map property and displays it. The standard shader doesn't provideany extra tooltip info, so we won't either.
voidDoNormals () {
map = FindProperty("_NormalMap");
editor.TexturePropertySingleLine(MakeLabel(map), map);
} Of course there is a bump scale as well, soadd it to the line.
editor.TexturePropertySingleLine(
MakeLabel(map), map, FindProperty("_BumpScale")
); Normal map and bump scale. The standard shader only shows the bumpscale when there is a normal map assigned to the material. We can do this too,by checking whether the property references a texture. If it does, show thebump scale. If not, just use null as an argument for TexturePropertySingleLine.
editor.TexturePropertySingleLine(
MakeLabel(map), map,
map.textureValue ? FindProperty("_BumpScale") : null
); Hidden bump scale. Showing Metallic and SmoothnessThe metallic and smoothness properties are simple floatranges. At least, for now. We can show them via the general-purpose .ShaderProperty method. Unlike the texture methods, this method has the property as its firstargument. The label contents come second.void DoMain () {
editor.TexturePropertySingleLine(
MakeLabel(mainTex, "Albedo (RGB)"), mainTex, FindProperty("_Tint")
DoMetallic();
DoSmoothness();
DoNormals();
editor.TextureScaleOffsetProperty(mainTex);
voidDoMetallic () {
slider = FindProperty("_Metallic");
editor.ShaderProperty(slider, MakeLabel(slider));
voidDoSmoothness () {
slider = FindProperty("_Smoothness");
editor.ShaderProperty(slider, MakeLabel(slider));
} Metallic and smoothness. We can make these properties line up with the other labels,by increasing the indent level of the editor. In this case, by two steps.The indent level can be adjusted via the static .indentLevel property. Make sure to reset it to its old value afterwards.void DoMetallic () {
slider = FindProperty("_Metallic");
.indentLevel += 2;
editor.ShaderProperty(slider, MakeLabel(slider));
.indentLevel -= 2;
void DoSmoothness () {
slider = FindProperty("_Smoothness");
.indentLevel += 2;
editor.ShaderProperty(slider, MakeLabel(slider));
.indentLevel -= 2;
} Indented properties. Showing the Secondary MapsThe secondary maps work just like the main maps. So createa DoSecondary method which takes care of the bold label, thedetail texture, and its tiling and offset.public override void OnGUI (
editor, [] properties
this.editor =
this.properties =
DoSecondary();
voidDoSecondary () {
.Label("Secondary Maps", .boldLabel);
detailTex = FindProperty("_DetailTex");
editor.TexturePropertySingleLine(
MakeLabel(detailTex, "Albedo (RGB) multiplied by 2"), detailTex
editor.TextureScaleOffsetProperty(detailTex);
} Adjust the display name of the detailtexture in our shader, to match the standard shader._DetailTex ("Detail Albedo", 2D) = "gray" {}Secondary maps. The detail normal map works just likethe main normal map. Curiously, the standard shader GUI doesn't hide the detailbump scale. But we're consistent, so we do hide it when there's no detailnormal map.void DoSecondary () {
DoSecondaryNormals();
editor.TextureScaleOffsetProperty(detailTex);
voidDoSecondaryNormals () {
map = FindProperty("_DetailNormalMap");
editor.TexturePropertySingleLine(
MakeLabel(map), map,
map.textureValue ? FindProperty("_DetailBumpScale") : null
} Complete inspector.
Mixing Metal and NonmetalBecause our shader uses a uniform value to determine howmetallic something is, it cannot vary across a material's surface. Thisprevents us from creating complex materials that actually represent a mix ofdifferent materials. For example, here are the albedo and normal maps for anartistic impression of computer circuitry.Albedo and normal map for circuitry. The green parts form the base of the circuit board, whilethe blue parts represent lights. These are nonmetallic. The yellow gold partsrepresent conductive circuitry, which should be metallic. On top of that aresome brown stains, for variety.Create a new material with these maps, using our lightingshader. Make it fairly smooth. Also, because the material isn't bright, itworks with Unity's default ambient environment. So set the scene's Ambient Intensity back to 1, if you stillhad it lowered to zero.Circuitry material. Using the Metallic slider, we can make the whole surface either nonmetallic, metallic, orsomething in between. This is not sufficient for the circuitry.Uniform nonmetal vs. metal. Metallic MapsThe standard shader has support for metallic maps. Thesemaps define the metallic value per texel, instead of for the whole material atonce. Here is a grayscale map which marks the circuitry as metallic, and therest as nonmetallic. Stained metal is darker, because of the semitransparentdirty layer on top.Metallic map. Add a property for such a map to our shader.Properties {
_Tint ("Tint", Color) = (1, 1, 1, 1)
_MainTex ("Albedo", 2D) = "white" {}
[NoScaleOffset] _NormalMap ("Normals", 2D) = "bump" {}
_BumpScale ("Bump Scale", Float) = 1
[NoScaleOffset] _MetallicMap ("Metallic", 2D) = "white" {}
[Gamma] _Metallic ("Metallic", Range(0, 1)) = 0
_Smoothness ("Smoothness", Range(0, 1)) = 0.1
_DetailTex ("Detail Albedo", 2D) = "gray" {}
[NoScaleOffset] _DetailNormalMap ("Detail Normals", 2D) = "bump" {}
_DetailBumpScale ("Detail Bump Scale", Float) = 1
} Do we still need the NoScaleOffset attributes?Those attributes are hints for the default shader GUI. Sono, we don't need them anymore. I keep using them in this tutorial as hints forpeople inspecting the shader code.Add the corresponding variable to ourinclude file as well.sampler2D_MetallicMfloat _M Let's create a function to retrieve themetallic value of a fragment, with the interpolators as a parameter. It simplysamples the metallic map and multiplies it with the uniform metallic value.Unity uses the R channel of the map, so we use that channel as well.struct Interpolators {
…}; floatGetMetallic (Interpolators i) {
return(_MetallicMap, i.uv.xy).r * _M} Now we can retrieve the metallic value in MyFragmentProgram.float4 MyFragmentProgram (Interpolators i) : SV_TARGET {
albedo = DiffuseAndSpecularFromMetallic(
albedo, GetMetallic(i), specularTint, oneMinusReflectivity
…} Note that the code of MyFragmentProgram doesn't care how the metallic value is obtained. If you want to determine themetallic value a different way, you only have to change GetMetallic.Custom GUIHad we still used the default shader GUI, the metallic mapwould've appeared in the inspector. But now we have to explicitly add it to MyLightingShaderGUI,by adjusting DoMetallic. Like the standard shader, we show the mapand the slider on a single line.void DoMetallic () {
map = FindProperty("_MetallicMap");
editor.TexturePropertySingleLine(
MakeLabel(map, "Metallic (R)"), map,
FindProperty("_Metallic")
} Using a metallic map. Map or SliderThe GUI of the standard shader hides the slider when ametallic map is used. We can do so as well. It works like the bump scales,except that the value is shown when there's no texture.editor.TexturePropertySingleLine(
MakeLabel(map, "Metallic (R)"), map,
map.textureValue ? null : FindProperty("_Metallic")
); Hidden slider. Custom Shader KeywordsThe metallic slider is hidden, because the standard shaderuses either a map, or a uniform value. They aren't multiplied. When a metallicmap is provided, the uniform value is ignored. To use the same approach, wehave to distinguish between materials with and without a metallic map. This canbe done by generating two shader variants, one with and one without the map.There are already multiple variants of our shadergenerated, due to the #pragma multi_compile directives in our shader. They're based on keywords provided by Unity. Bydefining our own shader keywords, we can create the variants that we need.You can name custom keywords however you like, but theconvention is to use uppercase words with a leading underscore. In this case,we'll use _METALLIC_MAP.Where are custom keywords defined?Unity detects all custom keywords in a project, based onmulti-compile statements and which keywords are added to materials. Internally,they are converted and combined into bit masks. Which identifier a keyword getsvaries per project.In Unity 5.4, the bit mask contains 128 bits. Therefore, upto 128 keywords can exist per project. This includes Unity's keywords plus anycustom keywords that are in use. This limit used to be lower, which madeshaders with many keywords a potential hazard. Unity 5.5 will increase thelimit to 256.To add custom keywords to a material, wehave to access the material directly in our GUI. We can get to the currentlyselected material via the .target property. As this is actually an inherited property from the base
class, it has the generic
type. So we have to cast it to .
public override void OnGUI (
editor, [] properties
this.target = editor.target as ;
this.editor =
this.properties =
DoSecondary();
} Adding a keyword to a shader is done with the .EnableKeyword method, which has the keyword's name as a parameter. For removal ofa keyword, there's .DisableKeyword. Let's create a convenient method that enables or disables akeyword based on a boolean parameter.voidSetKeyword (string keyword, bool state) {
if(state) {
target.EnableKeyword(keyword);
target.DisableKeyword(keyword);
} Debugging KeywordsYou can use the debug inspector to verify that our keywordgets added to or removed from the material. You can switch the inspector todebug mode via the dropdown menu at the top right of its tab bar. The customkeywords are shown as a list in the Shader Keywords text field.Debug inspector. Any unexpected shader keywords you find here have beendefined because of previous shaders that were assigned to the material. Forexample, as soon as you selected a new material, the standard shader GUI willadd the _EMISSION keyword. They are useless to our shader, so remove them fromthe list.Shader FeaturesTo generate the shader variants, we have to add anothermulti-compile directive to our shader. Do this for both the base pass and theadditive pass. The shadow pass doesn't need it.#pragma target 3.0
#pragma multi_compile _ _METALLIC_MAP When showing the shader variants, you willsee that our custom keyword has been included. The base pass now has a total ofeight variants.SHADOWS_SCREEN VERTEXLIGHT_ON _METALLIC_MAP 8 keyword variants used in scene: VERTEXLIGHT_ONSHADOWS_SCREENSHADOWS_SCREEN VERTEXLIGHT_ON_METALLIC_MAPVERTEXLIGHT_ON _METALLIC_MAPSHADOWS_SCREEN _METALLIC_MAPSHADOWS_SCREEN VERTEXLIGHT_ON _METALLIC_MAP When using a multi-compile directive, Unity generatesshader variants for all possible combinations. Compiling all permutations cantake a lot of time, when many keywords are used. All these variants are alsoincluded in builds, which might be unnecessary.An alternative is to define a shader feature, instead of amulti-compile directive. The difference is that permutations of shader featuresare only compiled when needed. If no material uses a certain keyword, then noshader variants for that keyword are compiled. Unity also checks which keywordsare used in builds, only including the necessary shader variants.So let's used #pragma shader_feature for our custom keyword.#pragma shader_feature _ _METALLIC_MAPWhen can you use shader features?When materials are configured at design time – in theeditor only – then you can use shader features without worry. But if you adjustthe keywords of materials at run time, then you have to make sure that allvariants are included. The simplest way is to stick to multi-compile directivesfor the relevant keywords. Alternatively, you can use a shader variantcollection asset.If the shader feature is a toggle for asingle keyword, you can omit the single underscore.#pragma shader_feature _METALLIC_MAP After making this change, all shadervariants are still listed, although the order in which Unity lists them mightbe different._METALLIC_MAPSHADOWS_SCREEN VERTEXLIGHT_ON 8 keyword variants used in scene: _METALLIC_MAPVERTEXLIGHT_ONVERTEXLIGHT_ON _METALLIC_MAPSHADOWS_SCREENSHADOWS_SCREEN _METALLIC_MAPSHADOWS_SCREEN VERTEXLIGHT_ONSHADOWS_SCREEN VERTEXLIGHT_ON _METALLIC_MAP Finally, adjust the GetMetallic function in our include file. When _METALLIC_MAP is defined, sample the map. Otherwise, return the uniform value.float GetMetallic (Interpolators i) {
#if defined(_METALLIC_MAP)
return (_MetallicMap, i.uv.xy).r;
#endif} So either _MetallicMap or _Metallic is used, never both?That is correct. So the material will always have at leastone useless property. It's a little bit of overhead for the sake offlexibility.Only Setting Keywords When NeededAt the moment, we're setting the material's keyword everytime OnGUI is invoked, which is often. Logically, we only have todo this when the map property has been edited. We can check whether somethingwas changed by using the .BeginChangeCheck and .EndChangeCheck methods. The first method defines the point from which we want to begintracking changes. The second method marks the end, and returns whether a changewas made.By putting these methods before and after the invocation of TexturePropertySingleLine, we can easily detect whether themetallic line was edited. If so, we set the keyword.
void DoMetallic () {
map = FindProperty("_MetallicMap");
.BeginChangeCheck();
editor.TexturePropertySingleLine(
MakeLabel(map, "Metallic (R)"), map,
map.textureValue ? null : FindProperty("_Metallic")
if(.EndChangeCheck()) {
SetKeyword("_METALLIC_MAP", map.textureValue);
} Doesn't this also trigger when _Metallic is changed?Yes, this code sets the keyword both when the map ischanged and also when the uniform value is edited. That is more often thanrequired, but still much better than all the time.Does this work with undo and redo?Yes. The
methods that we're using to show properties take care of recording the oldobject's state.Smoothness MapsLike the metallic map, smoothness can also be defined via amap. Here is a grayscale smoothness texture for the circuitry. The metal partsare the smoothest, followed by the lights. The rest is fairly rough. The stainsare smoother than the board, so the texture is lighter there.Smoothness map. Unity's standard shader expects smoothness to be stored inthe alpha channel. In fact, it is expected that the metallic and smoothnessmaps are combined in the same texture. Because DXT5 compressed the RGB and theA channels separately, merging the maps into one DXT5 texture produces the samequality as using two DXT1 textures. This doesn't require less memory, butallows us to retrieve both metallic and smoothness from a single texturesample, instead of two.Here is a texture that combines both maps. Althoughmetallic only needs the R channel, I still filled the RGB channels with themetallic value. Smoothness uses the alpha channel.Metallic and smoothness map. Determining SmoothnessWhen there is a metallic map, we can get the smoothnessfrom there. Otherwise, we use the uniform _Smoothness property. Add a GetSmoothness function to our include file to takecare of this. It's almost exactly like GetMetallic.floatGetSmoothness (Interpolators i) {
#if defined(_METALLIC_MAP)
return(_MetallicMap, i.uv.xy).a;
#endif} Aren't we still sampling the texture twice?Remember that the shader compiler gets rid of duplicatecode. We're sampling the same texture in two different functions, but thecompiled code will sample the texture only once. We don't have to explicitlycache these things.Actually, the standard shader has twodifferent properties for smoothness. One is the stand-alone uniform value, likewe have. The other is a scalar that modulates the smoothness map. Let's keep itsimple and use the _Smoothness property for both purposes. This means that you have to set it to 1 to get theunmodified smoothness map values.
return (_MetallicMap, i.uv.xy).a * _Smoothness; Use this new function to get the smoothnessin MyFragmentProgram.float4 MyFragmentProgram (Interpolators i) : SV_TARGET {
return UNITY_BRDF_PBS(
albedo, specularTint,
oneMinusReflectivity, GetSmoothness(i),
i.normal, viewDir,
CreateLight(i), CreateIndirectLight(i, viewDir)
);} But that's not the only place where we needthe smoothness. CreateIndirectLight makes use of it as well. We could add a smoothness parameter tothis function. But we can also just invoke GetSmoothness a secondtime. The shader compiler will detect the duplicate code and optimize it away.UnityIndirect CreateIndirectLight (Interpolators i, float3 viewDir) {
Unity_GlossyEnvironmentData envD
envData.roughness = 1 - GetSmoothness(i);
…} Mapped smoothness, at fullstrength. What are those square artifacts along the edges of themetal strips?Those artifacts are caused by the DXT5nm texturecompression of the normal map. Narrow ridges that are too thin cannot beapproximated correctly, especially if they aren't aligned with the UV axes. Thesharp diagonal edges in the circuitry are the worst case for this kind ofcompression. This limitation becomes clearly visible on surfaces that aremetallic and very smooth. Otherwise, it isn't as obvious.Using an uncompressed normal map. Combining Smoothness with AlbedoCombining metallic and smoothness maps into a singletexture is fine, when you need both of them. Metallic parts are nearly alwayssmoother than the other bits. So when you need a metallic map, you practicallyalways want a smoothness map too. But you might need a smoothness map, withoutmixing metals and nonmetals. In that case, the metallic map is useless.For opaque materials that don't require a metallic map, itis possible to store the smoothness in the alpha channel of the albedo map. Asthis practice is common, the standard shader supports packing smoothness ineither the metallic map or the albedo map. Let's support this too.Switching Between KeywordsLike the standard shader, we have to add an option to pickthe smoothness source to our GUI. Although the standard shader only supportsthe choice between two maps, let's go ahead and include a uniform smoothness asa third option as well. To represent these options, define an enumeration typeinside MyLightingShaderGUI.enumSmoothnessSource {
Uniform, Albedo, Metallic
} When the albedo map is used as the smoothness source, we'lladd the _SMOOTHNESS_ALBEDO keywordto the material. When the metallic source is used, we'll add _SMOOTHNESS_METALLIC instead. The uniformoption corresponds with no keyword.The standard shader also uses a float property to trackwhich option a material uses, but we can make do with the keywords alone. TheGUI can determine the current choice by checking which keyword is enabled. Thiscan be done via the .IsKeywordEnabled method, for which we'll create a convenient wrapper.boolIsKeywordEnabled (string keyword) {
returntarget.IsKeywordEnabled(keyword);
} Now DoSmoothness can figureout the current smoothness source of the selected material.void DoSmoothness () {
SmoothnessSourcesource = SmoothnessSource.U
if(IsKeywordEnabled("_SMOOTHNESS_ALBEDO")) {
source = SmoothnessSource.A
elseif (IsKeywordEnabled("_SMOOTHNESS_METALLIC")) {
source = SmoothnessSource.M
} To show the options, we can use the .EnumPopup method. Increase the indent level an additional step, to match the layout ofthe standard shader..indentLevel += 2;
editor.ShaderProperty(slider, MakeLabel(slider));
.indentLevel += 1;
.EnumPopup(MakeLabel("Source"), source);
.indentLevel -= 3; Smoothness source popup. EnumPopup is a basic editor widgetwhich creates a popup list for any enumeration. It returns the selected value.If the user doesn't choose a new option, this value is the same as the originalchoice. Otherwise, it is different. So to know which option was chosen, we haveto assign it back to the source variable. As the method works with the genericenumeration type, we have to cast it to SmoothnessSource.source = (SmoothnessSource).EnumPopup(
MakeLabel("Source"), source
); If a change has been made, we can use thesource variable to control which keyword should be set, if any..BeginChangeCheck();
source = (SmoothnessSource).EnumPopup(
MakeLabel("Source"), source
if(.EndChangeCheck()) {
SetKeyword("_SMOOTHNESS_ALBEDO", source == SmoothnessSource.Albedo);
SetKeyword(
"_SMOOTHNESS_METALLIC", source == SmoothnessSource.Metallic
} Smoothness from the metallic map. Supporting UndoWe can now change the smoothness source, but it doesn'tsupport undo and redo actions yet. Because we're using a basic widget, we haveto manually signal that we performed an action that should support undo. Thiscan be done via the .RegisterPropertyChangeUndo method, which has a descriptive label as a parameter. Create a wrapper for thismethod as well.voidRecordAction (string label) {
editor.RegisterPropertyChangeUndo(label);
} RecordAction must be invoked before the change that we're about to make. Itcreates a snapshot of the old state, so an undo action can revert to it.if (.EndChangeCheck()) {
RecordAction("Smoothness Source");
SetKeyword("_SMOOTHNESS_ALBEDO", source == SmoothnessSource.Albedo);
SetKeyword(
"_SMOOTHNESS_METALLIC", source == SmoothnessSource.Metallic
} Smoothness VariantsTo support all three options, add a shader feature thatselects between no keyword, _SMOOTHNESS_ALBEDO,and _SMOOTHNESS_METALLIC. Asbefore, both the base and additive passes have to support it.#pragma shader_feature _METALLIC_MAP
#pragma shader_feature _ _SMOOTHNESS_ALBEDO _SMOOTHNESS_METALLIC In GetSmoothness, start with a smoothness of1. Then check whether the albedo source is chosen. If so, replace the 1 withthe albedo map. Otherwise, check whether the metallic source is chosen, and ifso use the metallic map instead. Of course this is only sensible when thematerial actually uses a metallic map, so check for that as well.After that, return whatever smoothness value we got,multiplied with the value of the _Smoothness property. If we end up with a variant that does not use a map, the compilerwill optimize away the multiplication with 1.float GetSmoothness (Interpolators i) {
floatsmoothness = 1;
#if defined(_SMOOTHNESS_ALBEDO)
smoothness = (_MainTex, i.uv.xy).a;
#elif defined(_SMOOTHNESS_METALLIC) && defined(_METALLIC_MAP)
smoothness = (_MetallicMap, i.uv.xy).a;
returnsmoothness * _S} Lava MaterialHere are example albedo and normal maps for an artisticimpression of cooled lava. The material isn't metallic, but does have varyingsmoothness. So the smoothness values are stored in the alpha channel of thealbedo map.Albedo with smoothness, and normals. Create a material with these maps,using the albedo source option for smoothness.Lava material. When using the albedo source, thegrayish lumps turn out to be significantly smoother than the reddish gulleys.Uniform vs. mapped, using albedo alpha.Emissive SurfacesSo far, we've only worked with materials that reflectlight, via either diffuse or specular reflection. We need a light source to seesuch surfaces. But there are also surfaces that emit light themselves. Forexample, when something becomes hot enough, it starts to glow. You don't need adifferent light source to see that. The standard shader supports this via anemissive map and color, and so will we.Mapped and UniformAdd properties for an emission map and color to our shader.Both should be black by default, meaning that no light is emitted. As we onlycare about the RGB channels, we can omit the fourth component of the defaultcolor.[NoScaleOffset] _EmissionMap ("Emission", 2D) = "black" {}
_Emission ("Emission", Color) = (0, 0, 0) Many materials won't have an emission map,so let's use a shader feature to create variants without and with an emissionmap. Because we only have to add the emitted light once, only include thefeature in the base pass.#pragma shader_feature _METALLIC_MAP
#pragma shader_feature _ _SMOOTHNESS_ALBEDO _SMOOTHNESS_METALLIC
#pragma shader_feature _EMISSION_MAP Add the required sampler and floatvariables to the include file.sampler2D_EmissionMfloat3_E Create a GetEmission function toretrieve the emitted color, if any. When there's a map, sample it and multiplywith the uniform color. Otherwise, just return the uniform color. But onlybother to do this in the base pass. In all other cases, the emission is zero,which the compiler will optimize away.float3GetEmission (Interpolators i) {
#if defined(FORWARD_BASE_PASS)
#if defined(_EMISSION_MAP)
return(_EmissionMap, i.uv.xy) * _E
#endif} As the emission comes from the objectitself, it is independent of the reflected light. So just add it to the finalcolor.float4 MyFragmentProgram (Interpolators i) : SV_TARGET {
float4color = UNITY_BRDF_PBS(
albedo, specularTint,
oneMinusReflectivity, GetSmoothness(i),
i.normal, viewDir,
CreateLight(i), CreateIndirectLight(i, viewDir)
color.rgb += GetEmission(i);
return} Adding Emission to the GUICreate a DoEmission method inside MyLightingShaderGUI.The quickest way to do this is by duplicating DoMetallic andmaking a few changes.
void DoEmission () {
map = FindProperty("_EmissionMap");
.BeginChangeCheck();
editor.TexturePropertySingleLine(
MakeLabel(map, "Emission (RGB)"), map, FindProperty("_Emission")
if (.EndChangeCheck()) {
SetKeyword("_EMISSION_MAP", map.textureValue);
} Include it in the main maps section.
void DoMain () {
DoNormals();
DoEmission();
editor.TextureScaleOffsetProperty(mainTex);
} Inspector with emission map and color. HDR EmissionThe standard shader doesn't use a regular color foremission. Instead, it supports high-dynamic-range colors. This means that theRGB components of the color can be higher than 1. This way, you can representextremely bright light.Can we see colors that are brighter than 1?In real life, there is no hard limit to the amount ofphotons that you can be bombarded with. The sun is very bright, blindingly so.However, computer displays are limited. You cannot go higher than 1. How brightthat is depends on the brightness of the display.To use HDR colors in a meaningful wall, you have to performtone mapping. This means that you convert one range of colors to another range.We'll investigate tone mapping in a future tutorial. HDR colors are also oftenused to create bloom effects.As color properties are float vectors, we are not limitedto just values in the 0–1 range. However, the standard color widget is designedwith this limit in mind. Fortunately,
contains the TexturePropertyWithHDRColor method, which isspecifically made for a texture plus an HDR color property. It requires twoadditional parameters. First, configuration options for the HDR range. Second,whether the alpha channel should be shown, which we do not want.
void DoEmission () {
editor.TexturePropertyWithHDRColor(
MakeLabel("Emission (RGB)"), map, FindProperty("_Emission"),
emissionConfig, false
} The HDR color widget is configured via a
object. This object contains the allowed brightness and exposure range. Thestandard shader uses 0–99 for brightness, and nearly zero to 3 for exposure.We'll simply use the same ranges.
static emissionConfig =
new(0f, 99f, 1f / 99f, 3f); Inspector with HDR emission. The extra value after the color picker corresponds with thebrightness of the color. This is simply the largest of the RGB channels. Aquick way to switch the emissive color to black or white is to set this valueto 0 or 1.Emissive LavaHere is an emission map for the lava material. It makes thelava in the gulleys glowing hot. You can change the brightness and tint of theemission by adjusting the uniform color.Emission map for lava.I assigned the emission map, but it doesn't show?In that case, the uniform emission color is still black. Tosee the map at full strength, set the color to white.The standard shader automatically sets the emission colorto white, when a texture has been assigned while the color was black. You couldadd this functionality as well. However, that behavior is based on assumptionswhich might not be true for some people, which can lead to a lot of userfrustration.Glowing lava, lit and unlit. Emissive CircuitryAnd here is an emission map for the circuitry lights.Emission map for circuitry. The lights have varying brightness, and the stains affectthem as well.Circuitry with functioning lights, lit and unlit.Does the emitted light illuminate other objects?The emission is part of the material only. It doesn'taffect the rest of the scene. However, Unity's global illumination system canpick up this emitted light and add it to the indirect illumination data. Wewill investigate global illumination in a future tutorial.The next tutorial will add even more features to ourshader, bringing it closer to feature parity with the standard shader. It willbe released in November 2016.
Unity渲染教程(九):复杂材质
版权所有,禁止匿名转载;禁止商业使用;禁止个人使用。
翻译:赵菁菁(轩语轩缘)
审校:李笑达(DDBC4747)创建一个自定义着色器GUI混合金属和非金属使用nonuniform光滑度支持发射表面这是渲染教程系列的第九部分。上一次,我们加入了对于环境贴图的支持,在这一部分,我们会组合多种纹理来创建复杂材质。但是在我们开始之前,我们需要为我们的着色器创造一个更好的GUI。本教程的运行环境是Unity 5.4.1f1。同样的着色器,不同的贴图用户界面
到目前为止,我们一直都为我们的材质使用Unity默认的材质监视器,它很耐用,但是Unity的标准着色器长得非常不一样。仿照着标准着色器,让我们一起来为我们自己的着色器创建一个自定义监视器吧。我们默认的监视器和标准着色器监视器着色器GUI
我们可以通过添加一个继承UnityEditor.的类创建一个自定义监视器。因为它是一个编辑器类,所以应该把脚本文件放到Editor文件夹下。12345using UnityEusing UnityE
public class MyLightingShaderGUI : ShaderGUI {}我们不需要继承吗?Unity 4.1支持通过继承自定义材质监视器,你也可以依旧这样做,但是在5.0版本中是作为备选被加入的,它的创建与Substance材质有一些关系。Unity为标准着色器使用,所以我们也使用它。要使用一个自定义GUI,你需要向着色器加入CustomEditor指令,紧跟着一个字符串,字符串包含要使用的GUI类的名字。12345Shader "Custom/My First Lighting Shader" {
CustomEditor "MyLightingShaderGUI"} 类能放入命名空间吗?
可以。你需要在着色器中指定完全限定的类名。1CustomEditor "MyNamespace.MyShaderGUI"
为了替换默认监视器,我们需要重写.OnGUI方法,该方法有两个参数,第一个参数是的引用,这个对象管理当前被选材质的监视器;第二个参数是包含材质属性的数组。1234567public class MyLightingShaderGUI : ShaderGUI {
public override void OnGUI (
MaterialEditor editor, MaterialProperty[] properties
}}在这个方法内部,我们已经创建了我们自己的GUI。因为我们还什么都没做,所以监视器还是空的。创建一个标签
标准着色器GUI被分成两部分,一部分针对主要贴图,另一部分针对次要贴图。我们会在我们的GUI中采用相同的布局。为了保持代码整洁,我们会为GUI不同的部分采用单独的方法。我们从主要部分以及其标签入手。123456public override void OnGUI (
MaterialEditor editor, MaterialProperty[] properties
DoMain();}
void DoMain() {
GUILayout.Label("Main Maps");}主要贴图标签 是如何工作的?UnityEditor是用Unity的立即模式UI创建的,这是Unity老的UI系统,在当今基于画布的系统之前,它也用于游戏内UI。立即模式UI的基础是GUI类,它包含创建UI小工具的方法。你需要利用矩形来精确定位每个元素。类提供了相同的功能,但是它会使用简单布局系统自动定位小工具。除此之外, 类和 类提供了针对编辑器UI的小工具和特征的访问。标准着色器有一个粗体标签,所以我们也想要一个粗体标签,通过向标签添加GUI样式就可以实现,这样一来就是.boldLabel。1GUILayout.Label("Main Maps", EditorStyles.boldLabel);粗体标签.显示Albedo
为了显示我们材质的属性,我们需要在我们的方法中访问它们。我们会把OnGUI的参数传递到其他所有方法中,但是这会导致大量重复代码。我们不这么做,我们把它们放到域中。12345678MaterialEMaterialProperty[]
public override void OnGUI (
MaterialEditor editor, MaterialProperty[] properties
this.editor =
this.properties =
DoMain();}每次OnGUI被调用时我们都需要拷贝引用吗?当一个新的 实例被创建时, 会做出决定。目前来讲,这会在一个材质被选了的时候发生,就像你可能预期的那样。但是它也可能在撤销会重做动作执行的时候发生。这意味着你不能继续依赖实例了。每次,它都会是一个新的对象实例,你可以把OnGUI想成它好像是一个静态方法,尽管它并不是。
Albedo贴图在标准着色器中第一次展现。这是主要纹理,它的属性在属性数组的某一位置设置,它的数组下标取决于我们着色器中定义属性的顺序。但是通过名称查找的鲁棒性更强一些。包括FindProperty方法,它就是干这件事的,通过给定名称和属性数组找到对应结果。1234void DoMain () {
GUILayout.Label("Main Maps", EditorStyles.boldLabel);
MaterialProperty mainTex = FindProperty("_MainTex", properties);}
除了纹理属性之外,我们也需要定义标签的内容,这可以通过实现,它是一个简单的容器类。12MaterialProperty mainTex = FindProperty("_MainTex", properties);GUIContent albedoLabel = new GUIContent("Albedo");
但是我们已经在我们的着色器中为主要纹理Albedo命名了,我们只能使用已定义好的名称,我们可以通过属性访问到。1GUIContent albedoLabel = new GUIContent(mainTex.displayName);
为了创建那些小的纹理小工具,我们必须依赖我们已经引用的编辑器,它具有许多绘制这样小工具的方法。123MaterialProperty mainTex = FindProperty("_MainTex", properties);GUIContent albedoLabel = new GUIContent(mainTex.displayName);editor.TexturePropertySingleLine(albedoLabel, mainTex);Albedo贴图
这就是看起来像标准着色器的开始!但是当你鼠标经过属性标签时,那个监视器还有提示信息,在Albedo贴图的情况中,上面写着Albedo (RGB) 和Transparency (A)。
我们也可以加一个提示信息,很简单,只要向标签内容中添加就可以了。因为我们还不支持透明度,我们就只使用Albedo(RGB)。12GUIContent albedoLabel =
new GUIContent(mainTex.displayName, "Albedo (RGB)");带提示信息的Albedo
TexturePropertySingleLine方法有可以与多个(最多三个)属性作用的变体,第一个应该是纹理,但是其他可能就是别的什么了,它们都会被放在一行中,我们可以用这来显示纹理旁边的取色工具。12MaterialProperty tint = FindProperty("_Tint", properties);editor.TexturePropertySingleLine(albedoLabel, mainTex, tint);Albedo贴图和取色
让我们直接跳到主要部分的底部,那是显示主要纹理贴砖和偏移量的位置,这可以通过.TextureScaleOffsetProperty方法实现。12editor.TexturePropertySingleLine(albedoLabel, mainTex, tint);editor.TextureScaleOffsetProperty(mainTex);便利方法不用现有的FindProperty 方法,让我们创建一个只需要一个名称参数的方法,充分利用我们属性域的优势,这会让我们的代码更易读。123MaterialProperty FindProperty (string name) {
return FindProperty(name, properties);}在DoMain中换成用这个方法,我们也可以直接把色彩属性传递给TexturePropertySingleLine方法,我们不会在其它地方用到它。 123456789101112void DoMain () {
GUILayout.Label("Main Maps", EditorStyles.boldLabel);
MaterialProperty mainTex = FindProperty("_MainTex");
GUIContent albedoLabel =
new GUIContent(mainTex.displayName, "Albedo (RGB)");
editor.TexturePropertySingleLine(
albedoLabel, mainTex, FindProperty("_Tint")
editor.TextureScaleOffsetProperty(mainTex);}
让我们再建立一个方法来配置标签的内容。我们只需要使用一个单例静态就可以,我们把它的文本和提示信息修改了就好。因为我们可能不总是需要提示信息,让我们把它变为可选择的,同时带有一个默认的参数值。1234567static GUIContent staticLabel = new GUIContent();
static GUIContent MakeLabel (string text, string tooltip = null) {
staticLabel.text =
staticLabel.tooltip =
return staticL}
如果我不非要总是从属性中抽取出显示名称的话,就会更加方便,所以再建立一个MakeLabel变量来做这件事吧。1234567static GUIContent MakeLabel (
MaterialProperty property, string tooltip = null
staticLabel.text = property.displayN
staticLabel.tooltip =
return staticL}现在DoMain变得更小了,我们日后的方法也会遵循这一重构流程。1234567891011void DoMain () {
GUILayout.Label("Main Maps", EditorStyles.boldLabel);
MaterialProperty mainTex = FindProperty("_MainTex");
editor.TexturePropertySingleLine(
MakeLabel(mainTex, "Albedo (RGB)"), mainTex, FindProperty("_Tint")
editor.TextureScaleOffsetProperty(mainTex);}显示法线
下一个需要展示的纹理是法线贴图。不要把所有代码都放在DoMain中,把代码分派到一个独立的DoNormals方法中,在Albedo行之后、铺砖和偏移量之前调用。12DoNormals();editor.TextureScaleOffsetProperty(mainTex);新的DoNormals方法会检索贴图属性,之后展现出来,标准着色器不会提供任何额外的提示信息,所以我们也不会。1234void DoNormals () {
MaterialProperty map = FindProperty("_NormalMap");
editor.TexturePropertySingleLine(MakeLabel(map), map);}当然也有凹凸程度,所以把它加到代码中。123editor.TexturePropertySingleLine(
MakeLabel(map), map, FindProperty("_BumpScale"));法线贴图和凹凸程度
标准着色器只会在法线贴图赋值给材质的时候显示凹凸程度,我们也这样做,检查属性是否引用了一个纹理,如果引用了,就显示凹凸程度,否则就只是给TexturePropertySingleLine赋空值。1234editor.TexturePropertySingleLine(
MakeLabel(map), map,
map.textureValue ? FindProperty("_BumpScale") : null);隐藏的凹凸程度显示金属质感与光滑度金属和光滑度属性是简单的浮点范围。至少目前我们可以通过通用的.ShaderProperty方法显示它们。与纹理方法不同,该方法的第一个参数是属性,第二个参数是标签内容。12345678910111213141516171819202122void DoMain () {
editor.TexturePropertySingleLine(
MakeLabel(mainTex, "Albedo (RGB)"), mainTex, FindProperty("_Tint")
DoMetallic();
DoSmoothness();
DoNormals();
editor.TextureScaleOffsetProperty(mainTex);}
void DoMetallic () {
MaterialProperty slider = FindProperty("_Metallic");
editor.ShaderProperty(slider, MakeLabel(slider));}
void DoSmoothness () {
MaterialProperty slider = FindProperty("_Smoothness");
editor.ShaderProperty(slider, MakeLabel(slider));}金属质感与光滑度我们可以让这些属性与其他标签对齐,通过提升编辑器的缩进级别。在这种情况下,两步。缩进级别可以通过静态.indentLevel属性调整,确保之后把它设置回原来的值。12345678910111213void DoMetallic () {
MaterialProperty slider = FindProperty("_Metallic");
EditorGUI.indentLevel += 2;
editor.ShaderProperty(slider, MakeLabel(slider));
EditorGUI.indentLevel -= 2;}
void DoSmoothness () {
MaterialProperty slider = FindProperty("_Smoothness");
EditorGUI.indentLevel += 2;
editor.ShaderProperty(slider, MakeLabel(slider));
EditorGUI.indentLevel -= 2;}锯齿属性显示次要贴图
次要贴图与主要贴图看起来很像,所以创建一个DoSecondary方法,它可以管理粗体标签,细节纹理,以及它的贴砖和偏移量。1234567891011121314151617181920public override void OnGUI (
MaterialEditor editor, MaterialProperty[] properties
this.editor =
this.properties =
DoSecondary();
void DoSecondary () {
GUILayout.Label("Secondary Maps", EditorStyles.boldLabel);
MaterialProperty detailTex = FindProperty("_DetailTex");
editor.TexturePropertySingleLine(
MakeLabel(detailTex, "Albedo (RGB) multiplied by 2"), detailTex
editor.TextureScaleOffsetProperty(detailTex);}
调整我们着色器中细节纹理的显示名称,让它与标准着色器一致。1_DetailTex ("Detail Albedo", 2D) = "gray" {}次要贴图
细节法线贴图与主要法线贴图的效果一样,有趣的是,标准着色器GUI没有隐藏细节凹凸程度,但是我们要求一致性,所以当没有细节法线贴图的时候,我们把细节凹凸程度也隐藏。123456789101112void DoSecondary () {
DoSecondaryNormals();
editor.TextureScaleOffsetProperty(detailTex);}void DoSecondaryNormals () {
MaterialProperty map = FindProperty("_DetailNormalMap");
editor.TexturePropertySingleLine(
MakeLabel(map), map,
map.textureValue ? FindProperty("_DetailBumpScale") : null
);}完整监视器混合金属与非金属
因为我们的着色器使用uniform值来判断某个物体有多金属化,对于一个材质表面来说,它不能改变,这会阻碍我们创建复杂材质,这些材质实际上代表了不同材质的混合。例如,这里有计算机电路的艺术印象的Albedo和法线贴图。 电路的Albedo和法线贴图
绿色部分形成了电路板的基础,蓝色部分代表光,这些是非金属的。金黄色部分代表导电回路,这里应该是金属的。在顶部有一些棕色污点,是为了使变化丰富。
用这些贴图创建一个新的材质,同时使用我们的光照着色器,让效果相对光滑些。同时,因为材质不是明亮的,它与Unity默认的周边环境相互作用,所以如果你的场景的Ambient Intensity值比0小,把它设回1。电路材质
使用Metallic滑块,我们可以让整个表面变得非金属、金属或介于两者之间,这对于电路并不足够。 uniform非金属和金属金属度贴图
标准着色器支持金属度贴图,这些贴图每纹素都定义了金属值,而不是一次性为整个材质都定义。这里有一个灰度贴图,它把电路作为金属标记,余下的是非金属。有污渍的金属暗一些,因为表层上有半透明的脏东西。金属度贴图为着色器中这样的贴图添加一个属性。123456789101112131415Properties {
_Tint ("Tint", Color) = (1, 1, 1, 1)
_MainTex ("Albedo", 2D) = "white" {}
[NoScaleOffset] _NormalMap ("Normals", 2D) = "bump" {}
_BumpScale ("Bump Scale", Float) = 1
[NoScaleOffset] _MetallicMap ("Metallic", 2D) = "white" {}
[Gamma] _Metallic ("Metallic", Range(0, 1)) = 0
_Smoothness ("Smoothness", Range(0, 1)) = 0.1
_DetailTex ("Detail Albedo", 2D) = "gray" {}
[NoScaleOffset] _DetailNormalMap ("Detail Normals", 2D) = "bump" {}
_DetailBumpScale ("Detail Bump Scale", Float) = 1}我们还需要NoScaleOffset 属性吗?
那些属性是默认着色器GUI的提示,那么,我们就不再需要它们了,我在教程中保留它们,只是为了提示那些需要检验着色器代码的人。
也要在我们的include文件中加入相应的变量。12sampler2D _MetallicMfloat _M
让我们建立一个函数来检索一个片段的金属值,利用Interpolators作为参数,它简单地对金属度贴图进行了采样,并且用它和uniform金属值相乘。Unity用贴图的R通道,所以我们也用这个通道。1234567struct Interpolators {
float GetMetallic (Interpolators i) {
return tex2D(_MetallicMap, i.uv.xy).r * _M}现在我们可以检索MyFragmentProgram中的金属值。123456789float4 MyFragmentProgram (Interpolators i) : SV_TARGET {
albedo = DiffuseAndSpecularFromMetallic(
albedo, GetMetallic(i), specularTint, oneMinusReflectivity
注意MyFragmentProgram代码不关心金属值是如何得到的,如果你想要以不同的方式定义金属值,你只需要修改GetMetallic。自定义 GUI
如果我们仍然使用默认着色器GUI,金属度贴图就会出现在监视器中了,但是现在我们需要通过调整DoMetallic把它明确地加入到MyLightingShaderGUI中,就像标准着色器一样,我们把贴图和滑块在一行中显示。1234567void DoMetallic () {
MaterialProperty map = FindProperty("_MetallicMap");
editor.TexturePropertySingleLine(
MakeLabel(map, "Metallic (R)"), map,
FindProperty("_Metallic")
);}使用一个金属度贴图贴图或者滑块
当使用金属度贴图时,标准着色器的GUI会隐藏滑块,我们也可以这么做,它与凹凸程度的效果一样,当没有纹理的时候就会显示值。1234editor.TexturePropertySingleLine(
MakeLabel(map, "Metallic (R)"), map,
map.textureValue ? null : FindProperty("_Metallic"));隐藏的滑块自定义着色器关键字
金属滑块隐藏了,因为标准着色器用一个贴图或者一个uniform值,它们不是乘在一起的。当提供了金属度贴图时,uniform值就会被忽略。为了使用相同的方式,我们需要区分带有和没有金属度贴图的材质,这可以通过个两个着色器变体实现,一个有贴图,一个没有贴图。
由于着色器中的#pragma multi_compile指令,我们的着色器已经有多个变体生成,它们是基于Unity提供的关键字。通过定义我们自己的着色器关键字,我们可以创建我们需要的变体。
你可以任意命名自定义关键字,但是传统是使用一个以下划线开头的大写单词命名,在这个例子中,我们使用_METALLIC_MAP。自定义关键字在哪里定义?Unity检测项目中的所有自定义关键字,基于multi-compile语句,这些语句把关键字加入材质中。在内部,它们被转换,合并入位掩码 , 而且每个项目都可以得到不同的关键字标识符。在Unity 5.4中,位掩码有128位。因此,每个项目可以存在最多128个关键字。其中包括Unity的关键字,加上任何正在使}

我要回帖

更多关于 unity3d material 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信