精选文章 Unity渲染教程(九):复杂材质 https://www.jianshu.com/p/5e3af869870f

Unity渲染教程(九):复杂材质 https://www.jianshu.com/p/5e3af869870f

作者:kuangben2000 时间: 2021-02-05 09:43:20
kuangben2000 2021-02-05 09:43:20
【摘要】Unity渲染教程(九):复杂材质 https://www.jianshu.com/p/5e3af869870f 
同样的着色器,不同的贴图 
用户界面 
到目前为止,我们一直都为我们的材质使用Unity默认的材质监视器,它很耐用,但是Unity的标准着色器长得非常不一样。仿照着标准着色器,让我们一起来为我们自己的着色器创建一个自定义监视器吧。 
 
我们默认的监视器和标准着色器监视器 
着...

Unity渲染教程(九):复杂材质
https://www.jianshu.com/p/5e3af869870f

同样的着色器,不同的贴图

用户界面

到目前为止,我们一直都为我们的材质使用Unity默认的材质监视器,它很耐用,但是Unity的标准着色器长得非常不一样。仿照着标准着色器,让我们一起来为我们自己的着色器创建一个自定义监视器吧。

Unity渲染教程(九):复杂材质 https://www.jianshu.com/p/5e3af869870f1

我们默认的监视器和标准着色器监视器

着色器GUI

我们可以通过添加一个继承UnityEditor.ShaderGUI的类创建一个自定义监视器。因为它是一个编辑器类,所以应该把脚本文件放到Editor文件夹下。

usingUnityEngine;

usingUnityEditor;

publicclassMyLightingShaderGUI : ShaderGUI {

}

我们不需要继承MaterialEditor吗?

Unity 4.1支持通过继承MaterialEditor自定义材质监视器,你也可以依旧这样做,但是ShaderGUI在5.0版本中是作为备选被加入的,它的创建与Substance材质有一些关系。Unity为标准着色器使用ShaderGUI,所以我们也使用它。

要使用一个自定义GUI,你需要向着色器加入CustomEditor指令,紧跟着一个字符串,字符串包含要使用的GUI类的名字。

Shader"Custom/My First Lighting Shader"{

CustomEditor"MyLightingShaderGUI"

}

ShaderGUI类能放入命名空间吗?

可以。你需要在着色器中指定完全限定的类名。

CustomEditor"MyNamespace.MyShaderGUI"

为了替换默认监视器,我们需要重写ShaderGUI.OnGUI方法,该方法有两个参数,第一个参数是MaterialEditor的引用,这个对象管理当前被选材质的监视器;第二个参数是包含材质属性的数组。

publicclassMyLightingShaderGUI : ShaderGUI {

publicoverridevoidOnGUI (

MaterialEditor editor, MaterialProperty[] properties

) {

}

}

在这个方法内部,我们已经创建了我们自己的GUI。因为我们还什么都没做,所以监视器还是空的。

创建一个标签

标准着色器GUI被分成两部分,一部分针对主要贴图,另一部分针对次要贴图。我们会在我们的GUI中采用相同的布局。为了保持代码整洁,我们会为GUI不同的部分采用单独的方法。我们从主要部分以及其标签入手。

publicoverridevoidOnGUI ( MaterialEditor editor, MaterialProperty[] properties ) {

DoMain();

}

voidDoMain() {

GUILayout.Label("Main Maps");

}

Unity渲染教程(九):复杂材质 https://www.jianshu.com/p/5e3af869870f2

主要贴图标签

GUILayout是如何工作的?

UnityEditor是用Unity的立即模式UI创建的,这是Unity老的UI系统,在当今基于画布的系统之前,它也用于游戏内UI。

立即模式UI的基础是GUI类,它包含创建UI小工具的方法。你需要利用矩形来精确定位每个元素。GUILayout类提供了相同的功能,但是它会使用简单布局系统自动定位小工具。

除此之外,EditorGUI类和EditorGUILayout类提供了针对编辑器UI的小工具和特征的访问。

标准着色器有一个粗体标签,所以我们也想要一个粗体标签,通过向标签添加GUI样式就可以实现,这样一来就是EditorStyles.boldLabel。

GUILayout.Label("Main Maps", EditorStyles.boldLabel);

Unity渲染教程(九):复杂材质 https://www.jianshu.com/p/5e3af869870f3

粗体标签.

显示Albedo

为了显示我们材质的属性,我们需要在我们的方法中访问它们。我们会把OnGUI的参数传递到其他所有方法中,但是这会导致大量重复代码。我们不这么做,我们把它们放到域中。

MaterialEditor editor;

MaterialProperty[] properties;

publicoverridevoidOnGUI ( MaterialEditor editor, MaterialProperty[] properties ) {

this.editor = editor;

this.properties = properties;

DoMain();

}

每次OnGUI被调用时我们都需要拷贝引用吗?

当一个新的ShaderGUI实例被创建时,MaterialEditor会做出决定。目前来讲,这会在一个材质被选了的时候发生,就像你可能预期的那样。但是它也可能在撤销会重做动作执行的时候发生。这意味着你不能继续依赖ShaderGUI实例了。每次,它都会是一个新的对象实例,你可以把OnGUI想成它好像是一个静态方法,尽管它并不是。

Albedo贴图在标准着色器中第一次展现。这是主要纹理,它的属性在属性数组的某一位置设置,它的数组下标取决于我们着色器中定义属性的顺序。但是通过名称查找的鲁棒性更强一些。ShaderGUI包括FindProperty方法,它就是干这件事的,通过给定名称和属性数组找到对应结果。

voidDoMain () {

GUILayout.Label("Main Maps", EditorStyles.boldLabel);

MaterialProperty mainTex = FindProperty("_MainTex", properties);

}

除了纹理属性之外,我们也需要定义标签的内容,这可以通过GUIContent实现,它是一个简单的容器类。

MaterialProperty mainTex = FindProperty("_MainTex", properties);

GUIContent albedoLabel =newGUIContent("Albedo");

但是我们已经在我们的着色器中为主要纹理Albedo命名了,我们只能使用已定义好的名称,我们可以通过属性访问到。

GUIContent albedoLabel =newGUIContent(mainTex.displayName);

为了创建那些小的纹理小工具,我们必须依赖我们已经引用的编辑器,它具有许多绘制这样小工具的方法。

MaterialProperty mainTex = FindProperty("_MainTex", properties);

GUIContent albedoLabel =newGUIContent(mainTex.displayName);

editor.TexturePropertySingleLine(albedoLabel, mainTex);

Unity渲染教程(九):复杂材质 https://www.jianshu.com/p/5e3af869870f4

Albedo贴图

这就是看起来像标准着色器的开始!但是当你鼠标经过属性标签时,那个监视器还有提示信息,在Albedo贴图的情况中,上面写着Albedo (RGB)和Transparency (A)。

我们也可以加一个提示信息,很简单,只要向标签内容中添加就可以了。因为我们还不支持透明度,我们就只使用Albedo(RGB)。

GUIContent albedoLabel =

newGUIContent(mainTex.displayName,"Albedo (RGB)");

Unity渲染教程(九):复杂材质 https://www.jianshu.com/p/5e3af869870f5

带提示信息的Albedo

TexturePropertySingleLine方法有可以与多个(最多三个)属性作用的变体,第一个应该是纹理,但是其他可能就是别的什么了,它们都会被放在一行中,我们可以用这来显示纹理旁边的取色工具。

MaterialProperty tint = FindProperty("_Tint", properties);

editor.TexturePropertySingleLine(albedoLabel, mainTex, tint);

Unity渲染教程(九):复杂材质 https://www.jianshu.com/p/5e3af869870f6

Albedo贴图和取色

让我们直接跳到主要部分的底部,那是显示主要纹理贴砖和偏移量的位置,这可以通过MaterialEditor.TextureScaleOffsetProperty方法实现。

editor.TexturePropertySingleLine(albedoLabel, mainTex, tint);

editor.TextureScaleOffsetProperty(mainTex);

便利方法

不用现有的FindProperty方法,让我们创建一个只需要一个名称参数的方法,充分利用我们属性域的优势,这会让我们的代码更易读。

MaterialProperty FindProperty (stringname) {

returnFindProperty(name, properties);

}

在DoMain中换成用这个方法,我们也可以直接把色彩属性传递给TexturePropertySingleLine方法,我们不会在其它地方用到它。

voidDoMain () {

GUILayout.Label("Main Maps", EditorStyles.boldLabel);

MaterialProperty mainTex = FindProperty("_MainTex");

// MaterialProperty tint = FindProperty("_Tint", properties);

GUIContent albedoLabel =

newGUIContent(mainTex.displayName,"Albedo (RGB)");

editor.TexturePropertySingleLine(

albedoLabel, mainTex, FindProperty("_Tint")

);

editor.TextureScaleOffsetProperty(mainTex);

}

让我们再建立一个方法来配置标签的内容。我们只需要使用一个单例静态GUIContent就可以,我们把它的文本和提示信息修改了就好。因为我们可能不总是需要提示信息,让我们把它变为可选择的,同时带有一个默认的参数值。

staticGUIContent staticLabel =newGUIContent();

staticGUIContent MakeLabel (stringtext,stringtooltip =null) {

staticLabel.text = text;

staticLabel.tooltip = tooltip;

returnstaticLabel;

}

如果我不非要总是从属性中抽取出显示名称的话,就会更加方便,所以再建立一个MakeLabel变量来做这件事吧。

staticGUIContent MakeLabel (

MaterialProperty property,stringtooltip =null

) {

staticLabel.text = property.displayName;

staticLabel.tooltip = tooltip;

returnstaticLabel;

}

现在DoMain变得更小了,我们日后的方法也会遵循这一重构流程。

voidDoMain () {

GUILayout.Label("Main Maps", EditorStyles.boldLabel);

MaterialProperty mainTex = FindProperty("_MainTex");

// GUIContent albedoLabel =

// new GUIContent(mainTex.displayName, "Albedo (RGB)");

editor.TexturePropertySingleLine(

MakeLabel(mainTex,"Albedo (RGB)"), mainTex, FindProperty("_Tint")

);

editor.TextureScaleOffsetProperty(mainTex);

}

显示法线

下一个需要展示的纹理是法线贴图。不要把所有代码都放在DoMain中,把代码分派到一个独立的DoNormals方法中,在Albedo行之后、铺砖和偏移量之前调用。

DoNormals();

editor.TextureScaleOffsetProperty(mainTex);

新的DoNormals方法会检索贴图属性,之后展现出来,标准着色器不会提供任何额外的提示信息,所以我们也不会。

voidDoNormals () {

MaterialProperty map = FindProperty("_NormalMap");

editor.TexturePropertySingleLine(MakeLabel(map), map);

}

当然也有凹凸程度,所以把它加到代码中。

editor.TexturePropertySingleLine(

MakeLabel(map), map, FindProperty("_BumpScale")

);

Unity渲染教程(九):复杂材质 https://www.jianshu.com/p/5e3af869870f7

法线贴图和凹凸程度

标准着色器只会在法线贴图赋值给材质的时候显示凹凸程度,我们也这样做,检查属性是否引用了一个纹理,如果引用了,就显示凹凸程度,否则就只是给TexturePropertySingleLine赋空值。

editor.TexturePropertySingleLine(

MakeLabel(map), map,

map.textureValue ? FindProperty("_BumpScale") :null

);

Unity渲染教程(九):复杂材质 https://www.jianshu.com/p/5e3af869870f8

隐藏的凹凸程度

显示金属质感与光滑度

金属和光滑度属性是简单的浮点范围。至少目前我们可以通过通用的MaterialEditor.ShaderProperty方法显示它们。与纹理方法不同,该方法的第一个参数是属性,第二个参数是标签内容。

voidDoMain () {

editor.TexturePropertySingleLine(

MakeLabel(mainTex,"Albedo (RGB)"), mainTex, FindProperty("_Tint")

);

DoMetallic();

DoSmoothness();

DoNormals();

editor.TextureScaleOffsetProperty(mainTex);

}

voidDoMetallic () {

MaterialProperty slider = FindProperty("_Metallic");

editor.ShaderProperty(slider, MakeLabel(slider));

}

voidDoSmoothness () {

MaterialProperty slider = FindProperty("_Smoothness");

editor.ShaderProperty(slider, MakeLabel(slider));

}

Unity渲染教程(九):复杂材质 https://www.jianshu.com/p/5e3af869870f9

金属质感与光滑度

我们可以让这些属性与其他标签对齐,通过提升编辑器的缩进级别。在这种情况下,两步。缩进级别可以通过静态EditorGUI.indentLevel属性调整,确保之后把它设置回原来的值。

voidDoMetallic () {

MaterialProperty slider = FindProperty("_Metallic");

EditorGUI.indentLevel += 2;

editor.ShaderProperty(slider, MakeLabel(slider));

EditorGUI.indentLevel -= 2;

}

voidDoSmoothness () {

MaterialProperty slider = FindProperty("_Smoothness");

EditorGUI.indentLevel += 2;

editor.ShaderProperty(slider, MakeLabel(slider));

EditorGUI.indentLevel -= 2;

}

Unity渲染教程(九):复杂材质 https://www.jianshu.com/p/5e3af869870f10

锯齿属性

显示次要贴图

次要贴图与主要贴图看起来很像,所以创建一个DoSecondary方法,它可以管理粗体标签,细节纹理,以及它的贴砖和偏移量。

publicoverridevoidOnGUI (

MaterialEditor editor, MaterialProperty[] properties

) {

this.editor = editor;

this.properties = properties;

DoMain();

DoSecondary();

}

voidDoSecondary () {

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"{}

Unity渲染教程(九):复杂材质 https://www.jianshu.com/p/5e3af869870f11

次要贴图

细节法线贴图与主要法线贴图的效果一样,有趣的是,标准着色器GUI没有隐藏细节凹凸程度,但是我们要求一致性,所以当没有细节法线贴图的时候,我们把细节凹凸程度也隐藏。

voidDoSecondary () {

DoSecondaryNormals();

editor.TextureScaleOffsetProperty(detailTex);

}

voidDoSecondaryNormals () {

MaterialProperty map = FindProperty("_DetailNormalMap");

editor.TexturePropertySingleLine(

MakeLabel(map), map,

map.textureValue ? FindProperty("_DetailBumpScale") :null

);

}

Unity渲染教程(九):复杂材质 https://www.jianshu.com/p/5e3af869870f12

完整监视器

混合金属与非金属

因为我们的着色器使用uniform值来判断某个物体有多金属化,对于一个材质表面来说,它不能改变,这会阻碍我们创建复杂材质,这些材质实际上代表了不同材质的混合。例如,这里有计算机电路的艺术印象的Albedo和法线贴图。

Unity渲染教程(九):复杂材质 https://www.jianshu.com/p/5e3af869870f13

Unity渲染教程(九):复杂材质 https://www.jianshu.com/p/5e3af869870f14

电路的Albedo和法线贴图

绿色部分形成了电路板的基础,蓝色部分代表光,这些是非金属的。金黄色部分代表导电回路,这里应该是金属的。在顶部有一些棕色污点,是为了使变化丰富。

用这些贴图创建一个新的材质,同时使用我们的光照着色器,让效果相对光滑些。同时,因为材质不是明亮的,它与Unity默认的周边环境相互作用,所以如果你的场景的Ambient Intensity值比0小,把它设回1。

Unity渲染教程(九):复杂材质 https://www.jianshu.com/p/5e3af869870f15

电路材质

使用Metallic滑块,我们可以让整个表面变得非金属、金属或介于两者之间,这对于电路并不足够。

Unity渲染教程(九):复杂材质 https://www.jianshu.com/p/5e3af869870f16

Unity渲染教程(九):复杂材质 https://www.jianshu.com/p/5e3af869870f17

uniform非金属和金属

金属度贴图

标准着色器支持金属度贴图,这些贴图每纹素都定义了金属值,而不是一次性为整个材质都定义。这里有一个灰度贴图,它把电路作为金属标记,余下的是非金属。有污渍的金属暗一些,因为表层上有半透明的脏东西。

Unity渲染教程(九):复杂材质 https://www.jianshu.com/p/5e3af869870f18

金属度贴图

为着色器中这样的贴图添加一个属性。

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

}

我们还需要NoScaleOffset属性吗?

那些属性是默认着色器GUI的提示,那么,我们就不再需要它们了,我在教程中保留它们,只是为了提示那些需要检验着色器代码的人。

也要在我们的include文件中加入相应的变量。

1

2sampler2D _MetallicMap;

float_Metallic;

让我们建立一个函数来检索一个片段的金属值,利用Interpolators作为参数,它简单地对金属度贴图进行了采样,并且用它和uniform金属值相乘。Unity用贴图的R通道,所以我们也用这个通道。

structInterpolators {

};

floatGetMetallic (Interpolators i) {

returntex2D(_MetallicMap, i.uv.xy).r * _Metallic;

}

现在我们可以检索MyFragmentProgram中的金属值。

float4 MyFragmentProgram (Interpolators i) : SV_TARGET {

albedo = DiffuseAndSpecularFromMetallic(

albedo, GetMetallic(i), specularTint, oneMinusReflectivity

);

}

注意MyFragmentProgram代码不关心金属值是如何得到的,如果你想要以不同的方式定义金属值,你只需要修改GetMetallic。

自定义GUI

如果我们仍然使用默认着色器GUI,金属度贴图就会出现在监视器中了,但是现在我们需要通过调整DoMetallic把它明确地加入到MyLightingShaderGUI中,就像标准着色器一样,我们把贴图和滑块在一行中显示。

voidDoMetallic () {

MaterialProperty map = FindProperty("_MetallicMap");

editor.TexturePropertySingleLine(

MakeLabel(map,"Metallic (R)"), map,

FindProperty("_Metallic")

);

}

Unity渲染教程(九):复杂材质 https://www.jianshu.com/p/5e3af869870f19

Unity渲染教程(九):复杂材质 https://www.jianshu.com/p/5e3af869870f20

使用一个金属度贴图

贴图或者滑块

当使用金属度贴图时,标准着色器的GUI会隐藏滑块,我们也可以这么做,它与凹凸程度的效果一样,当没有纹理的时候就会显示值。

editor.TexturePropertySingleLine(

MakeLabel(map,"Metallic (R)"), map,

map.textureValue ?null: FindProperty("_Metallic")

);

Unity渲染教程(九):复杂材质 https://www.jianshu.com/p/5e3af869870f21

隐藏的滑块

自定义着色器关键字

金属滑块隐藏了,因为标准着色器用一个贴图或者一个uniform值,它们不是乘在一起的。当提供了金属度贴图时,uniform值就会被忽略。为了使用相同的方式,我们需要区分带有和没有金属度贴图的材质,这可以通过个两个着色器变体实现,一个有贴图,一个没有贴图。

由于着色器中的#pragma multi_compile指令,我们的着色器已经有多个变体生成,它们是基于Unity提供的关键字。通过定义我们自己的着色器关键字,我们可以创建我们需要的变体。

你可以任意命名自定义关键字,但是传统是使用一个以下划线开头的大写单词命名,在这个例子中,我们使用_METALLIC_MAP

自定义关键字在哪里定义?

Unity检测项目中的所有自定义关键字,基于multi-compile语句,这些语句把关键字加入材质中。在内部,它们被转换,合并入位掩码,而且每个项目都可以得到不同的关键字标识符。

在Unity 5.4中,位掩码有128位。因此,每个项目可以存在最多128个关键字。其中包括Unity的关键字,加上任何正在使用的自定义关键字。这个限制通常会低一些,这会让具有许多关键字的着色器成为潜在的危险。Unity 5.5会把限制值增加到256。

要向材质中添加自定义关键字,我们必须在我们的GUI中直接访问材质。我们可以通过MaterialEditor.target属性得到当前选择的材质,这实际上是来自Editor基类的一个继承属性,它是通用的Object类型,所以我们必须把它强制转换成Material。

Material target;

MaterialEditor editor;

MaterialProperty[] properties;

publicoverridevoidOnGUI (

MaterialEditor editor, MaterialProperty[] properties

) {

this.target = editor.targetasMaterial;

this.editor = editor;

this.properties = properties;

DoMain();

DoSecondary();

}

Material.EnableKeyword方法可以向着色器中添加一个关键字,其中关键字的名称作为参数。对于移除一个关键字,有Material.DisableKeyword方法。我们一起来创建一个便利的方法,它可以通过一个布尔型参数启用或者禁用一个关键字。

voidSetKeyword (stringkeyword,boolstate) {

if(state) {

target.EnableKeyword(keyword);

}

else{

target.DisableKeyword(keyword);

}

}

调试关键字

你可以使用调试监视器来证实我们的关键字已经加入材质或者从材质中移除,你可以通过选项卡右上的下拉菜单把监视器转到调试模式。自定义关键字在Shader Keywords文本域中以列表形式显示。

Unity渲染教程(九):复杂材质 https://www.jianshu.com/p/5e3af869870f22

调试监视器

你在这里找到的任何预料不到的着色器关键字都已经被定义,因为之前的着色器已经赋值给材质。例如,你选择一个新材质,标准着色器GUI就会添加一个_EMISSION关键字, 它们对于我们的着色器是毫无用处的,所以把它们从列表中移走。

着色器特征

为了生成着色器变体,我们需要向着色器添加另一个multi-compile指令,对于基础通道和附加通道都要这么做,阴影通道不需要。

#pragma target 3.0

#pragma multi_compile _ _METALLIC_MAP

当显示着色器变体时,你会发现我们的自定义关键字已经包含进来了,基础通道现在有总共八个变体。

// Total snippets: 3

// -----------------------------------------

// Snippet #0 platforms ffffffff:

SHADOWS_SCREEN VERTEXLIGHT_ON _METALLIC_MAP

8 keyword variants usedinscene:

 

VERTEXLIGHT_ON

SHADOWS_SCREEN

SHADOWS_SCREEN VERTEXLIGHT_ON

_METALLIC_MAP

VERTEXLIGHT_ON _METALLIC_MAP

SHADOWS_SCREEN _METALLIC_MAP

SHADOWS_SCREEN VERTEXLIGHT_ON _METALLIC_MAP

当使用multi-compile指令时,Unity会为所有可能的组合生成着色器变体。当使用很多关键字时,编译所有的排列会花费很多时间,所有这些变体都包含在构建项目中,这可能是不必要的。

另一个可行方法是定义一个着色器 ,而不是multi-compile指令,区别在于着色器特征的排列只在需要时编译,如果没有材质使用某个关键字,那么关于此关键字的着色器变体将不会被编译。Unity也会检查哪些关键字用在构建项目阶段,只会包含必需的着色器变体。

那么,让我们为我们的自定义关键字使用#pragma shader_feature。

#pragma shader_feature _ _METALLIC_MAP

你什么时候可以使用着色器特征?

当材质在设计阶段配置时——只在编辑器中——之后你大可放心使用着色器特征。但是如果你在运行时调整了材质的关键字,那么你必须确保所有的变体都包含进来了,最简单的方法就是紧跟相关关键字multi-compile指令。或者,你可以使用一个着色器变体集资源。

如果着色器特征是一个关键词的触发器,你可以忽略单下划线。

#pragma shader_feature _METALLIC_MAP

在做了这个改变之后,所有的变体仍旧列了一张表,尽管Unity排列的顺序可能不一样。

// Total snippets: 3

// -----------------------------------------

// Snippet #0 platforms ffffffff:

_METALLIC_MAP

SHADOWS_SCREEN VERTEXLIGHT_ON

8 keyword variants usedinscene:

 

_METALLIC_MAP

VERTEXLIGHT_ON

VERTEXLIGHT_ON _METALLIC_MAP

SHADOWS_SCREEN

SHADOWS_SCREEN _METALLIC_MAP

SHADOWS_SCREEN VERTEXLIGHT_ON

SHADOWS_SCREEN VERTEXLIGHT_ON _METALLIC_MAP

最后,在我们的include文件中调整GetMetallic函数,当定义了_METALLIC_MAP之后,取样贴图,否则就返回uniform值。

floatGetMetallic (Interpolators i) {

#if defined(_METALLIC_MAP)

returntex2D(_MetallicMap, i.uv.xy).r;

#else

return_Metallic;

#endif

}

所以只能用_MetallicMap或者_Metallic之一,而不是全用?

确实是这样,所以材质总会有至少一个无用的属性,为了灵活性,得有点开销。

只在需要的时候设置关键字

此刻,每当OnGUI被调用的时候我们就设置材质的关键词,这种调用很频繁。逻辑上,我们只需要在贴图属性已经被编辑的时候做这件事,我们可以检查通过使用EditorGUI.BeginChangeCheck和EditorGUI.EndChangeCheck方法是否有改变产生。第一个方法定义了我们想要开始追踪变化的点,第二个方法标记了结尾,并且返回是否产生了变化。

通过把这些方法放在TexturePropertySingleLine之前和之后,我们可以很容易地检测金属行是否被编辑了。如果是这样,我们设置关键字。

voidDoMetallic () {

MaterialProperty map = FindProperty("_MetallicMap");

EditorGUI.BeginChangeCheck();

editor.TexturePropertySingleLine(

MakeLabel(map,"Metallic (R)"), map,

map.textureValue ?null: FindProperty("_Metallic")

);

if(EditorGUI.EndChangeCheck()) {

SetKeyword("_METALLIC_MAP", map.textureValue);

}

}

当_Metallic改变时是否也会触发该方法?

是的,当贴图改变时,当uniform值被编辑时,代码都会设置关键字。这比要求的频繁,但是也比总设置强。

这会支持撤销和重做吗?

是的。我们正在使用显示属性的MaterialEditor方法来负责记录原对象的状态。

光滑度贴图

就像金属度贴图一样,光滑度也可以通过一个贴图定义。这里的电路有一个灰度光滑度纹理。金属部分是最光滑的,之后是灯泡,剩余部分非常粗糙。污渍比电路板光滑,所以那里的纹理更浅。

Unity渲染教程(九):复杂材质 https://www.jianshu.com/p/5e3af869870f23

光滑度贴图

Unity的标准着色器希望把光滑度存储在alpha通道中。实际上,希望金属度和光滑度贴图可以在相同的纹理中结合起来,因为DXT5把RGB和A通道分别压缩,把贴图合并到一个DXT5纹理中,可以产生与使用两个DXT1纹理相同的质量。这不会要求更少的内存,但是可以让我们从一次纹理采样中就检索到金属度和光滑度,而不是两次。

有一个纹理合并了贴图。尽管金属度只需要R通道,我依旧会用金属度值填充RGB通道,光滑度使用alpha通道。

Unity渲染教程(九):复杂材质 https://www.jianshu.com/p/5e3af869870f24

金属度和光滑度贴图

决定光滑度

当存在一个金属度贴图时,我们可以从中得到光滑度,否则的话,我们就使用uniform_Smoothness属性,向我们的include文件中加入一个GetSmoothness函数来管理它,这个函数与GetMetallic几乎一模一样。

floatGetSmoothness (Interpolators i) {

#if defined(_METALLIC_MAP)

returntex2D(_MetallicMap, i.uv.xy).a;

#else

return_Smoothness;

#endif

}

我们不还是要取样纹理两次吗?

记住着色器编译器摆脱了重复代码,我们正在不同的函数中采样相同的纹理,但是编译好的代码只会取样纹理一次,我们不需要明确地对这些内容进行缓存。

实际上,标准着色器有两个不同的光滑度属性,一个是单独的uniform值,就像我们的一样,另一个是调整光滑度贴图的标量。让我们简单点做,为了两个目标用_Smoothness属性,这意味着你需要把它设为1来获取未修改的光滑度贴图值。

returntex2D(_MetallicMap, i.uv.xy).a * _Smoothness;

用这个新函数在MyFragmentProgram中得到光滑度。

float4 MyFragmentProgram (Interpolators i) : SV_TARGET {

returnUNITY_BRDF_PBS(

albedo, specularTint,

oneMinusReflectivity, GetSmoothness(i),

i.normal, viewDir,

CreateLight(i), CreateIndirectLight(i, viewDir)

);

}

但是那不是我们唯一使用光滑度的地方,CreateIndirectLight也会使用光滑度。我们可以向这个函数中加入一个光滑度参数,但是我们也可以再调用一次GetSmoothness,着色器编译器会检测重复代码,并且优化。

UnityIndirect CreateIndirectLight (Interpolators i, float3 viewDir) {

Unity_GlossyEnvironmentData envData;

envData.roughness = 1 - GetSmoothness(i);

}

Unity渲染教程(九):复杂材质 https://www.jianshu.com/p/5e3af869870f25

Unity渲染教程(九):复杂材质 https://www.jianshu.com/p/5e3af869870f26

贴图的光滑度,强度最高

那些沿着金属条边缘的方形物件是什么?

那些物件是由于法线贴图的DXT5nm纹理压缩产生的。特别地,如果太瘦的窄脊与紫外线轴不是对齐的,就不能正确估测这些脊。电路中陡峭的对角线边缘是这种压缩最坏的情况,这种局限性在表面上会变得更清晰可见,这些表面是金属的而且非常光滑,否则的话,就不那么明显。

Unity渲染教程(九):复杂材质 https://www.jianshu.com/p/5e3af869870f27

使用未压缩的法线贴图

合并光滑度和Albedo

当你两个都需要时,把金属度和光滑度贴图合并入单一的纹理中很好。金属部分几乎总是比其它位的光滑度高,所以当你需要金属度贴图时,实际上你也总是需要一个光滑度贴图。但是你也有可能需要一个光滑度贴图,而不需要混合金属和非金属,在这种情况下,金属度贴图是没有用的。

对于那些不需要金属度贴图的不透明材质来说,可以在Albedo贴图的阿尔法通道中储存光滑度,这种用法很普遍,标准着色器支持在金属度贴图或者Albedo贴图中打包光滑度,让我们也这么做。

在关键词间转换

就像标准着色器一样,我们需要加一个选项来选择我们GUI的光滑度源。尽管标准着色器只支持两个贴图之间的选择,但是我们可以不用管它,直接把uniform光滑性也作为第三选项包含进来。为了体现这些选项,在MyLightingShaderGUI定义一个枚举类型。

1

2

3enumSmoothnessSource {

Uniform, Albedo, Metallic

}

当Albedo贴图也作为光滑度源使用时,我们会向材质中添加_SMOOTHNESS_ALBEDO关键字。当使用金属度源时,我们会加入_SMOOTHNESS_ALBEDO来代替。同时uniform选项不对应任何关键字。

标准着色器也使用了一个浮点属性来追踪某个材质用了什么选项,但是我们可以只针对关键字实现这一点。GUI可以通过检查启用了哪些关键字来确定当前选择,这可以通过Material.IsKeywordEnabled方法实现,我们将会创建一个简便的封装。

boolIsKeywordEnabled (stringkeyword) {

returntarget.IsKeywordEnabled(keyword);

}

现在DoSmoothness可以找出已选材质当前的光滑度源。

voidDoSmoothness () {

SmoothnessSource source = SmoothnessSource.Uniform;

if(IsKeywordEnabled("_SMOOTHNESS_ALBEDO")) {

source = SmoothnessSource.Albedo;

}

elseif(IsKeywordEnabled("_SMOOTHNESS_METALLIC")) {

source = SmoothnessSource.Metallic;

}

}

为了显示选项,我们可以用EditorGUILayout.EnumPopup方法,另外增加缩进级别,来与标准着色器的布局相匹配。

EditorGUI.indentLevel += 2;

editor.ShaderProperty(slider, MakeLabel(slider));

EditorGUI.indentLevel += 1;

EditorGUILayout.EnumPopup(MakeLabel("Source"), source);

EditorGUI.indentLevel -= 3;

Unity渲染教程(九):复杂材质 https://www.jianshu.com/p/5e3af869870f28

光滑度源弹出

EnumPopup是一个基本编辑器小工具,它可以为任何枚举创建弹出列表,它返回选择的值。如果用户没有选择一个新选项,返回值与原始选择相同,否则的话,值就不同。所以,要想知道选择了哪个选项,我们需要把值赋回给源变量。由于该方法是通用枚举类型的,我们需要把它强制转换成SmoothnessSource

source = (SmoothnessSource)EditorGUILayout.EnumPopup(

MakeLabel("Source"), source

);

如果有改变产生,我们用源变量来控制应该设置哪个关键字,假如有的话。

EditorGUI.BeginChangeCheck();

source = (SmoothnessSource)EditorGUILayout.EnumPopup(

MakeLabel("Source"), source

);

if(EditorGUI.EndChangeCheck()) { SetKeyword("_SMOOTHNESS_ALBEDO", source == SmoothnessSource.Albedo);

SetKeyword("_SMOOTHNESS_METALLIC", source == SmoothnessSource.Metallic);

}

Unity渲染教程(九):复杂材质 https://www.jianshu.com/p/5e3af869870f29

来自金属度贴图的光滑度

支持撤销

我们现在可以改变光滑度源,但是它还并不支持撤销和重做动作,因为我们正在使用基本小工具,我们必须手动表示我们进行了一个支持撤销的动作。这可以通过MaterialEditor.RegisterPropertyChangeUndo方法实现,它有一个参数是描述标签。为这个方法也建立一个封装。

voidRecordAction (stringlabel) {

editor.RegisterPropertyChangeUndo(label);

}

RecordAction必须在我们要改变的部分之前调用,它会为原状态创建一个快照,这样一来撤销动作就可以恢复到原状态。

if(EditorGUI.EndChangeCheck()) {

RecordAction("Smoothness Source");

SetKeyword("_SMOOTHNESS_ALBEDO", source == SmoothnessSource.Albedo);

SetKeyword(

"_SMOOTHNESS_METALLIC", source == SmoothnessSource.Metallic

);

}

光滑度变量

为了全部支持三个选项,加一个着色器特征,特征在没有关键字、_SMOOTHNESS_ALBEDO,和_SMOOTHNESS_METALLIC中选择。如之前,基本和附加通道都必须支持它。

#pragma shader_feature _METALLIC_MAP

#pragma shader_feature _ _SMOOTHNESS_ALBEDO _SMOOTHNESS_METALLIC

在GetSmoothness中,首先从光滑度为1开始,之后检查是否选择了Albedo源,如果选择了,Albedo贴图替换1;否则的话,检查是否选择了金属度源,如果选择了,用金属度源取而代之。当然了,只有当材质确实使用了金属度贴图的时候这才合理,所以也要检查材质是否使用了金属度贴图。

在那之后,返回我们得到的任何光滑度值,乘上_Smoothness属性的值,如果我们最终的变体没有使用贴图,编译器会乘上1来进行优化。

floatGetSmoothness (Interpolators i) {

floatsmoothness = 1;

#if defined(_SMOOTHNESS_ALBEDO)

smoothness = tex2D(_MainTex, i.uv.xy).a;

#elif defined(_SMOOTHNESS_METALLIC) && defined(_METALLIC_MAP)

smoothness = tex2D(_MetallicMap, i.uv.xy).a;

#endif

returnsmoothness * _Smoothness;

}

熔岩材质

这里是冷却熔岩印象的albedo及法线贴图。材质是非金属的,但是光滑度变化不定,所以光滑度值存在Albedo贴图的阿尔法通道中。

Unity渲染教程(九):复杂材质 https://www.jianshu.com/p/5e3af869870f30

Unity渲染教程(九):复杂材质 https://www.jianshu.com/p/5e3af869870f31

带有光滑度和法线的Albedo

用这些贴图创建一个材质,光滑度的Albedo源选项。

Unity渲染教程(九):复杂材质 https://www.jianshu.com/p/5e3af869870f32

熔岩材质

当使用Albedo源时,结果是灰色硬块的光滑度会比红沟高得多。

Unity渲染教程(九):复杂材质 https://www.jianshu.com/p/5e3af869870f33

Unity渲染教程(九):复杂材质 https://www.jianshu.com/p/5e3af869870f34

使用Albedo阿尔法,Uniform和mapped

发射表面

到目前为止,我们只处理过了那些反射灯光的材质,通过漫反射或是镜面反射。我们需要一个光源来看到这些表面,但是也有一些表面自己会发射光亮。比如说:当一些东西变得足够热了,它就会开始发光,而你不需要一个不同的光源来看到它。标准着色器通过发射贴图和颜色支持这一点,我们也会支持。

Mapped和Uniform

向我们的着色器中添加发射贴图和颜色的属性。两者都应该默认是黑色的,这意味着不发射任何光。因为我们只关心RGB通道,我们可以省略默认颜色的第四组件。

[NoScaleOffset] _EmissionMap ("Emission", 2D) ="black"{}

_Emission ("Emission", Color) = (0, 0, 0)

许多材质没有发射贴图,所以,我们用着色器特征来创建需要和不需要发射贴图的变体,因为我们只需要加发射光一次,只在基本通道中包含特征。

#pragma shader_feature _METALLIC_MAP

#pragma shader_feature _ _SMOOTHNESS_ALBEDO _SMOOTHNESS_METALLIC

#pragma shader_feature _EMISSION_MAP

向include文件中加入所需的采样器和浮点变量。

sampler2D _EmissionMap;

float3 _Emission;

创建一个GetEmission函数来检索发射的颜色,假如有的话。当存在贴图时,取样颜色并且乘上uniform颜色;否则的话,就只返回uniform颜色。但是只需在基本通道中做这件麻烦事,在其它所有情况下,发射都是0,编译器会自动优化。

float3 GetEmission (Interpolators i) {

#if defined(FORWARD_BASE_PASS)

#if defined(_EMISSION_MAP)

returntex2D(_EmissionMap, i.uv.xy) * _Emission;

#else

return_Emission;

#endif

#else

return0;

#endif

}

由于发射自己来源于对象,它是独立于反射光的,所以就把它加到final颜色中吧。

float4 MyFragmentProgram (Interpolators i) : SV_TARGET {

float4 color = UNITY_BRDF_PBS(

albedo, specularTint,

oneMinusReflectivity, GetSmoothness(i),

i.normal, viewDir,

CreateLight(i), CreateIndirectLight(i, viewDir)

);

color.rgb += GetEmission(i);

returncolor;

}

向GUI添加发射

MyLightingShaderGUI内部添加一个DoEmission方法,做这件事最快的方法是拷贝DoMetallic,再做一些改变。

voidDoEmission () {

MaterialProperty map = FindProperty("_EmissionMap");

EditorGUI.BeginChangeCheck();

editor.TexturePropertySingleLine(

MakeLabel(map,"Emission (RGB)"), map, FindProperty("_Emission")

);

if(EditorGUI.EndChangeCheck()) {

SetKeyword("_EMISSION_MAP", map.textureValue);

}

}

把它包含进入主要贴图部分。

voidDoMain () {

DoNormals();

DoEmission();

editor.TextureScaleOffsetProperty(mainTex);

}

Unity渲染教程(九):复杂材质 https://www.jianshu.com/p/5e3af869870f35

带有发射贴图和颜色的监视器

HDR发射

标准着色器不使用发射的常规颜色,不同的是,它支持高动态区域颜色。这意味着颜色的RGB组件可以高于1,这样一来,你可以表现出非常明亮的颜色。

我们能看到亮度大于1的颜色吗?

在现实生活中,轰炸你的光子的数量没有限制。太阳也非常明亮,也很炫目。但是,计算机显示是有限的,你不能比1更高,有多亮取决于显示的亮度。

要在一面有意义的墙上使用HDR颜色,你必须实现色调映射。这意味着你将颜色从一个范围转换到一个范围。我们会在未来的教程中深入研究色调映射。HDR颜色也经常用于创建闪亮效果。

因为颜色属性是浮点向量,我们的值不止限于0到1。但是,标准颜色小工具的值本身是0到1。幸运的是,MaterialEditor包括TexturePropertyWithHDRColor方法,这就是特别为纹理加上一个HDR颜色属性准备的,它需要两个额外参数,第一个参数是HDR范围的选项;第二个参数是是否应该显示阿尔法通道,这并不是我们想要的。

voidDoEmission () {

editor.TexturePropertyWithHDRColor(

MakeLabel("Emission (RGB)"), map, FindProperty("_Emission"),

emissionConfig,false

);

}

HDR颜色小工具通过ColorPickerHDRConfig对象配置,该对象包括允许的亮度和曝光范围。标准着色器的亮度是从0到99,曝光度是从近于0到3.我们就简单地使用相同的范围。

staticColorPickerHDRConfig emissionConfig =

newColorPickerHDRConfig(0f, 99f, 1f / 99f, 3f);

Unity渲染教程(九):复杂材质 https://www.jianshu.com/p/5e3af869870f36

带有HDR发射的监视器

在颜色选择器之后额外的值对应颜色的亮度,这应该是RGB通道最大的一个值,把发射颜色从转换到黑或白最快的方法就是把这个值设为0或1。

发射光的熔岩

这里有一个熔岩材质的发射贴图,它使沟壑中的熔岩发光发热,你可以通过调整uniform颜色来改变发射的亮度和色彩。

Unity渲染教程(九):复杂材质 https://www.jianshu.com/p/5e3af869870f37

Unity渲染教程(九):复杂材质 https://www.jianshu.com/p/5e3af869870f38

熔岩的发射贴图

我给发射贴图赋值了,但是它没显示?

在这种情况下,uniform发射颜色仍然是黑色,要想完全看到贴图,把颜色设置成白色。

当纹理已经被赋值但是颜色还是黑色的时候,标准着色器自动把发射颜色设成白色。你也可以把这个功能加上。但是,这种行为可能对于一些人来说不是真的,这会带来许多用户满意度的降低。

发光的容颜,点亮的和未点亮的

Unity渲染教程(九):复杂材质 https://www.jianshu.com/p/5e3af869870f39

Unity渲染教程(九):复杂材质 https://www.jianshu.com/p/5e3af869870f40

自发光的电路

这里是电路光照的放射贴图。

Unity渲染教程(九):复杂材质 https://www.jianshu.com/p/5e3af869870f41

Unity渲染教程(九):复杂材质 https://www.jianshu.com/p/5e3af869870f42

电路的放射贴图

光线有变化的亮度,污渍也会影响光线。

Unity渲染教程(九):复杂材质 https://www.jianshu.com/p/5e3af869870f43

Unity渲染教程(九):复杂材质 https://www.jianshu.com/p/5e3af869870f44

带有运行光照的电路,点亮的和未点亮的

发射的光会照亮其他物体吗?

发射只是材质的一部分,它不会影响场景的剩余部分。但是,Unity的全局照明系统可以检测到发射光,并且把它加到非直接照明数据中。我们会在之后的教程中深入了解全局照明。



作者:LeiLv
链接:https://www.jianshu.com/p/5e3af869870f
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

 

 

 

 

勿删,copyright占位
分享文章到微博
分享文章到朋友圈

上一篇:如何做好决策?

下一篇:新手科普 | MySQL手工注入之基本注入流程

您可能感兴趣

  • asp.net高级教程(续)

    前面讲到如何构造bbs对象,有朋友要求我简单介绍一下c#里如何构造对象,下面我就简单说一下,算是补上这一课吧。    C#里的类(Class),也可以叫做对象(object),它由以下几部分组成:成员变量,属性和方法,其中必不可少的是这个类不带任何参数的构造函数,它不指定返回类型,作用是初始化类的成员变量、分配内存等。和c++不同,c#类只有构造函数,不需要析购函数,也就是说你只需要为成员变...

  • DirectX5.0最新游戏编程指南 DirectDraw教程篇 一、配置DirectX SDK

    DirectX5.0最新游戏编程指南DirectDraw教程篇     DirectX是为Visual C++的用户准备的,因此要编制DirectDraw游戏程序,最好对VC要有一定的了解。不愿意使用VC的用户也可以利用消息Arakelian Soft公司开发的专门针对Visual Basic5.0用户的ActiveX控件DirectStudio98或Tegosoft公司的TegoSoft ...

  • DirectX5.0最新游戏编程指南 DirectDraw教程篇 二、第一个DirectDraw实例

    二、第一个DirectDraw实例    要使用DirectDraw,首先必须创建DirectDraw对象的一个实例来表征计算机上的显示适配卡,然后使用接口方法来处理对象。另外还需要创建一个或多个DirectDrawSurface对象的实例来显示游戏。DDEX1首先创建一个DirectDraw对象,再创建一个主表面(primary surface)和一个后台缓冲区(back buffer),...

  • DirectX5.0最新游戏编程指南 DirectDraw教程篇 三、创建动画

    三、创建动画    上面的例子都只是将数据写入后台缓冲区,然后将后台缓冲区与主表面翻转,其速度并不太快。下面的例子DDEX4和DDEX5优化了实时功能,使看起来更象一个真正的游戏。DDEX4显示了怎样为表面设置 Color key,怎样使用IDirectDrawSurface::BltFast方法将屏外表面各部分拷贝到后台缓冲区以产生动画。DDEX5加入了读取调色板并在动画运行时改变调色板的...

  • 第一个三角形:Jeff Molofee(NeHe) 的OPENGL教程-第二课

    Jeff Molofee(NeHe) 的 OPENGL 教程 第二课 Translated by CKER 第一课中,我教您如何创建一个OpenGL窗口。这一课中,我将教您如何创建三角形和四边形。我们讲使用来创建GL_TRIANGLES一个三角形,GL_QUADS来创建一个四边形。 在第一课代码的基础上,我们只需在DrawGLScene()过程中增加代码。下面我重写整个过程。如果您...

  • 利用VB设计打印复杂报表

    数据库管理系统的开发人员经常感叹的一个问题就是:我们中国人的报表太复杂了!无规则、嵌套、斜线、交叉线等历来都是困挠开发人员的最大问题。设计一个数据库固然有一定的技巧,设计数据操作也固然需要一定的逻辑分析能力,但这些问题对一般的开发人员来说应该是不成为问题的。用户可是不管你采用了多么灵活的算法多么方便的操作,他们最感兴趣的是最后他们出来的报表如何漂亮,出报表的操作如何简单(最好是一个按钮解决所...

  • C#简明教程(四)

    C#简明教程(四) 声明:我在翻译的时候,加了一些自己的看法以及其它的一些比较,希望能让大家看的更明白,理解更透彻。 有什么建议请mail:efoxxx@263.net 对了,那位在专家门诊上贴了关于“找到C#编译器”的兄弟,我怎么还没有收到你答应的C#编译器,急用,劳驾这位兄弟百忙之中给我发一个过来,谢了!   高效C#――字符串编程   ◆空字符串 检测一个字符串是否为空是一个基本的...

  • C#简明教程(六)---建立第一个C#工程

    C#简明教程   建立第一个C#工程(Project)   本文将一步一步指导你完成你的第一个C#工程。  第一步: 生产一个空的C#工程 从菜单选取 File->New->Project->Visual C# Projects-> Empty Project. 添上你的工程名, 并通过Browse 按钮来选择一个合适的目录。如下图所示:   第二步: 为它添加一个空类     在Solut...

CSDN

CSDN

中国开发者社区CSDN (Chinese Software Developer Network) 创立于1999年,致力为中国开发者提供知识传播、在线学习、职业发展等全生命周期服务。

华为云40多款云服务产品0元试用活动

免费套餐,马上领取!
Unity渲染教程(九):复杂材质 https://www.jianshu.com/p/5e3af869870f介绍:华为云为您免费提供Unity渲染教程(九):复杂材质 https://www.jianshu.com/p/5e3af869870f在博客、论坛、帮助中心等栏目的相关文章,同时还可以通过 站内搜索 查询更多Unity渲染教程(九):复杂材质 https://www.jianshu.com/p/5e3af869870f的相关内容。| 移动地址: Unity渲染教程(九):复杂材质 https://www.jianshu.com/p/5e3af869870f | 写博客