OpenGL简介
OpenGL为图形硬件提供软件接口,支持多种不同的硬件平台。OpenGL的客户端-服务器模式能够通过网络实现不同电脑之间的协同工作,客户端负责运行OpenGL程序,绘图任务则交给服务器端完成。
OpenGL的核心指令使用gl前缀,OpenGL Utility库的指令则使用glu前缀。所有的OpenGL常量均用以GL_开头的大写字母表示,此外,OpenGL函数调用时,用后缀制定参数个数和数据类型。
OpenGL渲染管道(Rendering Pipeline)
OpenGL管道包括一系列按次序执行的过程。顶点数据和像素数据这两种图形信息通过管道进行处理、融合并最终写入帧缓冲器。同时,OpenGL还能将处理后的数据回传给应用程序(如图中灰色虚线所示)。
Display list(显示列表)
Display list是保存并编译好的一组OpenGL指令,这组指令将在后面执行。所有的几何(顶点)数据和像素数据都可以保存在display list中。
Vertex Operation(顶点操作)
每一个顶点和法向坐标均通过GL_MODELVIEW矩阵实现从物体坐标系(object coordinates)到视觉坐标系(eye coordinates)的转换。同时,如果使能了光照功能,每个顶点的光照值是通过转换后的顶点和法向数据计算得到的。光照值会更新顶点的颜色。
Primitive Assembly(图元装配)
顶点操作之后,图元(点、线和多边形等)经投影矩阵(projection matrix)作进一步转换,再被视体(viewing volume)裁剪平面进行裁剪,从而由视觉坐标系(eye coordinates)转换到裁剪坐标系(clip coordinates)。之后,通过除w的透视除法操作(perspective division by w)和视口转换(viewport transform),将3D场景转换到窗口空间坐标系中。最后,如果使能了筛选(culling)功能,就执行筛选测试(culling test)。
Pixel Transfer Operation(像素转移)
来自客户端内存的像素数据被读取后,将会执行一系列的尺度缩放、添加偏置、映射和钳位操作,这些操作即被称为像素转移。完成像素转移操作后的数据要么存储在纹理存储器中, 要么直接光栅化到段中。
Texture Memory(纹理存储器)
纹理图片先加载到纹理存储器中,然后应用到几何对象上。
Rasterization(光栅化)
光栅化是指从集合数据和像素数据到段的转换。段是包含了颜色、深度、线宽、点的尺寸和抗混叠计算(GL_POINT_SMOOTH, GL_LINE_SMOOTH, GL_POLYGON_SMOOTH)的一个矩形阵列。如果着色模式(shading mode)设置为GL_FULL,那么多边形的内部像素区域在这一阶段将被填充。 每个段定英语帧缓冲器的一个像素。
Fragment Operation(段操作)
渲染管道的最后阶段是将段转换到帧缓冲器。这一阶段的第一步是纹理生成,纹理存储器中生成的纹理元素应用到每个段中;下一步是雾计算(fog calculation),接下来是一组顺序执行的段测试,即剪刀测试⇒ α \alpha α测试⇒模板测试⇒深度测试。最后,经过纹理融合、抖动、逻辑操作和位掩码操作之后,真实的像素数据被保存到帧缓冲区中。
Feedback(反馈)
OpenGL提供的glGet*()和glIsEnabled()指令可以获取绝大多数的当前状态和信息。此外,glReadPixels()可以从帧缓冲区中读取一个矩形区域的像素数据, glRenderMode(GL_FEEDBACK)可以得到完全转换后的顶点数据,. glCopyPixels()不会将像素数据返回到特定的系统内存,但是可以将其复制到另一个帧缓冲区中,例如将首缓冲区的像素数据复制到尾缓冲区。
OpenGL变换
包括顶点位置和法向量的几何数据在执行光栅化之前,是通过OpenGL管道的顶点操作和图元装配进行变换的。
物体坐标系 Object Coordinates
物体坐标系是物体的局部坐标系,表示的只在进行各种变换之前物体在其局部坐标系下的初始位姿。可用glRotatef(), glTranslatef(), glScalef()等函数对物体进行变换。
视觉坐标系 Eye Coordinates
将物体坐标系左乘GL_MODELVIEW矩阵即得到视觉坐标系。OpenGL通过GL_MODELVIEW矩阵将物体从局部物体坐标系变换到视觉坐标系中。GL_MODELVIEW实际上是模型矩阵(Model Matrix)和视觉矩阵(View Matrix)的组合。模型矩阵将物体从局部物体坐标系变换到世界坐标系,视觉矩阵再将物体从世界坐标系变换到视觉坐标系。
[
x
e
y
e
y
e
y
e
z
e
y
e
]
=
M
m
o
d
e
l
V
i
e
w
⋅
[
x
o
b
j
y
o
b
j
z
o
b
j
]
=
M
v
i
e
w
⋅
M
m
o
d
e
l
⋅
[
x
o
b
j
y
o
b
j
z
o
b
j
]
\left[ \begin{matrix} x_{eye} \\ y_{eye} \\ z_{eye} \end{matrix} \right] = M_{modelView} \cdot { \left[ \begin{matrix} x_{obj} \\ y_{obj} \\ z_{obj} \end{matrix} \right] } = M_{view} \cdot {M_{model}} \cdot { \left[ \begin{matrix} x_{obj} \\ y_{obj} \\ z_{obj} \end{matrix} \right] }
⎣⎡xeyeyeyezeye⎦⎤=MmodelView⋅⎣⎡xobjyobjzobj⎦⎤=Mview⋅Mmodel⋅⎣⎡xobjyobjzobj⎦⎤
为了计算光照,法向量也从物体局部坐标系变换到视觉坐标系。注意,法向量的变换矩阵不是GL_MODELVIEW,而是它的逆的转置。
[
n
x
e
y
e
n
y
e
y
e
n
z
e
y
e
]
=
(
M
m
o
d
e
l
V
i
e
w
−
1
)
T
⋅
[
n
x
o
b
j
n
y
o
b
j
n
z
o
b
j
]
\left[ \begin{matrix} nx_{eye} \\ ny_{eye} \\ nz_{eye} \end{matrix} \right] = {\left({M_{modelView}}^{-1}\right)}^T \cdot { \left[ \begin{matrix} nx_{obj} \\ ny_{obj} \\ nz_{obj} \end{matrix} \right] }
⎣⎡nxeyenyeyenzeye⎦⎤=(MmodelView−1)T⋅⎣⎡nxobjnyobjnzobj⎦⎤
裁剪坐标系 Clip Coordinates
视觉坐标系左乘投影矩阵GL_PROJECTION即可变换到裁剪坐标系。 GL_PROJECTION定义了一个视锥体,它体现了顶点数据是如何(通过透视投影或者正交投影)投影到屏幕上的。之所以称裁剪坐标系是因为投影之后的顶点(x, y, z)会进一步根据 ±w的大小作裁剪。
[
x
c
l
i
p
y
c
l
i
p
z
c
l
i
p
]
=
M
p
r
o
j
e
c
t
i
o
n
⋅
[
x
e
y
e
y
e
y
e
z
e
y
e
]
\left[ \begin{matrix} x_{clip} \\ y_{clip} \\ z_{clip} \end{matrix} \right] = M_{projection} \cdot { \left[ \begin{matrix} x_{eye} \\ y_{eye} \\ z_{eye} \end{matrix} \right] }
⎣⎡xclipyclipzclip⎦⎤=Mprojection⋅⎣⎡xeyeyeyezeye⎦⎤
归一化设备坐标系 Normalized Device Coordinates (NDC)
将裁剪坐标系的坐标值除以w记得到归一化设备坐标系的坐标值,这一步又叫做透视除法。归一化设备坐标系更加类似窗口(屏幕)坐标系了,只是还没有平移和缩放到屏幕像素。三个坐标的坐标值都被归一化到-1和1之间。
[
x
n
d
c
y
n
d
c
z
n
d
c
]
=
[
x
c
l
i
p
/
w
c
l
i
p
y
c
l
i
p
/
w
c
l
i
p
z
c
l
i
p
/
w
c
l
i
p
]
\left[ \begin{matrix} x_{ndc} \\ y_{ndc} \\ z_{ndc} \end{matrix} \right] = \left[ \begin{matrix} x_{clip} / w_{clip} \\ y_{clip} / w_{clip} \\ z_{clip} / w_{clip} \end{matrix} \right]
⎣⎡xndcyndczndc⎦⎤=⎣⎡xclip/wclipyclip/wclipzclip/wclip⎦⎤
窗口(屏幕)坐标系 Windows Coordinates (Screen Coordinates)
归一化设备坐标(NDC)经视口变换(viewport transformation)即得到窗口坐标。NDC坐标需经过缩放和平移以适应屏幕显示。窗口坐标最终经过OpenGL管道的光栅化过程变成一个段。glViewport()指令定义了最终图片映射到显示窗口的矩形区域,而glDepthRange()则确定了窗口坐标的z值。窗口坐标系的坐标值通过glViewport(x, y, w, h) 和glDepthRange(n, f)两个方程的给定参数计算得到。
[
x
w
y
w
z
w
]
=
[
w
2
x
n
d
c
+
(
x
+
w
2
)
h
2
y
n
d
c
+
(
y
+
h
2
)
f
−
n
2
z
n
d
c
+
f
+
n
2
]
\left[ \begin{matrix} x_{w} \\ y_{w} \\ z_{w} \end{matrix} \right] = \left[ \begin{matrix} \frac{w}{2} x_{ndc} + (x + \frac{w}{2}) \\ \frac{h}{2} y_{ndc} + (y + \frac{h}{2}) \\ \frac{f-n}{2} z_{ndc} + \frac{f+n}{2} \end{matrix} \right]
⎣⎡xwywzw⎦⎤=⎣⎡2wxndc+(x+2w)2hyndc+(y+2h)2f−nzndc+2f+n⎦⎤
视口变换(viewport transform)公式可简单地由NDC和窗口坐标系之间的关系给出:
{
−
1
→
x
1
→
x
+
w
{
−
1
→
y
1
→
y
+
h
{
−
1
→
n
1
→
f
\begin{cases} -1 \rightarrow x & \\ 1 \rightarrow {x + w} \end{cases} \begin{cases} -1 \rightarrow y & \\ 1 \rightarrow {y + h} \end{cases} \begin{cases} -1 \rightarrow n & \\ 1 \rightarrow f \end{cases}
{−1→x1→x+w{−1→y1→y+h{−1→n1→f
OpenGL变换矩阵
OpenGL由4x4的矩阵来表示一个变换。值得注意的是,在OpenGL中,变换矩阵的16个元素是以列优先的1维数组形式存储的. 如果要转换成标准的行优先的矩阵形式,需要对其做转置。
[
m
0
m
4
m
8
m
12
m
1
m
5
m
9
m
13
m
2
m
6
m
10
m
14
m
3
m
7
m
11
m
15
]
\left[ \begin{matrix} m_0 & m_4 & m_8 & m_{12} \\ m_1 & m_5 & m_9 & m_{13} \\ m_2 & m_6 & m_{10} & m_{14} \\ m_3 & m_7 & m_{11} & m_{15} \end{matrix} \right]
⎣⎢⎢⎡m0m1m2m3m4m5m6m7m8m9m10m11m12m13m14m15⎦⎥⎥⎤
OpenGL共有GL_MODELVIEW、GL_PROJECTION、GL_TEXTURE和GL_COLOR这四种不同的矩阵。在代码中通过使用glMatrixMode()函数来切换当前矩阵类型。例如,调用glMatrixMode(GL_MODELVIEW)即可选择GL_MODELVIEW矩阵。需要特别注意的是,OpenGL在对一个顶点做多个变换时,是按照与变换次序相反的次序进行矩阵相乘的。例如,如果对一个顶点先做 M A M_A MA变换,再做 M B M_B MB变换,那么OpenGL先做 M B × M A M_B \times M_A MB×MA,再与顶点相乘。因此在代码中后一个变换写在前面,前一个变换写在后面。
// Note that the object will be translated first then rotated
glRotatef(angle, 1, 0, 0); // rotate object angle degree around X-axis
glTranslatef(x, y, z); // move object to (x, y, z)
drawObject();
OpenGL投影矩阵
概述
因为计算机显示器是个2维平面,因此OpenGL产生的3维场景必须投影变换才能在2维屏幕上显示出来。投影矩阵GL_PROJECTION即是用来完成这一投影变换的。首先,它将顶点数据从视觉坐标系变换到裁剪坐标系,然后将裁剪坐标系坐标除以比例因子w变换到NDC坐标系。
投影变换
在投影变换中,视觉坐标系的椎体空间中的3D点被映射到NDC坐标系的立方体空间中,x坐标的范围从[l, r]映射到[-1, 1],y坐标的范围从[b, t]映射到[-1, 1],z坐标的范围从[-n, -f]映射到[-1, 1]。
透视投影矩阵 (GL_PROJECTION)
透视投影矩阵的一般形式如下:
M
p
r
o
j
e
c
t
i
o
n
=
[
2
n
r
−
l
0
r
+
l
r
−
l
0
0
2
n
t
−
b
t
+
b
t
−
b
0
0
0
−
f
+
n
f
−
n
−
2
f
n
f
−
n
0
0
−
1
0
]
M_{projection} = \left[ \begin{matrix} \frac{2n}{r-l} & 0 & \frac{r+l}{r-l} & 0 \\ 0 & \frac{2n}{t-b} & \frac{t+b}{t-b} & 0 \\ 0 & 0 & -\frac{f+n}{f-n} & -\frac{2fn}{f-n} \\ 0 & 0 & -1 & 0 \end{matrix} \right]
Mprojection=⎣⎢⎢⎡r−l2n0000t−b2n00r−lr+lt−bt+b−f−nf+n−100−f−n2fn0⎦⎥⎥⎤
如果视体是对称的,透视投影矩阵可简化为以下形式:
M
p
r
o
j
e
c
t
i
o
n
=
[
n
r
0
0
0
0
n
t
0
0
0
0
−
f
+
n
f
−
n
−
2
f
n
f
−
n
0
0
−
1
0
]
M_{projection} = \left[ \begin{matrix} \frac{n}{r} & 0 & 0 & 0 \\ 0 & \frac{n}{t} & 0 & 0 \\ 0 & 0 & -\frac{f+n}{f-n} & -\frac{2fn}{f-n} \\ 0 & 0 & -1 & 0 \end{matrix} \right]
Mprojection=⎣⎢⎢⎡rn0000tn0000−f−nf+n−100−f−n2fn0⎦⎥⎥⎤