渲染流水线
概念流水线的三个阶段
应用阶段
输出渲染图元(点、线、三角面等)
此阶段对于开发者来说的任务有:
- 要准备好场景数据(包括设计相机的位置、视锥体、模型、光源)
- 做一个粗粒度的剔除工作,把不可见的物体剔除
- 设置好每个模型的渲染状态(材质、纹理、Shader)
几何阶段
输出屏幕的顶点信息
光栅化阶段
使用上一阶段的数据来阐述屏幕上的像素,并渲染出最终的图像
CPU与GPU的通信
把数据加载到显存中
数据从硬盘加载到内存,然后网格和纹理等数据又被加载到显卡的存储空间,即显存里
设置渲染状态
定义场景中的网格是怎样被渲染的
调用Draw Call
Draw Call:每次CPU准备数据并调用图形绘制API通知GPU的过程就称之为一个DrawCall
所谓Draw Call,就是一个命令,发起方是CPU,接收方是GPU,仅仅指向一个需要被渲染的图元(点线面)列表,不包含任何材质信息(材质信息已经加载到了显存中)
GPU流水线
几何阶段和光栅化阶段可以分成若干个更小的流水线阶段。这些流水线阶段由GPU来实现,每个姐u但GPU提供不同的可配置性和可编程性
顶点着色器:实现顶点空间变换、顶点着色功能。把顶点坐标从模型空间转换到齐次裁剪空间(类似于归一化)
曲面细分着色器:用于细分图元
几何着色器:用于执行逐图元的着色操作,或者用于产生更多图元
裁剪:将不在摄像机视野内的顶点裁剪掉,剔除某些三角图元的面片
屏幕映射:负责把每个图元的坐标转换到屏幕坐标系中
// 在Unity中,屏幕坐标系是以屏幕左下角为原点,向右为 x 正方向,向上为 y 的正方向,相机世界坐标中 z 坐标的负方向为正方向的坐标系
三角形设置:固定函数阶段,计算光栅化一个三角形网格所需的信息
三角形遍历:固定函数阶段,检查每个像素是否被一个三角网格所覆盖,是的话就生成一个片元
// 一个片元并不是真正意义上的像素,只是包含要渲染的像素所需要的信息的集合,用于计算每个像素的最终颜色
片元着色器(像素着色器):实现逐片元的着色操作
逐片元操作(合并阶段):修改颜色、深度缓冲、进行混合等
输出:屏幕图像
着色器语言
HLSL
DX的着色器语言
GLSL
OpenGL的着色语言
CG
英伟达C for Graphic
小总结
Shader 所在的阶段就是渲染流水线的一部分,更具体来说 Shader 就是GPU流水线上一些可编程的部分。而着色器编译出来的代码最终是在GPU上运行的
Unity Shader基础
Unity 中提供了方便管理着色器、代码以及渲染设置的 Unity Shader
- 标准表面着色器(Standdard Surface Shader):包含标准光照的表面着色器模板
- 无光照着色器(Unlit Shader):不包含光照的基本顶点/片元着色器
- 图像效果着色器(Image Effect Shader):实现各种屏幕后处理效果
- 计算着色器(Compute Shader):产生一个特殊的Shader文件,旨在利用GPU并行性进行一些与常规渲染流水线无关的计算
- 光线追踪着色器(Ray Tracing Shader): 执行与光线追踪相关的计算
流程
创建一个材质(Material),创建一个着色器(Shader),将Shader赋予到材质上,再把材质赋予给游戏对象,在材质面板中调整属于以达到满意的效果。
ShaderLab
ShaderLab 是 Unity 中用于编写着色器的特定语言
Shader"_ShaderName"
{
Properties{ //属性}
SubShader{ //子着色器}
SubShader{ //子着色器}
Fallback "VertexLit" //SubShader都无法使用时调用Fallback
}
属性 Properties
属性是连接材质和Shader的桥梁,这些属性将出现在材质的面板中
- Int
- Float
- Range(min, max)
- Color
- Vertor
- 2D
- Cube
- 3D
子着色器 SubShader
SubShader可以有多个且至少有一个;
当需要加载Shader时,Unity会扫描所有SubShader并选择一个能够在目标平台运行的SubShader;
若所有SubShader都不支持则调用Fallback
SubShader{
// 可选的[Tags]
// 可选的[RenderSetup]
Pass{} // 每个Pass定义了一次完成的渲染流程,但Pass数目过多性能会下降
// Other Passes
}
常见的渲染状态设置选项:
- Cull(剔除)
- Back 背面
- Front 正面
- Off 关闭
- ZTest(深度测试)
- Less Greater
- LEqual
- GEqual
- Equal
- NotEqual
- Always
- Zwrite(深度写入)
- On
- Off
- Blend(混合模式)
- SrcFactor
- DstFactor
- Queue:控制渲染顺序
- RenderType:对着色器进行分类
- DisableBatching:指定是否使用批处理
- ForceNoShadowCasting:控制投射阴影
- IgnoreProjector:控制是否受 Projector 的影响。常用于半透明物体
- CanUseSpriteAtlas: 使用此子着色器的精灵是否与 Legacy Sprite Packer 兼容
- PreviewType:指明材质面板如何预览该材质,默认球形
Pass 的标签类型:
- LightMode:定义该 Pass 在渲染流水线中的角色
- RequireOptions:指定当满足某些条件时才渲染该 Pass
降级着色器 FallBack
FallBack "name" // 使用的最低级着色器
FallBack Off // 关闭 Fallback 功能
Unity中其它与Shader相关的术语
- 着色器/着色器程序:在 GPU 上运行的程序。默认着色器程序是图形管线的一部分
- Shader 对象:Shader类的一个实例。Shader对象是着色器程序和其他信息的封装器
- ShaderLab:Unity中用于编写着色器的特定语言
- Shader Graph:一种不用代码创建着色器的工具
- 着色器资源:Unity 项目中扩展名为 .shader 的文件。它定义一个 Shader 对象
- Shader Graph 资源:Unity 项目中的文件。它定义一个 Shader 对象
传统的 Shader 仅可以编写特定的Shader,例如顶点着色器或片元着色器,而 Unity Shader 中可以在一个文件中同时包含顶点着色器和片元着色器的代码。
传统的 Shader 中无法设置一些渲染设置,例如是否开启混合、深度测试等,而 Unity Shader 中可以通过特定指令来完成。
数学相关知识
坐标系与矢量
Unity:左手坐标系,但是观察空间为右手
UE:左手坐标系
// 判断小技巧:对应手的四指从 x 指向 y,大拇指方向为 z 轴
矢量点积 => 数
几何意义:投影长度
矢量叉积 => 矢量
几何意义:垂直于两个矢量的新矢量(模长为原矢量为边的平行四边形面积)
// UE中的mutiply节点既不是点积也不是叉积,而是各个分量的简单相乘而已
变换
矩阵在三维渲染中的主要作用就是变换,包括对物体的平移、旋转、缩放,对坐标空间的变换等
线性变换:保留矢量加和标量乘
仿射变换:合并线性变换和平移变换的变换类型
齐次坐标
线性变换可以做到缩放、旋转和正交投影,但是不能满足平移的需求。因此需要将三维矢量转换成四维矢量
[
M
t
0
1
]
\left[ \begin{array}{c|c} M&t\\ \hline 0&1 \end{array} \right]
[M0t1]
左上角M为表示旋转和缩放的3×3矩阵
M
=
[
1
0
0
0
1
0
0
0
1
]
M=\begin{bmatrix} 1&0&0\\ 0&1&0\\ 0&0&1\\ \end{bmatrix}
M=⎣⎡100010001⎦⎤
右上角为平移矩阵
t
=
[
t
x
t
y
t
z
]
t=\begin{bmatrix} t_x \\ t_y\\ t_z \\ \end{bmatrix}
t=⎣⎡txtytz⎦⎤
左下角为0矩阵,右下角为标量1
平移矩阵
点 P(x, y, z) 平移 (tx, ty, tz)
[ 1 0 0 t x 0 1 0 t y 0 0 1 t z 0 0 0 1 ] ⋅ [ x y z 1 ] = [ x + t x y + t y z + t z 1 ] \begin{bmatrix} 1&0&0&t_x\\ 0&1&0&t_y\\ 0&0&1&t_z\\ 0&0&0&1 \end{bmatrix} \cdot \begin{bmatrix} x\\ y\\ z\\ 1\\ \end{bmatrix} = \begin{bmatrix} x+t_x\\ y+t_y\\ z+t_z\\ 1\\ \end{bmatrix} ⎣⎢⎢⎡100001000010txtytz1⎦⎥⎥⎤⋅⎣⎢⎢⎡xyz1⎦⎥⎥⎤=⎣⎢⎢⎡x+txy+tyz+tz1⎦⎥⎥⎤
缩放矩阵
[
k
1
0
0
0
0
k
2
0
0
0
0
k
3
0
0
0
0
1
]
⋅
[
x
y
z
1
]
=
[
k
1
⋅
x
k
2
⋅
y
k
3
⋅
z
1
]
\begin{bmatrix} k_1&0&0&0\\ 0&k_2&0&0\\ 0&0&k_3&0\\ 0&0&0&1 \end{bmatrix} \cdot \begin{bmatrix} x\\ y\\ z\\ 1\\ \end{bmatrix} = \begin{bmatrix} k_1\cdot x\\ k_2\cdot y\\ k_3\cdot z\\ 1\\ \end{bmatrix}
⎣⎢⎢⎡k10000k20000k300001⎦⎥⎥⎤⋅⎣⎢⎢⎡xyz1⎦⎥⎥⎤=⎣⎢⎢⎡k1⋅xk2⋅yk3⋅z1⎦⎥⎥⎤
旋转矩阵
[ 1 0 0 0 0 c o s θ − s i n θ 0 0 s i n θ c o s θ 0 0 0 0 1 ] cot [ x y z 1 ] = [ x y ⋅ c o s θ − z ⋅ s i n θ y ⋅ s i n θ + z ⋅ c o s θ 1 ] \begin{bmatrix} 1&0&0&0\\ 0&cosθ&-sinθ&0\\ 0&sinθ&cosθ&0\\ 0&0&0&1 \end{bmatrix} \cot \begin{bmatrix} x\\ y\\ z\\ 1\\ \end{bmatrix} = \begin{bmatrix} x\\ y\cdot cosθ-z\cdot sinθ\\ y\cdot sinθ+z\cdot cosθ\\ 1\\ \end{bmatrix} ⎣⎢⎢⎡10000cosθsinθ00−sinθcosθ00001⎦⎥⎥⎤cot⎣⎢⎢⎡xyz1⎦⎥⎥⎤=⎣⎢⎢⎡xy⋅cosθ−z⋅sinθy⋅sinθ+z⋅cosθ1⎦⎥⎥⎤
绕 y 轴旋转 θ 度
[ c o s θ 0 s i n θ 0 0 1 0 0 − s i n θ 0 c o s θ 0 0 0 0 1 ] cot [ x y z 1 ] = [ x ⋅ c o s θ + z ⋅ s i n θ y − x ⋅ s i n θ + z ⋅ c o s θ 1 ] \begin{bmatrix} cosθ&0&sinθ&0\\ 0&1&0&0\\ -sinθ&0&cosθ&0\\ 0&0&0&1 \end{bmatrix} \cot \begin{bmatrix} x\\ y\\ z\\ 1\\ \end{bmatrix} = \begin{bmatrix} x\cdot cosθ+z\cdot sinθ\\ y\\ -x\cdot sinθ+z\cdot cosθ\\ 1\\ \end{bmatrix} ⎣⎢⎢⎡cosθ0−sinθ00100sinθ0cosθ00001⎦⎥⎥⎤cot⎣⎢⎢⎡xyz1⎦⎥⎥⎤=⎣⎢⎢⎡x⋅cosθ+z⋅sinθy−x⋅sinθ+z⋅cosθ1⎦⎥⎥⎤
绕 z 轴旋转 θ 度
[
c
o
s
θ
−
s
i
n
θ
0
0
s
i
n
θ
c
o
s
θ
0
0
0
0
1
0
0
0
0
1
]
cot
[
x
y
z
1
]
=
[
x
⋅
c
o
s
θ
−
y
⋅
s
i
n
θ
x
⋅
s
i
n
θ
+
y
⋅
c
o
s
θ
z
1
]
\begin{bmatrix} cosθ&-sinθ&0&0\\ sinθ&cosθ&0&0\\ 0&0&1&0\\ 0&0&0&1 \end{bmatrix} \cot \begin{bmatrix} x\\ y\\ z\\ 1\\ \end{bmatrix} = \begin{bmatrix} x\cdot cosθ-y\cdot sinθ\\ x\cdot sinθ+y\cdot cosθ\\ z\\ 1\\ \end{bmatrix}
⎣⎢⎢⎡cosθsinθ00−sinθcosθ0000100001⎦⎥⎥⎤cot⎣⎢⎢⎡xyz1⎦⎥⎥⎤=⎣⎢⎢⎡x⋅cosθ−y⋅sinθx⋅sinθ+y⋅cosθz1⎦⎥⎥⎤
一般的顺序是:缩放 -> 旋转 -> 平移
坐标空间
模型空间(Model Space)
// 又称对象空间(Object Space)或局部空间(Local Space)
世界空间(World Space)
观察空间(View Space)
裁剪空间(Clip Space)
把顶点从观察空间转换到裁剪空间中的矩阵叫做裁剪矩阵,也被称作投影矩阵。最终渲染的部分是由视锥体决定的。
Unity 中的相机参数如下:
视锥体投影类型有两种:正交投影、透视投影
透视投影
近裁剪平面和远裁剪平面决定可以看到的最近和最远距离
n
e
a
r
C
l
i
p
P
l
a
n
e
H
e
i
g
h
t
=
2
⋅
N
e
a
r
⋅
tan
F
O
V
2
nearClipPlaneHeight=2\cdot Near\cdot \tan\frac{FOV}{2}
nearClipPlaneHeight=2⋅Near⋅tan2FOV
F
a
r
C
l
i
p
P
l
a
n
e
H
e
i
g
h
t
=
2
⋅
F
a
r
⋅
tan
F
O
V
2
FarClipPlaneHeight=2\cdot Far\cdot \tan\frac{FOV}{2}
FarClipPlaneHeight=2⋅Far⋅tan2FOV
宽度由计算出的高度和设置的宽高比 Aspect 可以计算出。
透视投影矩阵:
[
cot
F
O
V
2
A
s
p
e
c
t
0
0
0
0
cot
F
O
V
2
0
0
0
0
−
F
a
r
+
N
e
a
r
F
a
r
−
N
e
a
r
−
2
⋅
N
e
a
r
⋅
F
a
r
F
a
r
−
N
e
a
r
0
0
−
1
0
]
\begin{bmatrix} \frac{\cot\frac{FOV}{2}}{Aspect}&0&0&0\\ 0&\cot\frac{FOV}{2}&0&0\\ 0&0&-\frac{Far+Near}{Far-Near}&-\frac{2\cdot Near\cdot Far}{Far-Near}\\ 0&0&-1&0 \end{bmatrix}
⎣⎢⎢⎢⎡Aspectcot2FOV0000cot2FOV0000−Far−NearFar+Near−100−Far−Near2⋅Near⋅Far0⎦⎥⎥⎥⎤
把顶点从观察空间转换到裁剪空间,将透视投影矩阵点乘顶点坐标:
[
cot
F
O
V
2
A
s
p
e
c
t
0
0
0
0
cot
F
O
V
2
0
0
0
0
−
F
a
r
+
N
e
a
r
F
a
r
−
N
e
a
r
−
2
⋅
N
e
a
r
⋅
F
a
r
F
a
r
−
N
e
a
r
0
0
−
1
0
]
⋅
[
x
v
y
v
z
v
1
]
=
[
x
c
y
c
z
c
−
z
v
]
\begin{bmatrix} \frac{\cot\frac{FOV}{2}}{Aspect}&0&0&0\\ 0&\cot\frac{FOV}{2}&0&0\\ 0&0&-\frac{Far+Near}{Far-Near}&-\frac{2\cdot Near\cdot Far}{Far-Near}\\ 0&0&-1&0 \end{bmatrix}\cdot \begin{bmatrix} x_v\\ y_v\\ z_v\\ 1\\ \end{bmatrix} = \begin{bmatrix} x_c\\ y_c\\ z_c\\ -z_v\\ \end{bmatrix}
⎣⎢⎢⎢⎡Aspectcot2FOV0000cot2FOV0000−Far−NearFar+Near−100−Far−Near2⋅Near⋅Far0⎦⎥⎥⎥⎤⋅⎣⎢⎢⎡xvyvzv1⎦⎥⎥⎤=⎣⎢⎢⎡xcyczc−zv⎦⎥⎥⎤
转换后顶点的 w 分量不再为1而是观察空间 z 坐标的相反数,使用该 w 分量判断顶点是否在视锥体内
−
w
≤
x
≤
w
−
w
≤
y
≤
w
−
w
≤
z
≤
w
-w\leq x \leq w \\ -w\leq y \leq w \\ -w\leq z \leq w
−w≤x≤w−w≤y≤w−w≤z≤w
正交投影
正交投影的视锥体是一个长方体
n
e
a
r
C
l
i
p
P
l
a
n
e
H
e
i
g
h
t
=
F
a
r
C
l
i
p
P
l
a
n
e
H
e
i
g
h
t
=
2
⋅
S
i
z
e
nearClipPlaneHeight=FarClipPlaneHeight=2\cdot Size
nearClipPlaneHeight=FarClipPlaneHeight=2⋅Size同理宽度也是由计算出的高度和设置的宽高比 Aspect 可以计算出。正交投影矩阵:
[
1
A
s
p
e
c
t
⋅
S
i
z
e
0
0
0
0
1
S
i
z
e
0
0
0
0
−
2
F
a
r
−
N
e
a
r
−
F
a
r
+
N
e
a
r
F
a
r
−
N
e
a
r
0
0
0
1
]
\begin{bmatrix} \frac{1}{Aspect\cdot Size}&0&0&0\\ 0&\frac{1}{Size}&0&0\\ 0&0&-\frac{2}{Far-Near}&-\frac{Far+Near}{Far-Near}\\ 0&0&0&1\\ \end{bmatrix}
⎣⎢⎢⎡Aspect⋅Size10000Size10000−Far−Near2000−Far−NearFar+Near1⎦⎥⎥⎤把顶点从观察空间转换到裁剪空间,将正交投影矩阵点乘顶点坐标:
[
1
A
s
p
e
c
t
⋅
S
i
z
e
0
0
0
0
1
S
i
z
e
0
0
0
0
−
2
F
a
r
−
N
e
a
r
−
F
a
r
+
N
e
a
r
F
a
r
−
N
e
a
r
0
0
0
1
]
⋅
[
x
v
y
v
z
v
1
]
=
[
x
c
y
c
z
c
1
]
\begin{bmatrix} \frac{1}{Aspect\cdot Size}&0&0&0\\ 0&\frac{1}{Size}&0&0\\ 0&0&-\frac{2}{Far-Near}&-\frac{Far+Near}{Far-Near}\\ 0&0&0&1\\ \end{bmatrix}\cdot \begin{bmatrix} x_v\\ y_v\\ z_v\\ 1\\ \end{bmatrix} = \begin{bmatrix} x_c\\ y_c\\ z_c\\ 1\\ \end{bmatrix}
⎣⎢⎢⎡Aspect⋅Size10000Size10000−Far−Near2000−Far−NearFar+Near1⎦⎥⎥⎤⋅⎣⎢⎢⎡xvyvzv1⎦⎥⎥⎤=⎣⎢⎢⎡xcyczc1⎦⎥⎥⎤变换后 w 分量仍然是1,判断是否在视锥体内的公式仍然不变
− w ≤ x ≤ w − w ≤ y ≤ w − w ≤ z ≤ w -w\leq x \leq w \\ -w\leq y \leq w \\ -w\leq z \leq w −w≤x≤w−w≤y≤w−w≤z≤w
屏幕空间(Screen Space)
顶点变换的最后一步,变换到屏幕空间,也就是把视锥体投影到屏幕空间。
用齐次除法(或者叫透视除法),将齐次坐标系 w 分量除以 x、y、z 分量,变换到一个立方体内。再进行屏幕映射求屏幕上的像素坐标(Screenx,Screeny):
S
c
r
e
e
n
x
=
c
l
i
p
x
⋅
p
i
x
e
l
W
i
d
t
h
2
⋅
c
l
i
p
w
+
p
i
x
e
l
W
i
d
t
h
2
Screen_x=\frac{clip_x\cdot pixelWidth}{2\cdot clip_w}+\frac{pixelWidth}{2}
Screenx=2⋅clipwclipx⋅pixelWidth+2pixelWidth
S
c
r
e
e
n
y
=
c
l
i
p
y
⋅
p
i
x
e
l
H
e
i
g
h
t
2
⋅
c
l
i
p
w
+
p
i
x
e
l
H
e
i
g
h
t
2
Screen_y=\frac{clip_y\cdot pixelHeight}{2\cdot clip_w}+\frac{pixelHeight}{2}
Screeny=2⋅clipwclipy⋅pixelHeight+2pixelHeight
- clip_x:裁剪空间中顶点的 x 分量
- clip_y:裁剪空间中顶点的 y 分量
- clip_w:裁剪空间中顶点的 w 分量
- pixelWidth:屏幕宽度像素分辨率
- pixelHeight:屏幕高度像素分辨率