Unity引擎自定义ShaderGUI
这次来介绍一下Unity引擎的自定义ShaderGUI。
大家好,我是阿赵。
这次来介绍一下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, "光泽度");
}
}
}
更多推荐
所有评论(0)