US5L1——ShaderGUI类

前置知识:UG1——IMGUI系列,UED——Unity编辑器拓展

本章代码关键字

1
2
3
4
5
6
7
8
9
10
11
12
ShaderGUI                            // 用于自定义材质Inspector界面的基类,通过继承ShaderGUI,可以完全控制材质编辑界面的布局和功能
void OnGUI() {} // 继承ShaderGUI的类需要重写该方法以实现自定义材质Inspector界面
MaterialEditor // 提供与材质交互的接口,比如材质属性值等
materialEditor.target // 可以利用它获取到材质球,需要手动as一次
materialEditor.ShaderProperty() // 可以将属性设置为对应名字,并且使用默认GUI显示
MaterialProperty // 在Shader的属性语句块中定义的属性
materialProperty.displayName // 获取属性名
materialProperty._Value // 获取属性当前的值,需要类型+Value来获取对应类型的值
materialProperty.floatValue // 获取float属性的当前值
materialProperty.textureValue // 获取贴图属性的当前值
shaderGUI.FindProperty() // 通过属性名直接获取到对应属性对象的方法
material.renderQueue // 材质的渲染队列属性

自定义材质面板

我们目前可以通过在 Shader 中添加属性的形式,把一些我们希望从外部设置的内容在材质球的 Inspector 窗口中显示

1
2
3
4
5
6
7
8
9
10
Properties
{
_MainTex("MainTex", 2D) = ""{} // 主纹理
_BumpMap("BumpMap", 2D) = ""{} // 法线纹理
_Cube("Cubemap", Cube) = ""{} // 立方体纹理
_Distortion("Distortion", Range(0, 10)) = 0 // 控制扭曲程度的变量
_WaveXSpeed("WaveXSpeed", Range(-0.1, 0.1)) = 0.01 // 控制水波水平速度偏移的属性
_WaveYSpeed("WaveYSpeed", Range(-0.1, 0.1)) = 0.01 // 控制水波竖直速度偏移的属性
_FresnelScale("FresnelScale", Range(0, 1)) = 1 // 菲涅尔反射率
}

image

自定义材质面板指的就是,Unity 除了这些默认的显示内容外,还可以让我们自定义材质球的 Inspector 窗口显示

ShaderGUI

它是一个用于自定义材质 Inspector 界面 的基类,通过继承 ShaderGUI​,你可以完全控制材质编辑界面的布局和功能
而不仅仅局限于 Shader 的 属性(Properties​)语句块定义的默认行为

ShaderGUI​ 可以让我们:

  1. 自定义材质界面布局
  2. 通过脚本逻辑,可以基于某些属性值动态隐藏或禁用其他属性
  3. 添加高级功能,比如通过添加按钮控件,触发某些操作,如实时更新材质预览,显示调试信息等
  4. 增强材质编辑体验,使美术人员或其他开发者无需关心底层 Shader 实现,而是通过友好的界面快速调整材质

等等

ShaderGUI类的基本使用

  1. 自定义 C# 脚本继承 ShaderGUI
  2. 重写 OnGUI​ 方法
  3. OnGUI​ 方法中书写自定义布局逻辑
  4. Shader​ 语句块最后加入 CustomEditor "自定义C#脚本名"

这样便可以让使用该 Shader 的材质面板使用自定义布局规则

假设要对下面的 Shader 自定义材质 Inspector 界面:

1
2
3
4
5
6
7
8
9
10
11
Shader "Unlit/CustomInspectorTest"
{
Properties
{
_MainTex("Texture", 2D) = "white"{}
_TestFloat("TestFloat", Float) = 1
}
SubShader {/*...*/}

CustomEditor "Lesson127_CustomShaderInspector"
}

当直接调用基类的方法时:

1
2
3
4
5
6
7
8
9
using UnityEditor;

public class Lesson127_CustomShaderInspector : ShaderGUI
{
public override void OnGUI(MaterialEditor materialEditor, MaterialProperty[] properties)
{
base.OnGUI(materialEditor, properties);
}
}

显示效果:

image

可见,base.OnGUI​ 的逻辑就是原来的绘制逻辑

OnGUI​ 不实现任何内容时:

1
2
3
4
5
6
7
8
9
using UnityEditor;

public class Lesson127_CustomShaderInspector : ShaderGUI
{
public override void OnGUI(MaterialEditor materialEditor, MaterialProperty[] properties)
{
// base.OnGUI(materialEditor, properties);
}
}

显示效果:

image

可见,当 OnGUI​ 没有逻辑时,Shader 在 Inspector 窗口上也没有任何内容

自定义材质面板

使用 ShaderGUI​ 自定义材质面板的核心方法就是我们重写的:void OnGUI(MaterialEditor materialEditor, MaterialProperty[] properties)​ 方法

其中两个参数非常重要:

  1. materialEditor​:提供与材质交互的接口,比如材质属性值等

    • 关键属性:target​,可以利用它获取到材质球

      1
      2
      3
      4
      5
      6
      public override void OnGUI(MaterialEditor materialEditor, MaterialProperty[] properties)
      {
      // 获取当前材质球
      Material material = materialEditor.target as Material;
      // 之后即可利用材质球去Set或者Get属性的值
      }
    • 关键方法:ShaderProperty(MaterialProperty对象, "名字")​,可以将属性设置为对应名字,并且使用默认 GUI 显示

      • 参数一:要显示的 MaterialProperty​ 对象
      • 参数二:属性名显示文本,可以自定义,也可以直接使用 materialProperty.displayName​ 用 Shader 定义的属性名设置
      1
      2
      3
      4
      5
      6
      7
      8
      public override void OnGUI(MaterialEditor materialEditor, MaterialProperty[] properties)
      {
      // 之后即可利用材质球去Set或者Get属性的值
      foreach (MaterialProperty property in properties)
      {
      materialEditor.ShaderProperty(property, property.displayName);
      }
      }

      显示效果:

      image

  2. properties​:包含所有在 Shader 的属性语句块中定义的属性

    可以通过 foreach​ 遍历 properties​ 的所有 MaterialProperty​,也就是 Shader 内定义的属性
    也可以直接使用 FindProperty​ 来直接查找某个特定的 MaterialProperty​ 属性

    • MaterialProperty​ 关键属性:displayName​,获取属性名

      我们可以通过 displayName​ 来判断是否是特定属性

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      using UnityEditor;
      using UnityEngine;

      public class Lesson127_CustomShaderInspector : ShaderGUI
      {
      private float testFloatValue;

      public override void OnGUI(MaterialEditor materialEditor, MaterialProperty[] properties)
      {
      // 获取当前材质球
      Material material = materialEditor.target as Material;

      foreach (MaterialProperty property in properties)
      {
      // 如果是TestFloat就自定义其显示
      if (property.displayName == "TestFloat")
      {
      testFloatValue = EditorGUILayout.Slider("自定义float属性", testFloatValue, -1, 1);
      material.SetFloat(property.displayName, testFloatValue);
      }
      // 其他属性按照原样绘制
      else
      {
      materialEditor.ShaderProperty(property, property.displayName);
      }
      }
      }
      }

      显示效果:

      image

      可见,TestFloat​ 这个属性的显示方式被改变了

    • 通过 类型+Value​ 的属性可以直接获取材质的 Shader 属性当前值

      除了通过获取材质球然后使用 Get​ 和 Set​ 的方法去获取或设置对应的属性的值,
      还可以使用 materialProperty.类型+Value​ 的属性直接获取和修改对应属性的值,
      例如 floatValue​ 即可获取 float​ 类型属性当前的值、textureValue​ 即可获取贴图类型属性的当前值

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      public override void OnGUI(MaterialEditor materialEditor, MaterialProperty[] properties)
      {
      // 获取当前材质球
      Material material = materialEditor.target as Material;

      // 自定义一个拖动条去设置TestFloat属性
      property = FindProperty("_TestFloat", properties);
      property.floatValue = EditorGUILayout.Slider("自定义float属性", property.floatValue, -1, 1);
      material.SetFloat(property.displayName, property.floatValue);
      }
    • 直接查找某个属性:MaterialProperty 属性对象 = FindProperty("属性名", properties)

      可以利用 FindProperty​ 方法,通过属性名直接获取到对应属性对象,而不用从 properties​ 遍历查找

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      public override void OnGUI(MaterialEditor materialEditor, MaterialProperty[] properties)
      {
      // 获取当前材质球
      Material material = materialEditor.target as Material;

      MaterialProperty property = FindProperty("_MainTex", properties);
      materialEditor.ShaderProperty(property, property.displayName);
      // 自定义一个拖动条去设置TestFloat属性
      property = FindProperty("_TestFloat", properties);
      property.floatValue = EditorGUILayout.Slider("自定义float属性", property.floatValue, -1, 1);
      material.SetFloat(property.displayName, property.floatValue);
      }

      显示效果:

      image

  3. 如果要设置材质的渲染队列,可以使用 material.renderQueue​ 属性去获取和修改

    1
    2
    3
    4
    5
    6
    public override void OnGUI(MaterialEditor materialEditor, MaterialProperty[] properties)
    {
    // 获取当前材质球
    Material material = materialEditor.target as Material;
    material.renderQueue = EditorGUILayout.IntField("渲染队列", material.renderQueue);
    }

    显示效果

    image

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
using UnityEditor;
using UnityEngine;

public class Lesson127_CustomShaderInspector : ShaderGUI
{
private bool isShow;

public override void OnGUI(MaterialEditor materialEditor, MaterialProperty[] properties)
{
if (GUILayout.Button(isShow ? "隐藏所有属性设置" : "显示所有属性设置"))
{
isShow = !isShow;
}

// 获取当前材质球
Material material = materialEditor.target as Material;

if (GUILayout.Button("重置材质球属性"))
{
material.SetTexture("_MainTex", null);
material.SetFloat("_TestFloat", 0);
}

if (isShow)
{
MaterialProperty property = FindProperty("_MainTex", properties);
materialEditor.ShaderProperty(property, property.displayName);
// 自定义一个拖动条去设置TestFloat属性
property = FindProperty("_TestFloat", properties);
property.floatValue = EditorGUILayout.Slider("自定义float属性", property.floatValue, -1, 1);
material.SetFloat(property.displayName, property.floatValue);
material.renderQueue = EditorGUILayout.IntField("渲染队列", material.renderQueue);
}
}
}

显示效果:

image