第三章 Unity Shader概述

发布时间 2023-06-22 23:18:59作者: 安川

第三章 Unity Shader概述

Shader和材质

Shader的创建

  • 标准表面着色器(Standard Surface Shader): 会产生一个包含了标准光照模型的表面着色器模板
  • 无光照着色器(Unlit Shader): 会产生一个不包含光照(但包含雾效)的基本的顶点/片元着色器
  • 图像效果着色器(Image Effect Shader): 为我们实现各种屏幕后处理效果提供了一个基本模板
  • 计算着色器(Compute Shader): 会产生一种特殊的Shader文件,这类Shader旨在利用GPU的并行性来进行一些与常规渲染流水线无关的计算。
    Shader不能脱离材质,单独的shader无法发挥作用。在材质的最上方,我们可以选择材质的Shader。

Shader的属性

ShaderLab

Unity Shader是Unity为开发者提供的高层级的渲染抽象层,所有的Unity Shader都是使用ShaderLab来编写的。

一个Unity Shader的基本结构如下

Shader "ShaderName" {
    Properties {
        // 属性
    }
    SubShader {
        // 显卡A使用的子着色器
    }
    SubShader {
        // 显卡B使用的子着色器
    }    
    Fallback "VertexLit"
}

Properties 语句块中定义了着色器所需的各种属性,这些属性将会出现在材质面板中

Unity Shader的结构

1. 名字

我们定义Shader叫做NewSurfaceShader

Shader "Custom/NewSurfaceShader"

则可以在Shader中找到

2. 属性

Properties
    {
    	//格式:Name ("display name", PropertyType) = DefaultValue
        _Color ("Color", Color) = (1,1,1,1)
        _MainTex ("Albedo (RGB)", 2D) = "white" {}
        _Glossiness ("Smoothness", Range(0,1)) = 0.5
        _Metallic ("Metallic", Range(0,1)) = 0.0
    }

在Shader中如上编写,则检查器中会有相应改变。

Property支持的数据类型

属性类型 定义语法 例子
Int number _Int ("Int", Int) = 2
Float number _Float ("Float", Float) = 1.5
Range(min, max) number _Range("Range", Range(0.0, 5.0)) = 3.0
Color (number,number,number,number) _Color ("Color", Color) = (1,1,1,1)
Vector (number,number,number,number) _Vector ("Vector", Vector) = (2, 3, 6, 1)
2D "defaulttexture" {} _2D ("2D", 2D) = "" {}
Cube "defaulttexture" {} _Cube ("Cube", Cube) = "white" {}
3D "defaulttexture" {} _3D ("3D", 3D) = "black" {}

3. SubShader

当Unity需要加载Unity Shader时,Unity会扫描所有的SubShader 语义块,然后选择第一个能够在目标平台上运行的SubShader 。如果都不支持的话,Unity就会使用Fallback 语义指定的Unity Shader。
SubShader主要用来适配不同的显卡,我们在性能较强的显卡上用复杂度较高的SubShader,在性能较弱的显卡上用复杂度较低的SubShader。
SubShader的定义

SubShader {
    // 可选的
    [Tags]

    // 可选的
        [RenderSetup]

	//每个Pass定义了一个完整的渲染过程
    Pass {
    }
    // Other Passes
}
  • 状态设置
    常见的渲染选项
状态名称 设置指令 解释
Cull Cull Back / Front/ Off 设置剔除模式:剔除背面/正面/关闭剔除
ZTest ZTest Less Greater/ LEqual/ GEqual/ Equal/ NotEqual/ Always 设置深度测试时使用的函数
ZWrite ZWrite On/Off 开启/关闭深度写入
Blend Blend SrcFactor DstFactor 开启并设置混合模式
  • 标签
    标签结构如下
Tags { "TagName1" = "Value1" "TagName2" = "Value2" }

支持的标签

标签类型 说明 例子
Queue 控制渲染顺序,指定该物体属于哪一个渲染队列,通过这种方式可以保证所有的透明物体可以在所有不透明物体后面被渲染(详见第8章),我们也可以自定义使用的渲染队列来控制物体的渲染顺序 Tags { "Queue" = "Transparent" }
RenderType 对着色器进行分类,例如这是一个不透明的着色器,或是一个透明的着色器等。这可以被用于着色器替换(Shader Replacement)功能 Tags { "RenderType" = "Opaque" }
DisableBatching 一些SubShader 在使用Unity的批处理功能时会出现问题,例如使用了模型空间下的坐标进行顶点动画(详见11.3节)。这时可以通过该标签来直接指明是否对该SubShader 使用批处理 Tags { "DisableBatching" = "True" }
ForceNoShadowCasting 控制使用该SubShader 的物体是否会投射阴影(详见8.4节) Tags { "ForceNoShadowCasting" = "True" }
IgnoreProjector 如果该标签值为“True”,那么使用该SubShader 的物体将不会受Projector的影响。通常用于半透明物体 Tags { "IgnoreProjector" = "True" }
CanUseSpriteAtlas 当该SubShader 是用于精灵(sprites)时,将该标签设为“False” Tags { "CanUseSpriteAtlas" = "False" }
PreviewType 指明材质面板将如何预览该材质。默认情况下,材质将显示为一个球形,我们可以通过把该标签的值设为“Plane”“SkyBox”来改变预览类型 Tags { "PreviewType" = "Plane" }
  • pass语义块
    pass语义块包含的代码如下
Pass { 
    [Name] //定义pass的名称
    [Tags] 
    [RenderSetup] 
    // Other code
}

我们可以用UsePass方法调用其他Shader中的pass

UsePass "OtherShader/MyPass"

其次,我们可以对Pass 设置渲染状态。SubShader 的状态设置同样适用于Pass

Pass 同样可以设置标签,但它的标签不同于SubShader 的标签。这些标签也是用于告诉渲染引擎我们希望怎样来渲染该物体。

标 签 类 型 说 明 例 子
LightMode 定义该Pass在Unity的渲染流水线中的角色 Tags { "LightMode" = "ForwardBase" }
RequireOptions 用于指定当满足某些条件时才渲染该Pass,它的值是一个由空格分隔的字符串。目前,Unity支持的选项有:SoftVegetation。在后面的版本中,可能会增加更多的选项 Tags { "RequireOptions" = "SoftVegetation" }
  • fallback
    如果所有的SubShader都不能起作用,那么就会调用fallback,代码如下
Fallback "name"
// 或者
Fallback Off

Shader的格式

表面着色器

表面着色器本质是和顶点/片元着色器是一样的,但是代码较为简单。也就是说,Unity会将一个表面着色器转换为一个顶点/片元着色器。
在表面着色器中,Unity会为我们处理很多光照细节,这些不用我们费心。
代码示例如下:

Shader "Custom/Simple Surface Shader" {
    SubShader {
        Tags { "RenderType" = "Opaque" }
        CGPROGRAM
        #pragma surface surf Lambert
        struct Input {
            float4 color : COLOR;
        };
        void surf (Input IN, inout SurfaceOutput o) {
            o.Albedo = 1;
        }
        ENDCG
    }
    Fallback "Diffuse"
}

从上述程序中可以看出,表面着色器被定义在SubShader 语义块(而非Pass 语义块)中的CGPROGRAM 和ENDCG 之间。原因是,表面着色器不需要开发者关心使用多少个Pass、每个Pass如何渲染等问题,Unity会在背后为我们做好这些事情.

顶点/片元着色器

顶点/片元着色器更复杂,也更加灵活。
代码示例如下:

Shader "Custom/Simple VertexFragment Shader" {
    SubShader {
        Pass {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            float4 vert(float4 v : POSITION) : SV_POSITION {
                return mul (UNITY_MATRIX_MVP, v);
            }

            fixed4 frag() : SV_Target {
                return fixed4(1.0,0.0,0.0,1.0);
            }

            ENDCG
        }
    }
}

和表面着色器类似,顶点/片元着色器的代码也需要定义在CGPROGRAM 和ENDCG 之间,但不同的是,顶点/片元着色器是写在Pass 语义块内,而非SubShader 内的。原因是,我们需要自己定义每个Pass需要使用的Shader代码。虽然我们可能需要编写更多的代码,但带来的好处是灵活性很高。更重要的是,我们可以控制渲染的实现细节。同样,这里的CGPROGRAM 和ENDCG 之间的代码也是使用Cg/HLSL编写的。

固定函数着色器

对于一些比较旧的设备,他们不支持可编程管线着色器,这个时候就要用固定函数着色器。
示例代码如下:

Shader "Tutorial/Basic" {
    Properties {
        _Color ("Main Color", Color) = (1,0.5,0.5,1)
    }
    SubShader {
            Pass {
            Material {
                Diffuse [_Color]
            }
            Lighting On
        }
    }
}

着色器的选择

  • 除非你有非常明确的需求必须要使用固定函数着色器,例如需要在非常旧的设备上运行你的游戏(这些设备非常少见),否则请使用可编程管线的着色器,即表面着色器或顶点/片元着色器。
  • 如果你想和各种光源打交道,你可能更喜欢使用表面着色器,但需要小心它在移动平台的性能表现。
  • 如果你需要使用的光照数目非常少,例如只有一个平行光,那么使用顶点/片元着色器是一个更好的选择。
  • 最重要的是,如果你有很多自定义的渲染效果,那么请选择顶点/片元着色器。