【转载】Unity ShaderLab 框架全解析1、2、3

282 阅读6分钟

原文链接

版权声明:本文为CSDN博主「_delta」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。

原文链接:

该系列主要是根据 Unity 官方文档和个人搜集的知识对 Unity ShaderLab 框架进行总结,比较基础,用于提高知识学习、复习的效率。对于文中给到的文章链接的作者表示感谢

1、Properties

Properties

官方链接:ShaderLab: Properties

属性是 ShaderLab 当中比较容易理解的,在 Shader 当中定义了变量之后,它可以 将 Shader 当中所用到的变量暴露出来,方便我们在材质面板进行调节和在代码中进行调用

下面展示一个 ShaderLab 当中比较全面的属性代码:

Properties{
        //数值和滑动条
        _Num1("Num1Name",Range(8,256)) = 20
        _Num2("Num2Name",Float) = 1
        _Num3("Num3Name",Int) = 1
        //颜色和向量
        _Color("ColorName",Color) = (1,1,1,1)
        _Vector("VectorName",Vector) = (1,1,1,1)
        //纹理
        _Texture1("Texture1Name",2D) = "defaulttexture"{}
        _Texture2("Texture2Name",Cube) = "defaulttexture"{}
        _Texture3("Texture3Name",3D) = "defaulttexture"{}
}

对应的材质 inspector

image.png

更多细节

其中 2D 纹理可以设定 Unity 内置的默认纹理,比如将上面的 defaulttexture 写为

  • white (RGBA: 1,1,1,1),
  • black (RGBA: 0,0,0,0),
  • gray (RGBA: 0.5,0.5,0.5,0.5),
  • bump (RGBA: 0.5,0.5,1,0.5)
  • 或者 red (RGBA: 1,0,0,0)。

Cube 和 3D 纹理在材质没有被外部赋值时,则默认采用 gray (RGBA: 0.5,0.5,0.5,0.5)。

属性特性

在每一个属性代码的前面加 [特性名] 可以设置属性在面板上面的显示和配置方式,Unity 可以辨识的属性特性有:

  • [HideInInspector] - 在材质面板上不显示该属性
  • [NoScaleOffset] - 材质面板不显示纹理的缩放和偏移控制输入框,也就是上图中的 TillingOffset
  • [Normal] - 表示该纹理需要一个法线纹理输入
  • [HDR] - 表示需要高动态范围的纹理或者颜色
  • [Gamma] - 表示该向量或者浮点数值在 UI 中以 sRGB 来指定的,需要采用 Gamma 矫正。
  • [PerRendererData] - 表示纹理属性将以 MaterialPropertyBlock 的形式来自每个渲染器数据。

MaterialPropertyBlock 中设置的 Per-Renderer 值。表示这通常是 “每个实例” 的数据(例如,为许多共享相同材质的对象个性化定义色调颜色)。具体可参看相关官方文档链接 Accessing shader properties in Cg/HLSL

上面特性的具体效果可以自行测试。

2、SubShader、Fallback

SubShader

ShaderLab 是由一个或者多个 SubShader 组成的,当 Unity 渲染网格模型时,会在 Shader 文件即 ShaderLab 框架当中寻找 能在当前显卡上运行的最高级的效果最好的 SubShader 来使用,SubShader 由一系列的 Pass 组成。Pass 当中 包含着真正的 Shader 的具体代码。ShaderLab 或者说 Shader 文件的框架大概如下:

Shader "Shader文件路径名"{
	Properties{
		……	
	}
	SubShader{
		Pass{
			……
		}
		Pass{
			……
		}
		……
	}
	SubShader{
		Pass{
			……
		}
		Pass{
			……
		}
		……
	}
	……

}

SubShader Tag

SubShader 使用 Tag控制自身何时怎样被引擎使用来渲染

Tag 使用的基本方法如下:

SubShader{
        Tags { "TagName1" = "Value1" "TagName2" = "Value2" }
        Pass{
                ……
        }
        Pass{
                ……
        }
        ……
}

在大括号内可以指定任意数量的键值对,Unity 有一些 预设的键值对

Rendering Order

渲染顺序键,分别对应着几种预设的值,每个顺序值在内部实现上都是整数

  1. Background - 对象最先渲染,值为 1000
  2. Geometry (default) - 是渲染顺序的缺省值,渲染绝大多数不透明的网格,值为 2000
  3. AlphaTest - 通过 alpha 测试的 geometry 使用此队列。这是与“Geometry ” 相分离的队列,因为在绘制所有实体(Solid)后,可以更有效地渲染通过 Alpha 测试的对象。值为 2450
  4. Transparent - 在 “Geometry” 和 “AlphaTest” 之后渲染,当中的网格按照从后往前的顺序渲染,所有使用 alpha-blended 的( shader 当中不进行深度写入的 )在这个渲染队列当中(比如玻璃,粒子),值为 3000
  5. Overlay - 最后渲染的对象,比如光晕、UI,值为 4000

因为队列值为整数,所以可以自定义非预设的值,比如 "Queue" = "Geometry+1" ,表示在所有的 Geometry 对象之后渲染。

RenderType

用来给 Shader 进行分类,比如 Unity 内置的一些常用 RenderType,如Opaque, Transparent, Background, Overlay,另外还可以自定义 RenderType,可以用来进行材质替代渲染,具体实现参考之前一篇文章:Unity材质替代渲染相关: RenderWithShader & SetReplacementShader

ForceNoShadowCasting

如果对应值为真,则使用该 Subshader 则不会进行阴影投射,不执行 Shadow Pass

IgnoreProjector

如果对应值为真,则忽略 Projector 的影响,Projector 具体参照官方文档:Projector,比较容易理解。

CanUseSpriteAtlas

如果值为假,则 shader 使用 sprite,那么当 sprite 装入图集之后 shader 将不起作用。

PreviewType

控制材质球在预览面板的显示方式,默认展示球形,可以设置为 PlaneSkybox

Fallback

在所有的 Subshader 定义之后,可以定义 Fallback,表示如果没有 Subshader 可以运行在当前的硬件环境上,那么就使用一个已定义的shader (这不就是传说中的 备胎 嘛, T_T):

Fallback "name"

使用下面的语句表示即使所有的 Subshader 都不能运行,也不使用其他的 shader 并且不允许报错。

Fallback Off

3、Custom Shader GUI

有时 shader 的一些属性数据 无法在编辑器面板上很好的展示出来,所有 Unity 允许用户自定义 shader 的显示面板,比如增加其他的控制方式。

在 ShaderLab 的 Subshader 外写下如下语句:

CustomEditor "name"

Unity 会自动寻找名为 name 的类,该类需要继承自 ShaderGUI 类,每一个定义上述语句的 shader 都会实例化一个 name 类的实例并执行里面的代码。

下面根据个人理解解释一下官方案例:

自定义的表面着色器:

Shader "Custom/Redify" {
    Properties {
        _MainTex ("Base (RGB)", 2D) = "white" {}
    }
    __SubShader__ {
        Tags { "RenderType"="Opaque" }
        __LOD__ 200
        
        CGPROGRAM
        #pragma surface surf Lambert addshadow
        #pragma shader_feature REDIFY_ON //通过 shader_feature  编译指令定义关键字

        sampler2D _MainTex;

        struct Input {
            float2 uv_MainTex;
        };

        void surf (Input IN, inout SurfaceOutput o) {
            half4 c = tex2D (_MainTex, IN.uv_MainTex);
            o.Albedo = c.rgb;
            o.Alpha = c.a;

            #if REDIFY_ON				// 如果定义关键字,那么就改变输出的 Albedo 颜色
            o.Albedo.gb *= 0.5;
            #endif
        }
        ENDCG
    } 
    CustomEditor "CustomShaderGUI"
}

自定义的 CustomShaderGUI 类

继承自 ShaderGUI

using UnityEngine;
using UnityEditor;
using System;

public class CustomShaderGUI : ShaderGUI
{
    public override void OnGUI(MaterialEditor materialEditor, MaterialProperty[] properties)
    {
        // render the default gui
        base.OnGUI(materialEditor, properties);

        Material targetMat = materialEditor.target as Material;

        // see if redify is set, and show a checkbox
        bool redify = Array.IndexOf(targetMat.shaderKeywords, "REDIFY_ON") != -1;
        EditorGUI.BeginChangeCheck();
        redify = EditorGUILayout.Toggle("Redify material", redify);
        if (EditorGUI.EndChangeCheck())
        {
            // enable or disable the keyword based on checkbox
            if (redify)
                targetMat.EnableKeyword("REDIFY_ON");
            else
                targetMat.DisableKeyword("REDIFY_ON");
        }
    }
}

和之前说的一样,在 shader 文件中写下如下语句:

CustomEditor "CustomShaderGUI"

Unity 查找到对应的类并执行代码,另外 关键的是,shader 中使用编译指令定义了关键字 —— REDIFY_ON (这部分和编译指令以及 shader 变体有关,具体参考这篇文章:Shader入门(二十一)多重变体(Multiple Variants))。

总之,通过控制该关键字的定义,可以 产生不同效果 的 shader。在 CustomShaderGUI 类的 OnGUI 函数中进行检测,如果 GUI 发生变动(即 checkbox 被手动更改),那么根据 redify 的值来控制是否定义 shader 当中的 REDIFY_ON 关键字,从而达到控制输出的目的。

image.png

目前还没有应用到这方面知识,之后会进行更复杂的实现。