unity6 Shaderlab语法笔记

前言


shaderlab是unity提供的一套着色器方案。使用HLSL语法,并包含一些CG的关键字和一些文件。

注:像素着色器 == 片元着色器

注:本文基本是CG风格写法 

CG和HLSL的区别,CG能自动导入shader库。HLSL则需要手动导入。


语法

ulishader框架如下:

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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
Shader "Pao/BasicGrammar" // 这时shader的名称,可用与表示shader,游戏程序可以通过名称找到这个shader。
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
}
SubShader // 一个shader可以有多个subshader,但只有一个可以被使用。
{
Tags {
"RenderPipeline" = "UniversalPipeline" # 通过指定渲染管线,让此subshader在URP管线下生效。
"RenderType"="Opaque"
}
LOD 500
/*
level of detail的缩写,表示这个subshader的复杂度。系统会从上到下查找符合的subshader使用,大的应该在上。
代码例如:Shader.Find("Pao/BasicGrammar").maximumLOD = 450;
这里的LOD大于450,就会找下一个subshader。
*/
Pass // 用于管理一组顶点/像素着色器的。
{
/*
BRP中支持多个常规Pass,每个Pass作为一个物体单独渲染,几个pass就是几个drawCall。
可以使用Pass中的name属性分辨,也可以使用代码指定激活某个Pass。
URP不支持多个常规Pass。
都支持多个带有LightMode的Pass。
*/
Name "Pass1"

CGPROGRAM // 虽然这里写的是CG,但使用的还是HLSL的语法,只是会引用一些Shader库。
#pragma vertex vert // 编译指令用于指定顶点着色器vert
#pragma fragment frag // 编译指令用于指定像素着色器frag
// make fog work
#pragma multi_compile_fog

#include "UnityCG.cginc"

struct appdata // 这是顶点着色器的输入数据
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};

struct v2f // 这时像素着色器的输入数据,也是顶点着色器的输出数据
{
float2 uv : TEXCOORD0;
UNITY_FOG_COORDS(1) // 宏:将雾效坐标存入TEXCOORD1(自动声明float fogCoord : TEXCOORD1)
float4 vertex : SV_POSITION;
};

sampler2D _MainTex; // simpler state + texture2D 采样状态+一张纹理索引
float4 _MainTex_ST;

v2f vert (appdata v) // 顶点着色器
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex); // 顶点着色器的主要作用,将顶点数据转换到齐次裁剪空间。
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
UNITY_TRANSFER_FOG(o,o.vertex);
return o;
}

fixed4 frag (v2f i) : SV_Target// 像素着色器
{
// sample the texture
fixed4 col = tex2D(_MainTex, i.uv); // 像素着色器的主要作用,通过UV对材质进行纹理采样。
// 这里可以对颜色做一定修改,实现想要的效果。
UNITY_APPLY_FOG(i.fogCoord, col);// 这时雾效应用宏,根据雾效坐标将颜色覆盖上雾。
return col;
}
ENDCG
}
}

FallBack "Unity/aaa"
/* 如果使用LOD寻找subShader没有找到符合条件的subshader,那么就会使用FallBack到另一个shader中去寻找。
也可以使用 FallBack off 表示不使用跳转。
*/
}

include

可以在代码块(CGPROGRAM)里使用include,导入任意文本文件中的代码,后缀名任意都可以。

但是最好使用Unity规定的.cginc或.hlsl。因为只用这两种才会被unity实时追踪,其内容更改才能实时生效。

语义

定义结构体时,结构体内的变量需要标注语义。以明确其用处。

对于顶点着色器的输出,和像素着色器的输入数据,其语义有以下要求:

1
2
3
4
5
6
7
struct v2f // 这时像素着色器的输入数据,也是顶点着色器的输出数据
{
float2 uv : TEXCOORD0; // 除了顶点和颜色,其他都建议使用TEXCOORDn, 这里的n从0开始。不能重复
UNITY_FOG_COORDS(1)
float4 vertex : SV_POSITION; // 齐次裁剪空间顶点坐标使用SV_POSITION
float4 color : COLOR; // 颜色使用COLOR
};

TEXCOORDn是通用浮点数据语义,可以表达普通插值变量。

语法糖:

1
2
float4(v.uv, 1, 1); //等价于 
float4(v.uv.x, v.uv.y, 1, 1); // float2可以直接用于构造float4

片元着色器的语义:

1
2
3
4
5
6
7
8
fixed4 frag (v2f i) : SV_Target // SV_Target也是一个语义
{
// sample the texture
fixed4 col = tex2D(_MainTex, i.uv); // 像素着色器的主要作用,通过UV对材质进行纹理采样。
// 这里可以对颜色做一定修改,实现想要的效果。
UNITY_APPLY_FOG(i.fogCoord, col);// 这时雾效应用宏,根据雾效坐标将颜色覆盖上雾。
return col;
}

SV_Target表示像素着色器的渲染目标,SV_Target就是第0个,也可以有SV_Target1等。

这个语义对应的是是着色器的返回值fixed4,所以也可以写成:

1
2
3
void frag (v2f i, out fixed4 col : SV_Target){
col = tex2D(_MainTex, i.uv);// 使用了out就不用return了
}

材质参数

1
sampler2D _MainTex; // simpler state + texture2D 采样状态+一张纹理索引

采样状态由贴图面板中的 Wrap Mode和Filter Mode决定的。

Wrap Mode:当UV坐标超过0~1的范围时,如何进行采样。

  • Repeat:重复,循环采样
  • Clamp:边缘拉伸,UV>1的情况取UV=1。
  • Mirror:镜像重复,0-1正常,1-2翻转,2-3再翻转
  • Mirror Once:只镜像一次,0-1正常,1-2翻转,2以后停在边缘(Clamp)
  • Per-axis:分别控制U、V,两个方向使用不同模式。

Filter Mode:是UV对贴图采样的过滤方式,有:

  • Point (no filter) (不过滤)

  • Bilinear(双线性过滤):在一张mipmap中的像素间插值。

  • Trilinear(三线性过滤):在两张mipmap中分别Bilinear,然后两个结果进行层级间插值。

也有特殊的:

  • Anisotropic Filtering (各向异性过滤,AF)

uniform变量

变量可以在CG代码块定义,Properties中定义同名同类型的变量,可以在面板中显示它。本质是同一个。

如:

1
2
3
4
float4 _Color; // 此处定义uniform变量默认值是(0,0,0,0)
fixed4 frag (v2f i) : SV_Target {
return _Color; // 此处直接返回
}

这样写就是让物体变成纯色_Color的颜色。

可以通过脚本代码修改这个颜色:

1
2
3
4
5
6
7
8
9
10
11
public class UniformSetter : MonoBehavior
{
[SerializeField]
private Color m_Color;

private void OnValidate()
{
var sr = this.GetComponent<SpriteRenderer>(); // 获取物体渲染组件
sr.sharedMaterial.SetColor("_Color",this.m_Color); // 获取渲染组件shader材质,设置材质的_Color属性。
}
}

Properties中定义:

1
2
3
4
5
6
7
Properties
{
_MainTex ("Texture", 2D) = "white" {}
_Color("Color",Color) = (1,1,1,1)
// 格式是: 变量名(“显示名称”,类型)=默认值
// 变量名以下划线“_"开头
}

这里的_Color一般指主颜色,就是渲染组件(Sprite Renderer)中的Color。在这里写了,unity才会把主颜色赋值给他

这样就能在材质面板中修改shader中的uniform变量。

常用的有:

1
2
3
4
5
6
7
8
9
10
11
12
//数值类型
_MyFloat("My float", Float) = 0.5
_MyRange("My Range", Range(0,1)) = 0.5 // Renge被本质也是也是Float,只是有上下限。
_MyColor("Some Color", Color) = (1,1,1,1)
_MyVector("Some Vector", Vector) = (0,0,0,0)

//纹理类型
_MyTexture("My Texture", 2D) = "white" {} // 这里的"white",是使用unity内置的一张白色材质。
_MyCubemap("My Cubemap", Cube) = "" {} // 这里的“”,是材质置空。

//触发器
[Toggle(keyword)] 变量名("aaa", float) = 0

对于数值类型可以不写最后的”{}”,但是对于纹理类型,必须写最后的”{}”。

这个”{}”中用于填写扩展配置,即便没有配置也需要写一个空的”{}”。

数值类型:

  • fixed:低精度
  • half:中精度
  • float:高精度

在高性能平台如PC,unity会默认把fixed和half当做float处理。

想要在高性能平台使用低、中精度,可以在项目设置里,修改shader Settings 下的Shader Precision Model。

在现在,硬件性能更高了。fixed在多数移动平台中也会被作为half处理。

注:Properties中的多分量变量只有:Color和Vector。本质都是四维向量。

使用二维向量或三维向量,要在代码块中定义float2,然后在Properties中使用Color或Vector。这样只对应一部分。

不常用类型:

  • Integer:整数
  • 2DArray:2D贴图的列表
  • CubeArray:Cube贴图的列表

surf shader

除了上述常规shader,unity还提供一种简化shader——surf shader:

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
36
37
38
39
40
41
42
43
44
45
46
47
Shader "Custom/NewSurfaceShader"
{
Properties
{
_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
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 200

CGPROGRAM

#pragma surface surf Standard fullforwardshadows

#pragma target 3.0

sampler2D _MainTex;

struct Input
{
float2 uv_MainTex;
};

half _Glossiness;
half _Metallic;
fixed4 _Color;

UNITY_INSTANCING_BUFFER_START(Props)
UNITY_INSTANCING_BUFFER_END(Props)

void surf (Input IN, inout SurfaceOutputStandard o)
{
fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
o.Albedo = c.rgb;

o.Metallic = _Metallic;
o.Smoothness = _Glossiness;
o.Alpha = c.a;
}
ENDCG
}
FallBack "Diffuse"
}

这种shader只有一个surface,这是unity提供的语法糖。本质上还是顶点像素着色器。可以用于快速的写一些简单光照效果。

渲染状态

渲染状态有以下:

1
2
3
4
5
6
7
8
9
10
11
Conservative True // True/False 开启"保守光栅化"。(轮廓描边,阴影体积等)
Cull off // off/on 关闭、开启面剔除
Offset 1, 1 // 设置深度偏移,改变当前片元的深度,避免深度冲突(Z-fighting),都是偏移系数,正数变深,负数变近
ZClip True // True/False 是否开启齐次裁剪空间深度裁剪,GPU阶段剔除超出远、近裁剪面的物体。
ZTest Less // 深度测试 Less(默认)/Greater(反向深度)/Always(永远通过,UI、特效)/Equal(相等通过,阴影)
ZWrite On // Off(透明)/On(不透明) 是否开启深度写入,决定是否遮挡深处的物体。影响后续深度测试。
Stencil {} // 模版测试配置块
Blend SrcAlpha OneMinusSrcAlpha // 设置颜色混合模式为“源Alpha混合” 还有:One One(加法,火焰)/DstColor Zero(乘法,阴影)
BlendOp Add // 设置混合操作类型为加法 Add(加法)/Sub(减法)/RevSub(减法取负数)/Min(取最小)/Max(取最大)
ColorMask RGB // 设置颜色通道 RGB/RGBA/0/R/G/B/A
AlphaToMask on // on/off 开启"Alpha遮罩"。(需要抗锯齿的镂空物体)

渲染状态可以写在:

  • SubShader,对次SubShader中的所以Pass生效
  • Pass中,对当前Pass生效,生效级别比SubShader更高

Tags

SubShader:

1
2
3
4
5
6
7
8
9
Tags {
"RenderPipeline" = "UniversalPipeline" //设置渲染管线
"Queue"="Transparent" //设置Queue值
"RenderType" = "Transparent" //设置渲染类型
"ForceNoShadowCasting" = "True" //是否强制关闭该Shader对应物体的“投射阴影”
"DisableBatching"="True" //是否强制关闭物体的“动态合批”
"IgnoreProjector"="True" //是否让物体忽略“投影器的影响,灯光投影等”
"PreviewType"="Sphere" //指定材质面板中,“预览模型的类型”为“球体”
}

RenderType有以下取值:

  • Opaque (不透明)
  • Transparent (半透明)
  • TransparentCutout (镂空,Alpha测试)
  • Background (背景)
  • Overlay (UI)

Pass:

1
2
3
4
5
Tags {
"LightMode"="Always" // 设置光照模式
"PassFlags"="OnlyDirectional" // 限定Pass处理的灯光范围
"RequireOptions"="SoftVegetation" // 只有引擎开启选项时生效
}

注:SubShader和Pass的Tags不一样,改变位置不能生效。

LightMode有以下取值:

  • Always:无条件执行,忽略光照管线规则,无光照的特效、UI、纯色物体

  • ForwardBase:正向渲染基础通道(处理主方向光 + 环境光),普通不透明物体的主光照

  • ForwardAdd 正向渲染附加通道(处理点光 / 聚光灯),多光源场景的附加光照

  • ShadowCaster 阴影投射通道(生成阴影贴图),物体投射阴影的专用 Pass

PassFlags有以下取值:

  • OnlyDirectional:仅处理方向光(太阳 / 主光源)
  • None:处理所有类型灯光(默认)
  • Specular:仅处理灯光的高光部分(内置管线专用)

RequireOptions有以下取值:

  • SoftVegetation 引擎开启SoftVegetation
  • DisableBatching 仅合批关闭时执行 Pass
  • Instancing 仅实例化渲染时执行 Pass

变体

SubShader变体语法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Properties
{
[Toggle(ReturnColor)] _ReturnColorOnly("ReturnColor", float) = 0
// 格式[Toggle(keyword)] _变量名(“显示名”, float) = 0
// 此处变量名随意
}


#pragma multi_compile_local _ ReturnColor
// 格式: #pragma multi_compile_local “keyword”
// 下划线“_”表示空

float4 _Color;

fixed4 frag (v2f i) : SV_Target
{
#ifdef ReturnColor // 此处用法类似c++条件宏定义
return _Color;
#endif
}

本质上,unity会将变体编译为多个着色器。

1
2
3
4
5
6
7
8
9
10
11
12
#pragma multi_compile_local _ ReturnColorOnly
#pragma multi_compile_local _ CC DD
// 这样的定义会产生以下变体

Keywords: <no keywords defined>
Keywords: CC
Keywords: DD
Keywords: ReturnColorOnly
Keywords: CC ReturnColorOnly
Keywords: DD ReturnColorOnly

// 也就是说同一条编译命令的keyword不会相互组合。因为一个编译命令对于一个变量,一个变量不可能同时又两个值。

变体的编译指令的用法:

编译指令 编译 用途 控制范围 C#控制
multi_compile 编译所有变体 需要运行时切换变体 所有材质 Shader.SetKeyword(GlobalKeyword.Create(“aa”), true);
multi_compile_local 编译所有变体 需要运行时切换变体 单个材质 mat.SetKeyword(“aa”, true);
shader_feature 只编译材质用到的变体 不需要动态切换变体 所有材质 Shader.SetKeyword(GlobalKeyword.Create(“aa”), true);
shader_feature_local 只编译材质用到的变体 不需要动态切换变体 单个材质 mat.SetKeyword(“aa”, true);

类似C++的宏定义,是纯粹的文本替换:

1
2
3
4
5
6
7
8
9
// 普通宏
#define YELLOW float4(1,1,0,1)
// 使用
return YELLOW;

// 带有参数的宏
#define MYCOLOR(a) float4(1,1,a,1)
//使用
return MYCOLOR(1);

swizzle语法

一种语法糖:

1
SV_Target0.xyw = vec3(1.0,1.0,1.0) //本质对x、y、w分别按顺序赋值。

材质UV缩放和偏移

在材质面板的Texture下有Tiling和Offset用于控制UV缩放和偏移。

可以这样声明变量,以关联着两个数值:

1
2
3
sampler2D _MainTex;
float4 _MainTex_ST;
//在对应贴图变量名后加“_ST”,unity可以自动把Tiling对应x、y,把Offset对应z、w。

注意:这种写法本质是一个宏。


HLSL风格

(HLSL风格片元着色器,不建议在CG中使用):

1
2
3
4
5
6
7
8
9
TEXTURE2D(_MainTex);      // 声明2D纹理
SAMPLER(sampler_MainTex); // 声明对应采样器

fixed4 frag (v2f i) : SV_Target {
// 用宏采样:纹理 + 采样器 + UV
fixed4 col = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, i.uv);
// 和tex2D(_MainTex, i.uv); 是等价的
return col;
}

(HLSL风格顶点着色器,不能在CG中使用):

1
2
3
4
5
6
7
8
9
10
11
12
struct Attributes
{
float4 positionOS : POSITION
float2 uv : TEXCOORD0
};

Varyings vert(Attributes IN)
{
Varyings OUT;
OUT.uv = TRANSFORM_TEX(IN.uv, _MainTex);
OUT.positionHCS = TransformObjectToHClip(IN.positionOS); // 类似CG的UnityObjectToClipPos()
}

SRP Batcher

在HLSL中会使用CBUFFER包含所有的uniform变量。

在CG中,使用同一个材质的不同实例的物体会打断合批,原因是不同实例使用了不同的变量。使用SRP Batcher可以将变量缓冲在CBUFFER中,将使用同一个shader变体的但不同实例的物体依旧能够进行合批。

1
2
3
4
5
CBUFFER_START(UnityPerMaterial)
// 这中间定义uniform变量
float4 _Color;
float4 _MainTex_ST;
CBUFFER_END

其实在CG中也可以,只要CG代码兼容HLSL并且在URP管线中。因为untiy的CG和HLSL本质是一样的。