大家好,我是阿赵。
  这次来介绍一下Unity引擎的自定义ShaderGUI。

Unity引擎自定义ShaderGUI

一、用途说明

  在写Shader的时候,可能会定义了很多变量,还有一些宏的定义之类。一般显示在材质球上面,会是这样子:
在这里插入图片描述

  由于在Shader里面定义变量的时候,可以指定显示名字,所以一些变量可以通过名字来看出它是什么内容。但比如宏定义这种,默认是不会显示在材质球属性上面的。
  如果Shader的使用者不懂看代码,单纯靠显示的变量名字去猜测Shader的用法,可能会出现偏差。为了让Shader在材质球上面能更容易使用,Unity是允许我们自定义Shader在材质球上面的显示的。
  比如上面的这个Shader,我可以改成这样:
在这里插入图片描述

  也可以显示成这样:
在这里插入图片描述

  由于我这个Shader是挺简单的光照模型加法线贴图显示的Shader,所以用到的参数不多,为了增加GUI的复杂度,我做了一个简单模式和复杂模式,对于相同的变量属性,我用了不同的样式去显示,在高级模式还添加了一个预览效果的大图显示。然后在是否启用法线的宏做了勾选,这样使用这个Shader的人就能很清楚的知道用法,也不需要去记住参数名称和宏定义的名称。

二、 ShaderGUI的编写

1、 ShaderGUI的基本结构

在Editor文件夹新建一个C#文件:

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

  这是ShaderGUI最基本的结构,using UnityEditor,然后继承ShaderGUI,并且重写实现OnGUI方法。

  这时候,在对应的目标Shader的最外层大括号里面,加上一句

CustomEditor "NormalShaderEditor"

  那么,这个shader就会指定刚才的NormalShaderEditor 来显示了。
在这里插入图片描述

  这时候一看,法线和没有自定义之前没有任何区别。
  这是因为base.OnGUI(materialEditor, properties);这里调用了父类的方法,就会显示默认的属性。所以先把这句代码注释掉:

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

在这里插入图片描述

  现在材质球上面,就变得什么都没有显示了,接下来就可以自定义显示内容了。

2、 获取材质球上的参数

public override void OnGUI(MaterialEditor materialEditor, MaterialProperty[] properties)

  在OnGUI重载方法里面,会传入2个参数,第一个参数materialEditor是当前这个材质编辑器界面,第二个参数properties则是当前所用的Shader里面定义的所有变量。
我们要自定义显示,第一步首先是要获得当前这个材质球关联的Shader的各种参数。
获取的方法有2种

1. 通过MaterialProperty获取

  由于传进来的properties参数里面已经包含了所有变量参数,所以可以直接从这里获取。
举例,我要获取”_MainTex”这个参数,可以:

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

  这样获取到的mainTex,是一个MaterialProperty对象。它里面包含了参数的类型和值。比如如果Shader里面定义的变量是贴图,它就是贴图。如果Shader里面定义的是Range范围值,那么这个MaterialProperty对象也会包含了Range范围的最大最小值的数据。所以其实我们获取的时候,是不需要指定类型的,只需要有参数名字就能获取。
  MaterialProperty是要配合着MaterialEditor使用的,下面会说到。

2. 通过材质球获取

  我们也可以直接获得当前材质编辑器对应的材质球,然后通过正常材质球操作的方法,来获得需要的参数。
  首先要获得材质球:

Material mat = materialEditor.target as Material;

  然后还是用”_MainTex”作为例子:

Texture2D tex = (Texture2D)mat.GetTexture("_MainTex");

  从材质球获取参数,是需要知道类型的,比如获取贴图是用GetTexture,获取浮点数是用GetFloat。
  通过这种方法获取的是Unity通用的变量类型,可以和正常的GUI命令配合使用。

3、 显示各种组件

  由于获取参数有2种方式,说显示组件同样也有2种方式。通过材质球获取的通用变量类型,和写正常的编辑器显示没什么区别,可以用GUI、GUILayout、EditorGUILayout等的常规方法来显示。这里就不多说了。
  说一下ShaderGUI特有的,通过MaterialEditor和MaterialProperty配合的显示方式。

1. 通用显示

  如果完全不知道MaterialEditor有什么显示方法,那么可以有一个通用的显示:

MaterialEditor.ShaderProperty

  使用方法很简单,传入2个参数

public void ShaderProperty(MaterialProperty prop, string label);

  第一个参数是之前获得的随便一个MaterialProperty,第二个参数是显示的文字。
比如:

MaterialProperty specIntensity = FindProperty("_specIntensity", properties);
materialEditor.ShaderProperty(specIntensity, "高光强度");

  我通过FindProperty获取了一个specIntensity,然后传进去materialEditor.ShaderProperty,我可能不知道specIntensity的实际类型,但MaterialEditor会帮我们找到合适的显示:
在这里插入图片描述

  这样是最省事的,对于一些不需要特殊显示的参数,这样就可以了。
  如果想指定在某个范围内显示,也可以传入Rect

public void ShaderProperty(Rect position, MaterialProperty prop, string label);

2. 贴图类

  对于贴图类的参数,有多种显示的方法

1. MaterialEditor.TextureProperty

  这个贴图显示的方式,就是平常看到的
在这里插入图片描述

  可以通过不同的参数重载,决定是否需要显示scaleOffset,是否有tip显示,或者是否指定具体的Rect位置。
在这里插入图片描述

2. MaterialEditor.TexturePropertyMiniThumbnail
public Texture TexturePropertyMiniThumbnail(Rect position, MaterialProperty prop, string label, string tooltip);

这个方法显示的是一行的贴图属性
比如这样:

materialEditor.TexturePropertyMiniThumbnail(new Rect(10, 10, 200, 20), mainTex, "漫反射贴图", "用于漫反射颜色显示的贴图");

在这里插入图片描述

鼠标悬浮在文字上时,会有tip显示。

3. MaterialEditor.TexturePropertySingleLine

  这个是一行显示贴图属性,有多个重载
在这里插入图片描述

比如这样:

materialEditor.TexturePropertySingleLine(new GUIContent("漫反射贴图"), mainTex);

在这里插入图片描述

或者这样:

mainTex = FindProperty("_MainTex", properties);
baseColor = FindProperty("_BaseColor", properties);
materialEditor.TexturePropertySingleLine(new GUIContent("漫反射贴图"), mainTex,baseColor);

在这里插入图片描述

或者这样:

	mainTex = FindProperty("_MainTex", properties);
    	baseColor = FindProperty("_BaseColor", properties);
   	ambientIntensity = FindProperty("_ambientIntensity", properties);
materialEditor.TexturePropertySingleLine(new GUIContent("漫反射贴图"), mainTex,baseColor,ambientIntensity);

在这里插入图片描述

4. MaterialEditor.TexturePropertyTwoLines
public Rect TexturePropertyTwoLines(GUIContent label, MaterialProperty textureProp, MaterialProperty extraProperty1, GUIContent label2, MaterialProperty extraProperty2);

用两行来显示贴图属性
比如这样:

mainTex = FindProperty("_MainTex", properties);
baseColor = FindProperty("_BaseColor", properties);
ambientIntensity = FindProperty("_ambientIntensity", properties);
materialEditor.TexturePropertyTwoLines(new GUIContent("漫反射贴图"), mainTex, baseColor, new GUIContent("环境光强度"), ambientIntensity);

在这里插入图片描述

第一行会显示2个属性,然后第二行的属性会有缩进效果

5. MaterialEditor.TexturePropertyWithHDRColor
public Rect TexturePropertyWithHDRColor(GUIContent label, MaterialProperty textureProp, MaterialProperty colorProperty, bool showAlpha);

显示贴图和HDR颜色
比如这样:

mainTex = FindProperty("_MainTex", properties);
baseColor = FindProperty("_BaseColor", properties);
materialEditor.TexturePropertyWithHDRColor(new GUIContent("漫反射贴图"), mainTex, baseColor,true);

在这里插入图片描述

在这里插入图片描述

  最后一个参数决定点开的HDR颜色拾取界面有没有Alpha通道。

6. MaterialEditor.TextureScaleOffsetProperty

  由于上面很多简略的贴图属性显示,都没有显示贴图的平铺和偏移的,所以可以单独显示:
比如:

materialEditor.TextureScaleOffsetProperty(mainTex);

在这里插入图片描述

  也可以通过重载,指定显示的Rect
在这里插入图片描述

3. 数字类

1. MaterialEditor.FloatProperty

  简单的显示一个浮点参数:
比如:

ambientIntensity = FindProperty("_ambientIntensity", properties);
materialEditor.FloatProperty(ambientIntensity, "环境光强度");

在这里插入图片描述

或者通过重载指定显示的Rect
在这里插入图片描述

2. MaterialEditor.RangeProperty

  如果在Shader里面定义的参数类型是Range范围值,可以通过这个方法显示一个滑条。
比如:

ambientIntensity = FindProperty("_ambientIntensity", properties);
materialEditor.RangeProperty(ambientIntensity, "环境光强度");

在这里插入图片描述

  这个滑条的最大最小值,其实就是Shader里面定义的Range的范围。
  不过要注意的是,假如Shader里面没有定义Range,却用了RangeProperty显示会有问题。
在这里插入图片描述

  会出现滑条没有最大最小值,只能直接输入数字。

3. MaterialEditor.VectorProperty

显示的是四元数的值
比如

ambientIntensity = FindProperty("_ambientIntensity", properties);
materialEditor.VectorProperty(ambientIntensity, "环境光强度");

在这里插入图片描述

4. 颜色类

  除了上面贴图用过的MaterialEditor.TexturePropertyWithHDRColor可以指定颜色
也可以用MaterialEditor.ColorProperty来显示颜色的参数
比如:

baseColor = FindProperty("_BaseColor", properties);
materialEditor.ColorProperty(baseColor, "漫反射颜色");

在这里插入图片描述

这个颜色的选择,是依赖于Shader里面的定义的,假如Shader里面定义了HDR颜色
比如:

[HDR]_BaseColor("BaseColor",Color) = (1,1,1,1)

那么在界面上的颜色选择也会变成HDR的:
在这里插入图片描述

5. 其他显示

  MaterialEditor还有一些功能性的显示,可以显示特定的选项,比如:

1. Editor.DrawHeader

比如:

materialEditor.DrawHeader();

会显示多一个材质球的标题
在这里插入图片描述

2. Editor.DrawPreview

比如:

materialEditor.DrawPreview(new Rect(0,0,200,200));

会显示一个预览的材质球:
在这里插入图片描述

3. Editor.DrawDefaultInspector

比如:

materialEditor.DrawDefaultInspector();

会显示一堆debug模式的参数:
在这里插入图片描述

4. MaterialEditor.EnableInstancingField

比如:

materialEditor.EnableInstancingField(new Rect(10,0,200,20));

会显示是否激活GPU Instancing:
在这里插入图片描述

三、 源码

1、 Shader

Shader "azhao/NormalShader"
{
	Properties
	{
		_MainTex("Texture", 2D) = "white" {}
		_BaseColor("BaseColor",Color) = (1,1,1,1)
		_NormalTex("Normal Tex", 2D) = "black"{}
		_normalScale("normalScale", Range(-1 , 1)) = 0
		_specColor("SpecColor",Color) = (1,1,1,1)
		_shininess("shininess", Range(1 , 100)) = 1
		_specIntensity("specIntensity",Range(0,1)) = 1
		_ambientIntensity("ambientIntensity",Range(0,1)) = 1
	}
		SubShader
		{
			Tags { "RenderType" = "Opaque" }
			LOD 100

			Pass
			{
				cull off
				CGPROGRAM
				#pragma vertex vert
				#pragma fragment frag
				#pragma multi_compile _ _USENORMALMAP_ON
				#include "UnityCG.cginc"
				struct appdata
				{
					float4 vertex : POSITION;
					float2 uv : TEXCOORD0;
					float3 normal:NORMAL;
					float3 tangent:TANGENT;
				};

				struct v2f
				{
					float4 pos : SV_POSITION;
					float2 uv : TEXCOORD0;
					float3 worldPos : TEXCOORD1;
					//为了构建TBN矩阵,所以要获取下面这三个值
					float3 worldNormal : TEXCOORD2;
					float3 worldTangent :TEXCOORD3;
					float3 worldBitangent : TEXCOORD4;
				};

				sampler2D _MainTex;
				float4 _MainTex_ST;
				float4 _BaseColor;
				sampler2D _NormalTex;
				float4 _NormalTex_ST;
				float _normalScale;
				float4 _specColor;
				float _shininess;
				float _specIntensity;
				float _ambientIntensity;

				//简化版的转换法线并缩放的方法
				half3 UnpackScaleNormal(half4 packednormal, half bumpScale)
				{
					half3 normal;
					//由于法线贴图代表的颜色是0到1,而法线向量的范围是-1到1
					//所以通过*2-1,把色值范围转换到-1到1
					normal = packednormal * 2 - 1;
					//对法线进行缩放
					normal.xy *= bumpScale;
					//向量标准化
					normal = normalize(normal);
					return normal;
				}



				//获取HalfLambert漫反射值
				float GetHalfLambertDiffuse(float3 worldPos, float3 worldNormal)
				{
					float3 lightDir = UnityWorldSpaceLightDir(worldPos);
					float NDotL = saturate(dot(worldNormal, lightDir));
					float halfVal = NDotL * 0.5 + 0.5;
					return halfVal;
				}

				//获取BlinnPhong高光
				float GetBlinnPhongSpec(float3 worldPos, float3 worldNormal)
				{
					float3 viewDir = normalize(UnityWorldSpaceViewDir(worldPos));
					float3 halfDir = normalize((viewDir + _WorldSpaceLightPos0.xyz));
					float specDir = max(dot(normalize(worldNormal), halfDir), 0);
					float specVal = pow(specDir, _shininess);
					return specVal;
				}

				v2f vert(appdata v)
				{
					v2f o;
					o.pos = UnityObjectToClipPos(v.vertex);
					o.uv = TRANSFORM_TEX(v.uv, _MainTex);
					o.worldPos = mul(unity_ObjectToWorld, v.vertex);
					o.worldNormal = UnityObjectToWorldNormal(v.normal);
					o.worldTangent = UnityObjectToWorldDir(v.tangent);
					o.worldBitangent = cross(o.worldNormal, o.worldTangent);
					return o;
				}

				half4 frag(v2f i) : SV_Target
				{
					//采样漫反射贴图的颜色
					half4 col = tex2D(_MainTex, i.uv);

					float3 worldNormal = i.worldNormal;
					#if _USENORMALMAP_ON
					//计算法线贴图的UV
					half2 normalUV = i.uv * _NormalTex_ST.xy + _NormalTex_ST.zw;
					//采样法线贴图的颜色
					half4 normalCol = tex2D(_NormalTex, normalUV);
					//得到切线空间的法线方向
					half3 normalVal = UnpackScaleNormal(normalCol, _normalScale).rgb;

					//构建TBN矩阵
					float3 tanToWorld0 = float3(i.worldTangent.x, i.worldBitangent.x, i.worldNormal.x);
					float3 tanToWorld1 = float3(i.worldTangent.y, i.worldBitangent.y, i.worldNormal.y);
					float3 tanToWorld2 = float3(i.worldTangent.z, i.worldBitangent.z, i.worldNormal.z);

					//通过切线空间的法线方向和TBN矩阵,得出法线贴图代表的物体世界空间的法线方向
					worldNormal = float3(dot(tanToWorld0, normalVal), dot(tanToWorld1, normalVal), dot(tanToWorld2, normalVal));
					#endif
					//用法线贴图的世界空间法线,算漫反射
					half diffuseVal = GetHalfLambertDiffuse(i.worldPos, worldNormal);

					//用法线贴图的世界空间法线,算高光角度
					half3 specCol = _specColor * GetBlinnPhongSpec(i.worldPos, worldNormal)*_specIntensity;

					//最终颜色 = 环境色+漫反射颜色+高光颜色
					half3 finalCol = UNITY_LIGHTMODEL_AMBIENT * _ambientIntensity + col.rgb*_BaseColor*diffuseVal + specCol;
					return half4(finalCol,1);
				}
				ENDCG
			}
		}
		CustomEditor "NormalShaderEditor"
}

2、 C#

  NormalShaderEditor.cs,因为using UnityEditor,所以必须放在Editor文件夹下

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
public class NormalShaderEditor : ShaderGUI
{
    private bool isSimpleMode = true;
    private MaterialProperty mainTex;
    private MaterialProperty baseColor;
    private MaterialProperty normalTex;
    private MaterialProperty normalScale;
    private MaterialProperty specColor;
    private MaterialProperty shininess;
    private MaterialProperty specIntensity;
    private MaterialProperty ambientIntensity;
    private Material mat;
    private bool useNormalMap = true;

    public override void OnGUI(MaterialEditor materialEditor, MaterialProperty[] properties)
    {
        //base.OnGUI(materialEditor, properties);
        mat = materialEditor.target as Material;
        mainTex = FindProperty("_MainTex", properties);
        baseColor = FindProperty("_BaseColor", properties);
        normalTex = FindProperty("_NormalTex", properties);
        normalScale = FindProperty("_normalScale", properties);
        specColor = FindProperty("_specColor", properties);
        shininess = FindProperty("_shininess", properties);
        specIntensity = FindProperty("_specIntensity", properties);
        ambientIntensity = FindProperty("_ambientIntensity", properties);
        useNormalMap = mat.IsKeywordEnabled("_USENORMALMAP_ON") ? true : false;
        GUILayout.BeginHorizontal();
        if(isSimpleMode)
        {
            GUILayout.Box("简单模式", GUILayout.Width(100));
            if(GUILayout.Button("高级模式",GUILayout.Width(100)))
            {
                isSimpleMode = false;
            }
        }
        else
        {
            if (GUILayout.Button("简单模式", GUILayout.Width(100)))
            {
                isSimpleMode = true;
            }
            GUILayout.Box("高级模式", GUILayout.Width(100));
        }
        GUILayout.EndHorizontal();
        if(isSimpleMode)
        {
            materialEditor.TexturePropertyTwoLines(new GUIContent("漫反射贴图"), mainTex, baseColor, new GUIContent("环境光强度"), ambientIntensity);
            useNormalMap = EditorGUILayout.BeginToggleGroup("启用法线贴图", useNormalMap);
            if(useNormalMap)
            {
                mat.EnableKeyword("_USENORMALMAP_ON");
                materialEditor.TexturePropertySingleLine( new GUIContent("法线贴图"), normalTex,normalScale);
            }
            else
            {
                mat.DisableKeyword("_USENORMALMAP_ON");
            }
            EditorGUILayout.EndToggleGroup();

            materialEditor.ColorProperty(specColor, "高光颜色");
            materialEditor.ShaderProperty(specIntensity, "高光强度");
            materialEditor.ShaderProperty(shininess, "光泽度");
        }
        else
        {
            GUILayout.Label("效果预览");

            materialEditor.DefaultPreviewGUI(new Rect(20,50,200,200),GUIStyle.none);
            GUILayout.Label("", GUILayout.Height(200));
            materialEditor.TextureProperty(mainTex, "漫反射贴图");
            materialEditor.ColorProperty(baseColor, "漫反射颜色");
            materialEditor.ShaderProperty(ambientIntensity, "环境光强度");
            useNormalMap = EditorGUILayout.BeginToggleGroup("启用法线贴图", useNormalMap);
            if (useNormalMap)
            {
                mat.EnableKeyword("_USENORMALMAP_ON");
                materialEditor.TextureProperty(normalTex,"法线贴图");
                materialEditor.ShaderProperty(normalScale, "法线强度");
            }
            else
            {
                mat.DisableKeyword("_USENORMALMAP_ON");
            }
            EditorGUILayout.EndToggleGroup();
            materialEditor.ColorProperty(specColor, "高光颜色");
            materialEditor.ShaderProperty(specIntensity, "高光强度");
            materialEditor.ShaderProperty(shininess, "光泽度");
        }
    }
}
Logo

分享前沿Unity技术干货和开发经验,精彩的Unity活动和社区相关信息

更多推荐