创建一个自定义的shader GUI
混合金属和非金属
使用不统一的光滑度
支持自发光表面
本节是渲染课程的第九节。上一节我们学习了如何反射周围的环境,包括反射环境光和周围的建筑物。本节我们会使用更多多的贴图创建更复杂的材质。但是在此之前,我们要需要一个更好的GUI来显示shader的属性。
本节课程使用untiy版本为5.4.1f1
1 用户接口
unity标准的shader有很好的展示界面,我们也要自己定制一个属性面板,来显示shader。
1.1 ShaderGUI
创建一个继承了UnityEditor.SahderGUI类,如下:
using UnityEngine;
using UnityEditor;
public class MyLightingSahderGUI:ShaderGUI
{
……
}
为了使用自定义的渲染器,还需要在shader中添加一行代码:
Shader "Custom/My First Lighting Shader"
{
CustomEditor "MyLightingShaderGUI"
}
重新OnGUI方法:
public class MyLightingShaderGUI:ShaderGUI
{
public override void OnGUI(MaterialEditor editor, MaterialProperty[] properties)
{
……
}
}
1.2 创建一个label
public override void OnGUI (
MaterialEditor editor, MaterialProperty[] properties
) {
DoMain();
}
void DoMain() {
GUILayout.Label("Main Maps", EditorStyles.boldLabel);
}
效果如下:
1.3 显示反射率
把OnGUI中的传递的属性数组保存起来:
MaterialEditor editor;
MaterialProperty[] properties;
public override void OnGUI(MaterialEditor editor, MaterialProperty[] properties)
{
this.eidtor = editor;
this.properties = properties;
DoMain();
}
根据名字找到shader中的某个属性:
void DoMain()
{
MaterialProperty mainTex = FindProperty("_MainTex", properties);
}
给属性一个显示的名字:
void DoMain()
{
MaterialProperty mainTex = FindProperty("_MainTex", properties);
GUIContent albedoLabel = new GUIContent("Albedo");
}
因为在shader中属性_MainTex的显示名就已经为Albedo:
_MainTex("Albedo", 2D) = "white"{}
所以我们可以直接用这个属性的展示名:
void DoMain()
{
MaterialProperty mainTex = FindProperty("_MainTex", properties);
GUIContent albedoLabel = new GUIContent(mainTex.displayName);
editor.TexturePropertySingleLine(albedoLabel, mainTex);
}
这样写了之后:
这个和unity内置的standard shader类似,但是内置的比我们自定义的还多了提示信息。当你的鼠标悬停在属性上的时候要给出提示可以通过下面的方法:
GUIContent albedoLabel = new GUIContent(mainTex.displayName, "Albedo (RGB)");
只要把原来的label后面再添加要给tip字符串就可以了。
TexturePropertySingleLine 函数后面还可以加上额外的属性,然后显示在一行,比如下面:
MaterialProperty tint = FindProperty("_Tint", properties);
editor.TexturePropertySingleLine(albedoLabel, mainTex, tint);
为了贴图增加scale和offset的设置选项:
editor.TextureScaleOffsetProperty(mainTex);
1.4 提取方法
上面找属性的方法可以共用出来,如下:
MaterialProperty FindProperty (string name)
{
return FindProperty(name, properties);
}
简化之后我们的代码变为:
void 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);
}
创建一个静态的文本方法:
static GUIContent staticLabel = new GUIContent();
static GUIContent MakeLabel(string text, string toolTip = null)
{
staticLabel.text = text;
staticLabel.tooltip = toolTip;
return staticLabel;
}
更简单的方法是直接利用属性的display name作为label的text。则有如下的方法:
static GUIContent MakeLabel(MaterialProperty property, string tooltip = null)
{
staticLabel.text = property.displayName;
staticLabel.tooltip = tooltip;
return staticLabel;
}
那么现在就可以进一步的简化代码了:
void DoMain()
{
GUILayout.Label("Main Maps", EditorStyles.boldLabel);
MaterialProperty mainTex = FindProperty("_MainTex");
editor.TexturePropertySingleLine(MakeLabel(mainTex, "Albedo (RGB)"), mainTex , FindProperty("_Tint"));
editor.TextureScaleOffsetProperty(mainTex);
}
1.5 显示法线
创建单独的函数显示法线:
DoNormals();
editor.TextureScaleOffsetProperty(mainTex);
然后再DoNormals()中加入下面的代码:
void DoNormals()
{
MaterialProperty map = FindProperty("_NormalMap");
editor.TexturePropertySingleLine(MakeLabel(map), map);
}
然后在法线贴图下面添加一个凹凸系数:
void DoNormals()
{
MaterialProperty map = FindProperty("_NormalMap");
editor.TexturePropertySingleLine(MakeLabel(map), map, FindProperty("_BumpScale"));
}
这样之后就会得到:
这样不管Normals贴图是否赋值上去,都会有一个scale,那么标准的shader中不是这样的,当有值得时候才会显示这个scale。
1.6 显示金属和光滑度
void 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;
}
1.7 显示第二个纹理贴图和法线贴图
和显示第一个贴图一样,我们直接这样做:
void DoSecondary () {
GUILayout.Label("Secondary Maps", EditorStyles.boldLabel);
MaterialProperty detailTex = FindProperty("_DetailTex");
editor.TexturePropertySingleLine(
MakeLabel(detailTex, "Albedo (RGB) multiplied by 2"), detailTex
);
DoSecondaryNormals();
editor.TextureScaleOffsetProperty(detailTex);
}
void DoSecondaryNormals () {
MaterialProperty map = FindProperty("_DetailNormalMap");
editor.TexturePropertySingleLine(
MakeLabel(map), map,
map.textureValue ? FindProperty("_DetailBumpScale") : null
);
}
2 混合金属和非金属
我们的shader是使用同一的变量来控制物体表面是否是金属,所以一个物体不是金属就是非金属,这个灵活性不大,所以需要使用一个滑动条来控制某个材质介于金属和非金属之间。
2.1 金属贴图
内置标准shader支持金属贴图,这个贴图定义了每个像素位置的金属性,而不是整个材质是否是金属。下面是一张灰白图来标记电路板的金属性。
增加一个金属贴图属性:
[NoScaleOffset] _MetallicMap ("Metallic", 2D) = "white" {}
[Gamma] _Metallic ("Metallic", Range(0, 1)) = 0
使用这个贴图的方法:
sampler2D _MetallicMap;
float _Metallic;
struct Interpolators
{
……
};
float GetMetallic(Interpolators i)
{
return tex2D(_MetallicMap, i.uv.xy).r * _Metallic;
}
然后是在片段着色器中使用赋颜色:
float MyFragmentProgram(Interpolators i):SV_TARGET
{
……
albedo = DiffuseAndSpecularFromMetallic(albedo, GetMetallic(i), specularTint, oneMinusReflectivity);
……
}
你可自己定义计算金属性的方法,在GetMetallic方法中修改。DiffuseAndSpecularFromMetallic函数在UnityStandardUtils中定义。
2.2 自定义GUI
添加金属贴图自定义面板:
void DoMetallic ()
{
MaterialProperty map = FindProperty("_MetallicMap");
editor.TexturePropertySingleLine(
MakeLabel(map, "Metallic (R)"), map,
FindProperty("_Metallic")
);
}
2.3 贴图和滑动条
选择用金属贴图或者是滑动条:
editor.TexturePropertySingleLine(
MakeLabel(map, "Metallic (R)"), map,
map.textureValue ? null : FindProperty("_Metallic")
);
无金属贴图:
2.4 自定义shader关键字