概要
渲染管线主要分为两种:光栅化渲染管线、光线追踪渲染管线。后者在RTX显卡上支持。
阅读本文需要对渲染管线有一定的了解,本文旨在了解光栅化渲染管线的过程,细节层面没有详细介绍。
主要过程
光栅化渲染管线的主要过程包括:顶点输入、顶点聚集、顶点着色器、曲面细分着色器、几何着色器、面剔除、裁剪、透视除法、视口变换、光栅化、提前深度测试、片元着色器、模板测试、深度测试、透明度测试、颜色混合、帧缓存输出。
黄色标注的过程为可编程部分,其余的过程为固定部分。
顶点输入
输入多个顶点,每个顶点包含一些属性,例如:位置坐标、颜色、法线、纹理坐标等。
顶点聚集
需要在CPU端指定图元信息,常见的图元包括:点、线、三角形等。
图元详情介绍
顶点着色器
主要功能是进行坐标变换。
在渲染三维物体时,通常经历:局部坐标
→
\rightarrow
→世界坐标
→
\rightarrow
→观察坐标
→
\rightarrow
→裁剪坐标
曲面细分着色器(可选)
从左到右,三角形细分越来越多
几何着色器(可选)
输入是图元,主要功能是新增或删除图元。详情可参考几何着色器
输出图元类型跟输入图元类型无关,输出图元的数量跟输入图元数量无关
几何着色器使用案例:
- 法线可视化
输入:一个三角形,输出:三个线段 - 公告牌技术
公告牌技术就是以2维图片来代替实际模型渲染的技术,使2维图片总是面向摄像机
输入:一个点,输出:一个四边形
面剔除
分为正面剔除和被面剔除
对于一个三角形,顶点顺序为逆时针表示为正面,顺时针表示为背面。可以使用右手螺旋法则来判断三角形的法线。
裁剪
对于部分位于视椎体的图元,需要对他们进行裁剪操作
对位于左下角的线段和右上角的三角形进行裁剪,得到新的顶点
透视除法
裁剪坐标 → \rightarrow →NDC坐标
归一化设备坐标(Normalized Device Coordinates,NDC)
NDC坐标范围[-1,1],可以通过NDC坐标的z分量计算出实际的线性深度值
Z
v
i
e
w
=
f
a
r
−
n
e
a
r
2
Z
n
d
c
+
f
a
r
+
n
e
a
r
2
Z_{view} = \frac{far-near}{2} Z_{ndc} + \frac{far+near}{2}
Zview=2far−nearZndc+2far+near
视口变换
NDC坐标
→
\rightarrow
→窗口坐标
窗口坐标的z分量表示非线性的深度值,范围在[0,1]。
Z
w
i
n
d
o
w
=
1
Z
v
i
e
w
−
1
n
e
a
r
1
f
a
r
−
1
n
e
a
r
Z_{window} = \frac{ \frac{1}{Z_{view}} - \frac{1}{near} } {\frac{1}{far} - \frac{1}{near}}
Zwindow=far1−near1Zview1−near1
光栅化
光栅化是个离散化的过程,将连续的物体转化为离散屏幕像素点的过程。
会对顶点着色器的输出数据进行插值,得到各个片段对应的数据值,为后面的片段着色提供片段数据。
两种插值方法如下:
- 重心坐标插值
T P = α T A + β T B + γ T C T_P = \alpha T_A + \beta T_B + \gamma T_C TP=αTA+βTB+γTC - 透视校正插值
1 T P = α 1 T A + β 1 T B + γ 1 T C \frac 1 T_P = \alpha \frac 1 {T_A} + \beta \frac 1 {T_B} + \gamma \frac 1 {T_C} T1P=αTA1+βTB1+γTC1
因为 1 Z A \frac 1 {Z_A} ZA1, 1 Z B \frac 1 {Z_B} ZB1, 1 Z C \frac 1 {Z_C} ZC1是已知的,使用如下公式计算更方便:
T P = α T A Z A + β T B Z B + γ T C Z C α 1 Z A + β 1 Z B + γ 1 Z C T_P = \frac {\alpha \frac {T_A} {Z_A} + \beta \frac {T_B} {Z_B} + \gamma \frac {T_C} {Z_C}} {\alpha \frac {1} {Z_A} + \beta \frac {1} {Z_B } + \gamma \frac {1} {Z_C}} TP=αZA1+βZB1+γZC1αZATA+βZBTB+γZCTC
提前深度测试
提前深度测试与透明度测试有冲突
开启前提:不能在片元着色器去修改深度缓冲
片元着色器
片元着色器为每个片元计算颜色,用来决定屏幕上像素的最终颜色
常用的贴图技术:
- 光照贴图:纹理贴图(漫反射贴图)+ 镜面光贴图
- 法线贴图
- 凹凸贴图
常用的光照技术:
-
Gouraud着色
-
Phong着色
-
Blinn-Phong着色
-
PBR
L o ( p , ω ⃗ 0 ) = ∫ Ω f r ( p , ω ⃗ i , ω ⃗ o ) × L i ( p , ω ⃗ i ) × ( n ⃗ ⋅ ω ⃗ i ) d ω ⃗ i L_o(p,\vec \omega_0) = \int_{\Omega} f_r (p,\vec \omega_i,\vec \omega_o) \times L_i(p,\vec \omega_i) \times ( \vec n \cdot \vec \omega_i ) d \vec \omega_i Lo(p,ω0)=∫Ωfr(p,ωi,ωo)×Li(p,ωi)×(n⋅ωi)dωi
p p p是一个观察的点, ω ⃗ 0 \vec \omega_0 ω0是出射方向, Ω \Omega Ω表示半球面, ω ⃗ i \vec \omega_i ωi是入射方向, n ⃗ \vec n n是法线方向
L o ( p , ω ⃗ 0 ) L_o(p,\vec \omega_0) Lo(p,ω0)表示出射光, f r ( p , ω ⃗ i , ω ⃗ o ) f_r (p,\vec \omega_i,\vec \omega_o) fr(p,ωi,ωo)表示反射率, L i ( p , ω ⃗ i ) L_i(p,\vec \omega_i) Li(p,ωi)表示入射光, n ⃗ ⋅ ω ⃗ i \vec n \cdot \vec \omega_i n⋅ωi表示法向与入射方向夹角的余弦值
∫ Ω d ω ⃗ i \int_{\Omega} d \vec \omega_i ∫Ωdωi表示对入射方向 ω ⃗ i \vec \omega_i ωi,沿着整个半球面 Ω \Omega Ω的积分
注意: ω \omega ω在这里代表方向的说法并不严谨,实际上是立体角
模板测试
何时更新模板缓冲?
可以在三种时机更新模板缓冲:
- 模板测试失败时;
- 模板测试通过,但深度测试失败时;
- 模板测试和深度测试都通过时。
如何更新模板缓冲?
常用的更新方式有:
- 不更新
- 更新为参考值
- 更新为0
模板函数
模板函数可以设置模板测试函数,参考值,掩码。
常见的模板测试函数有:
- 一定不通过
- 一定通过
- 等于参考值
- 不等于参考值
参考值会先与掩码进行与运算,然后再与模板缓冲的值进行比较
模板掩码
与将要写入模板缓冲的模板值进行与运算,再将运算后的值写入模板缓冲
通常情况下,值为0xFF表示不改变模板值,值为0x00表示将模板值置为0
物体轮廓绘制示例:
- 开启模板测试
- 开启模板写入(模板掩码为0xFF)。模板测试函数:测试一定通过,参考值为1。
- 正常绘制物体
得到的模板缓冲如下图所示:
- 关闭模板写入(模板掩码为0x00),模板测试函数:不等于1时测试通过
- 然后稍微放大物体,以轮廓色绘制物体
通过的片段如下图所示
最终生成的物体轮廓如下图所示:
深度测试
深度测试简单来说是一个比较当前片元深度值和深度缓冲区中对应深度值大小的过程,比较完后需要决定是否保留片元,是否更新深度缓冲区。
每个片元的z分量中存储着非线性的深度值,值的范围为0~1
透明度测试
丢弃不符合透明度条件的片段
颜色混合
混合方程如下:
C
r
e
s
u
l
t
=
C
s
o
u
r
c
e
×
F
s
o
u
r
c
e
+
C
d
e
s
t
i
n
a
t
i
o
n
×
F
d
e
s
t
i
n
a
t
i
o
n
C_{result} = C_{source} \times F_{source} + C_{destination} \times F_{destination}
Cresult=Csource×Fsource+Cdestination×Fdestination
C
r
e
s
u
l
t
C_{result}
Cresult表示源颜色,
F
s
o
u
r
c
e
F_{source}
Fsource表示源因子,
C
d
e
s
t
i
n
a
t
i
o
n
C_{destination}
Cdestination表示目标颜色,
F
d
e
s
t
i
n
a
t
i
o
n
F_{destination}
Fdestination表示目标因子
目标颜色是在颜色缓冲上的颜色,源颜色是片元着色器输出的颜色
常用的因子有:
- (0, 0, 0, 0)
- (1, 1, 1, 1)
- C s o u r c e C_{source} Csource
- 1 − C s o u r c e 1-C_{source} 1−Csource
- C d e s t i n a t i o n C_{destination} Cdestination
- 1 − C d e s t i n a t i o n 1-C_{destination} 1−Cdestination
- A s o u r c e = ( a s , a s , a s , a s ) , 其中 a s = C s o u r c e . a l p h a A_{source} = (a_s, a_s, a_s, a_s), 其中a_s = C_{source}.alpha Asource=(as,as,as,as),其中as=Csource.alpha
- 1 − A s o u r c e = ( 1 − a s , 1 − a s , 1 − a s , 1 − a s ) 1-A_{source} = (1-a_s, 1-a_s, 1-a_s, 1-a_s) 1−Asource=(1−as,1−as,1−as,1−as)
- A d e s t i n a t i o n = ( a d , a d , a d , a d ) , 其中 a d = C d e s t i n a t i o n . a l p h a A_{destination} = (a_d, a_d, a_d, a_d), 其中a_d = C_{destination}.alpha Adestination=(ad,ad,ad,ad),其中ad=Cdestination.alpha
- 1 − A d e s t i n a t i o n = ( 1 − a d , 1 − a d , 1 − a d , 1 − a d ) 1-A_{destination} = (1-a_d, 1-a_d, 1-a_d, 1-a_d) 1−Adestination=(1−ad,1−ad,1−ad,1−ad)
通常情况的半透明混合, F s o u r c e = A s o u r c e F_{source} = A_{source} Fsource=Asource, F d e s t i n a t i o n = 1 − A s o u r c e F_{destination} = 1-A_{source} Fdestination=1−Asource
帧缓冲输出
帧缓冲 = 颜色缓冲( ≥ 1 \geq 1 ≥1个) + 深度缓冲( ≥ 0 \geq 0 ≥0个) + 模板缓冲( ≥ 0 \geq 0 ≥0个)
每种缓冲都可以附加附件,有两种附件:
- 纹理附件
- 渲染缓冲对象
附件形式 | 优点 | 缺点 | 适用情况 |
---|---|---|---|
纹理附件 | 可读可写,着色器使用方便 | 读写速度较慢 | 需要从缓冲中采样数据(如颜色) |
渲染缓冲对象 | 写入或复制速度快 | 不可读,只可写 | 常被用于绑定深度缓冲或者模板缓冲 |