Appearance
Unity Shader
渲染管线
名称 | 解释 |
---|---|
Unity的内置渲染 | 适用于所有平台,非常可靠,容易使用。但是不可定制,也非最有效 |
通用渲染管线(URP) | 非常适合移动、Web和VR项目,因为它针对性能进行了高度优化。配置有点复杂,但比内置渲染管道更具可定制性,可以产生不错的图形 |
高清渲染管线(HDRP) | 旨在为具有强大处理能力的高端平台(如游戏机或游戏计算机)生成高质量图形。配置非常复杂,适合具有大量图形经验的人使用 |
URP和HDRP都是Scriptable Render Pipelines(SRP)。SRP是高度可定制的。
渲染流水线
应用阶段(由CPU负责)
1、场景数据加载到显存中
渲染所需数据从硬盘加载到内存中,网格纹理等数据被加载到显存中(一般加载到显存后内存中的数据就会被移除)
2、设置渲染状态
处理用于定义场景中的网格如何渲染,例如,使用哪个顶点着色器,哪个纹理等。
该阶段还会剔除不可见内容。
3、调用Draw Call
是一个命令,由CPU发起,GPU接收。该命令仅仅会指向一个需要被渲染的图元列表,而不会包含任何材质信息
应用阶段会输出渲染图元到下个阶段
几何阶段(GPU负责)
把顶点坐标变换到屏幕空间中,再交给光栅器处理
该阶段会输出屏幕空间的顶点信息到下阶段
光栅阶段(GPU负责)
使用上阶段传递的数据,产生屏幕上的像素并最终渲染出图像
GPU流水线
几何阶段和光栅化阶段中,开发者只能对某些步骤进行操作。例如:
几何阶段中:顶点着色器、曲面细分着色器、几何着色器、裁剪(仅可配置)
光栅化阶段中:片元着色器、逐片元操作(仅可配置)
整个流程为:
顶点数据->顶点着色器->曲面细分着色器->几何着色器->裁剪->屏幕映射->(后面是光栅化阶段)三角形设置->三角形遍历->片元着色器->逐片元操作->屏幕图像
顶点着色器、裁切、屏幕映射
顶点着色器,顾名思义,此处是对顶点的操作。输入的每个顶点都会调用一次顶点着色器,主要完成顶点坐标变换以及逐顶点光照,输出后续所需数据。
裁切,超出摄像机部分的裁剪掉不作渲染处理。
屏幕映射,将三维空间的数据映射到二维平面。OpenGL的原点在左下角,坐标系朝右上角延伸。DX原点在左上角,朝右下角延伸。
三角形设置、三角形遍历
顶点数据处理完以后进入到光栅化阶段。此处的内容是以三角形为基本单位,大部分处理都与三角形有关。
首先就是根据坐标点,确定一个三角形,然后根据三角形的覆盖范围,在像素上生成片元。
逐片元操作
在DX中,这个阶段叫做输出合并阶段。
在这个阶段会对每个片元的可见性进行设置,这往往需要经过深度测试、模板测试等测试来对最终结果进行确定。当确定了结果以后,就把该片元的颜色值和储存在颜色缓冲区的色彩进行合并。
shader分类
Unity中使用ShaderLab编写Shader。
shader分成三类:
类型名 | 英文名 | 说明 |
---|---|---|
表面着色器 | Surface Shader | 表面着色器相当于封装了顶点片元着色器 |
顶点/片元着色器 | Vertex/Fragment Shader | 更底层的着色器,更灵活 |
固定函数着色器(已弃用) | Fixed Function Shader |
基本结构
Shader "文件路径/文件名"
{
Properties{}
SubShader{
Pass{}
}
FallBack "Diffuse"
}
Properties里声明和定义属性。变量名一般会以下划线开头。
SubShader中书写方法。可以有多个SubShader。真正意义上的Shader代码都会出现在这里。在这里还可以写表面着色器,顶点/片元着色器,固定函数着色器
FallBack后跟的是默认的shader解决方法,仅当显卡不支持SubShader中的所有方法时生效
Properties 属性
Properties是property的复数形式,但同样都表达“属性”的含义。
以下是支持的属性:
属性类型 | 默认值的定义语法 | 例子 |
---|---|---|
Int | number | _Int("Int",Int) = 2 |
Float | number | _Float("Float",Float) = 2.8 |
Range(min,max) | number | _Range("Range",Range(0.0,5.0)) = 2.5 |
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"{} |
Subshader
状态
csharp
SubShader{
// 可选的——标签
[Tags]
// 可选的——状态
[RenderSetup]
Pass{}
// Other Passes
}
标签和状态在Pass中也可以声明,但是Pass过多会造成渲染性能下降,且有的标签是SubShader特有的。 此外,在Subshader中设置的状态会应用到所有的Pass。
- 常见的状态设置
状态名称 | 设置指令 | 解释 |
---|---|---|
Cull | Cull Back / Front / Off | 设置剔除模式:剔除背面/正面/关闭剔除 |
ZTest | ZTest Less Greater / LEqual / GEqual / Equal / NotEqual / Always | 设置深度测试时使用的函数 |
ZWrite | ZWrite On/ Off | 开启/关闭深度写入 |
Blend | Blend SrcFactor DstFactor | 开启并设置混合模式 |
LOD |
用例:
csharp
SubShader{
Tags{"RenderType" = "Opaque"}
// 可选的——状态
Cull off
ZWrite off
LOD 100
Pass{}
}
标签
标签是一个键值对,键、值都是字符串类型。这些键值对连接着SubShader和渲染引擎,告诉Unity如何渲染某个对象。
结构:Tags
- 常见标签类型
标签类型 | 说明 | 例子 |
---|---|---|
Queue | 控制渲染顺序 | Tags |
RenderType | 对着色器进行分类、替换 | Tags |
DisableBatching | 指明是否使用Unity的批处理功能,是否进行合批 | Tags |
ForceNoShadowCasting | 使用该SubShader的物体是否会投影 | Tags |
IgnoreProjector | 控制物体是否受Projector的影响,通常用于透明物体 | Tags |
CanUseSpriteAtlas | 用于Sprites的时候需设为False,通常用于UI | Tags |
PreviewType | 指明材质面板如何预览该材质 | Tags |
可以在pass里也写tag。如果外面写了,那么使用的是外面的。
Pass语义块
csharp
Pass {
[Name] // Name "MyPassName"
[Tags]
[RenderSetup]
// Other code
}
可以使用Name使用其他Unity Shader中的Pass
UsePass "MyShader/MYPASSNAME" // Pass名会被Unity转换为全大写
// 还有个GrabPass,负责抓取屏幕并将结果存储在一张纹理中用于后续Pass处理
- Pass标签
标签类型 | 说明 | 例子 |
---|---|---|
LightMode | 定义该Pass在Unity的渲染流水线中的角色 | Tags |
RequireOptions | 指定当满足某些条件时才渲染该Pass,其值为空格分隔的字符串 | Tags |
注意:不建议写太多pass,会影响性能
Fallback
这是一个shader的最终解决方案,即所有SubShader都失效时的方案。
Fallback中内置一个阴影Pass。如果没有写阴影相关的内容,那么,为了Shader可以正确展示,要设置合适的Fallback。
自定义材质
可以使用CustomEditor语义扩展编辑界面,还可以使用Category语义对Unity Shader中的命令进行分组。
各种着色器
表面着色器
Unity在处理表面着色器的时候,会将其转换为对应的顶点/片元着色器(所以一定程度上这两个着色器可视作同一种)。
示例:
csharp
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"
}
CGPROGRAM和ENDCG之间的代码是使用Cg/HLSL编写的,所以我们需要把Cg/HLSL语言嵌套在ShaderLab语言中。 但这里的Cg/HLSL是Unity封装过的,有些函数未实现,且细节上有所不同。
顶点/片元着色器
示例:
csharp
Shader "Custom/Simple VertexFragment Shader" {
SubShader {
Pass { // 注意代码写在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
}
}
}
写在Pass里的原因是,我们需要自己定义每个Pass,灵活控制Shader。
固定函数着色器
对于老旧设备需要使用该着色器。自Unity 5.2始,该种着色器会编译为顶点/片元着色器。
示例:
csharp
Shader "Tutorial/Basic" {
Properties {
_Color ("Main Color", Color) = (1,0.5,0.5,1)
}
SubShader {
Pass {
Material {
Diffuse [_Color]
}
Lighting on
}
}
}
着色器适用场景说明
表面着色器,适用于与光源打交道(性能消耗较大)
顶点/片元着色器,适用于多自定义的渲染效果,以及光源少的场合
如果您坚持想用GLSL写着色器,可以将着色器嵌套到GLSLPROGRAM和ENDGLSL之间。当然,这也意味着只有支持GL的平台能够使用。
坐标空间(坐标系空间)
有四种坐标空间:物体,世界,摄像机,投影成像
1、物体空间:即3D物体自己的坐标空间
2、世界空间:即游戏世界空间
3、摄像机空间:即以摄像机为原点的坐标系空间
4、投影成像:即3D坐标转换到屏幕时,屏幕上的二位坐标空间
坐标系之间的转换
这部分的转换需要背诵常用的转换API
API | 说明 |
---|---|
transform.localToWorldMatrix | 局部转世界的矩阵 |
transform.worldToLocalMatrix | 世界坐标转局部坐标矩阵 |
可通过MultiplyPoint, MultiplyPoint3x4, MultiplyVector来进行坐标变换
Shader中也有坐标变换
|左乘_World2Object矩阵|世界坐标转局部坐标| |左乘_Object2World|局部转世界|
以下是一些矩阵名
名 | 说明 |
---|---|
UNITY_MATRIX_MV | 基本变换矩阵 x 摄像机矩阵 |
UNITY_MATRIX_MVP | 基本变换矩阵 x 摄像机矩阵 x 投影矩阵 |
UNITY_MATRIX_V | 摄像机矩阵 |
UNITY_MATRIX_P | 投影矩阵 |
UNITY_MATRIX_VP | 摄像机矩阵 x 投影矩阵 |
UNITY_MATRIX_T_MV | (基本变换矩阵*摄像机矩阵)的转置矩阵 |
UNITY_MATRIX_IT_MV | (基本变换矩阵*摄像机矩阵)的逆转置矩阵 |
UNITY_MATRIX_TEXTURE0 | 纹理变化矩阵 |
常用语义
名 | 说明 |
---|---|
POSITION | 位置 |
TANGENT | 切线 |
NORMAL | 法线 |
TEXCOORD0 | 第一套纹理 |
TEXCOORD1 | 第二套纹理 |
TEXCOORD2 | 第三套纹理 |
TEXCOORD3 | 第四套纹理 |
COLOR | 颜色 |
结构体和语义
下面是结构体的格式:
struct name{
类型 名字;
// 尽量不使用函数
返回值 函数名称(参数){
// 如果成员函数里面使用该结构体的数据成员,
// 该数据成员一定要定义在函数前
}
}
基本类型表达式
- 提供float/half/double浮点类型数值
- 定点数fixed高效处理某些小数
- 用int表示整数
- bool表示逻辑类型
- sampler*,纹理对象的句柄,sampler/1D/2D/3D/CUBE/RECT
- 内置向量数据类型:float4(float,float,float,float),长度不能超过4
- 内置矩阵数据类型:float1x1,float2x3,float4x3,float4x4.最多不能超过4x4
- 数组类型:float a[10].指10个float;float4 b[10],指10个float4
- 语义绑定:float4 a: POSITION,返回值也可以语义绑定
附录A:标准内置函数
函数名 | 解释 |
---|---|
abs(num) | 获取绝对值 |
cross(a,b) | 向量叉积 |
determinant(M) | 矩阵的行列式 |
dot(a,b) | 向量点积 |
floor(x) | 向下取整 |
lerp(a,b,f) | a,b两点间的线性插值 |
log2(x) | 以2为底的x的对数 |
mul(m,n) | 矩阵运算。可以是三种情况:矩阵x矩阵,矩阵x向量,向量x矩阵 |
power(x,y) | x的y次方 |
radians(x) | 度转弧度 |
reflect(v,n) | v关于法线n的反射向量 |
round(x) | 靠近取整 |
tex2D(sample,x) | 二位纹理查找 |
tex3Dproj(sample,x) | 投影三维纹理查找 |
texCUBE | 立方体贴图纹理查找 |
如果想使用Unity自带函数,需要引入库。引入语句为:
#include "UnityCG.cginc" Unity->Edit->Data->CGIncludes;
附录B:float4 fixed4 _Time
内置向量float4(x,y,z,w)。假设有float4 a;访问单独成员a.x,a.y,a.z,a.w
内置向量fixed4(r,g,b,a)。假设有fixed4 c;访问单独成员c.r,c.g,c.b,c.a
内置向量float3(x,y,z)
内置向量fixed3(r,g,b)
内置向量float2(x,y)
_Time:自场景加载开始所经过的时间。假设为t,可获取4个分量:t/20,t,t*2,t*3
_SinTime: 时间的正弦值。假设为t,4个分量:t/8,t/4,t/2,t
_CosTime: 时间的余弦值。假设为t,4个分量:t/8,t/4,t/2,t
unity_DeltaTime: 时间增量。假设为dt, 4个分量:dt,1/dt,smoothDt,1/smoothDt。其中,smoothDt是平滑时间,防止时间间隔起伏太大。