说明:简单了解一下OpenGL的工作原理,初步认识计算机对于图形渲染的底层设计与实现,第一次接触,也没学过C艹,欢迎各位批评指正。
一 什么是OpenGL?
OpenGL是一个开放标准(specification),是一种接口规范,并没有固定实现。每个硬件厂商负责对自己的硬件提供OpenGL接口标准的具体实现。三者关系如下链表:OpenGL API---硬件厂商【各自完成具体实现接口】--使用者【调用OpenGL提供的接口】【厂商的第三方库并不开源,但目前已有开源GL实现的DEMO,如Mesa,有兴趣可自行了解。】
在日常使用OpenGL时,需要的一些辅助第三方库:GLUT,辅助实现窗口显示、控制流程等功能,而OpenGL仅仅承担图形渲染的工作,即具体利用视图变换、光照以及纹理着色等技术,还原实现了虚拟场景:
实例1
实例2
二 OpenGL中不得不提的图形渲染管线(Pipeline)
图形渲染管线(Pipeline)可以看做是一条像素生产流水线,【流水线的输入端是顶点数据和像素数据,经过光栅化后,将片元(Fragment)组合着色成为屏幕可视内容,实现渲染效果】,
- 固定的图形管线渲染流程如下:
1 )指定需要完成渲染的几何图元:这些几何图元均是通过顶点指定的,其中包括了点、直线以及三角形等几何要素。
2) 顶点变换操作:在获得了指定图元后,就可以对定义图元的顶点进行变换处理。
3 )裁剪、透视触发以及视点适应变换等:将各个顶点进行变换处理后,需要将世界坐标中的图元规范化处理,统一缩放到正规化可视空间CCV中,图元坐标将会从世界坐标转换为窗口坐标;
2/3这一套处理方式可以从图形渲染管线的主干中分出一个顶点渲染管线分支,该分支将在下一节详细介绍。
4 )光栅化【1】:坐标变换,几何离散化,即将图元从三维坐标转换为一个二维平面图像。
5 )片元处理操作:对光栅化后的片元图像进行综合处理:提取纹理、效果、颜色等,对片元进行逐个测试,如像素所有权、剪切、模版测试以及深度测试等,只有当所有测试都通过之后,才会写入帧缓冲区(Frame Buffer)【2】
6) 片元数据转换为像素数据形式,写入帧缓冲区
图1 OpenGL的图形渲染管线【3】
三 固定管线中的核心——顶点处理管线
在图像渲染管线中细分出的顶点处理管线主要用于对顶点数据进行降维转换,即将三维空间中的实点转换到二维空间屏幕的坐标系上。
1)数学基础
在顶点处理管线中,最重要的一个概念在于变换,即上面概念提到的实点的空间转换。但在详细解释顶点处理管线中的空间转换前,我们需要简单了解一下所需的数学基础:齐次坐标 和 矩阵与图形变换。
- 坐标:本质上是一个增加维度的操作,即n维向量(p1,p2,p3,…,pn)增加至第n+1维(p1,p2,p3…,pn,pn+1),方便空间变换中的旋转、平移、透视等操作。
- 矩阵与坐标变换:图形是由一个个坐标点组成的,通过矩阵与转换矩阵相乘,得到变换后的坐标,即得到了空间变换后的图形。
举一个简单的例子:
平移操作:设某几何图形的三维点坐标集合为{ (x,y,z) | x , y , z∈R},先要将图形整体平移(a, b , c),首先,需要将三维点坐标拓展为齐次坐标(x,y,z,1),其次,构造一个4*4的矩阵T:
图2 平移单位矩阵T
所以,平移操作可以视为:(x,y,z,1)·T = (x+a, y+b, z+c, 1),所以该几何图形经过平移后的三维点坐标集合为{ (x+a, y+b, z+c) | x , y , z∈R}
2)顶点处理管线主要步骤
2-1)模型视图变换:
① 模型变换主要包括了位移、旋转和缩放等仿射变换【4】,这个部分的模型变换比较简单,上文也提到了关于平移操作的具体矩阵,所以在此就不赘述。模型变换中涉及到的变换大都是仿射变换,仅通过目标几何体的齐次坐标矩阵与操作单位矩阵点乘即可得到变换位置后的几何体。
上述三个仿射变换操作,在OpenGL中分别对应着:glTranslate*(), glRotate*(), glScale*()。
图3 仿射变换
② 视图变换
主要目的在于将目标几何体从世界坐标中移至摄像机坐标,该变换主要围绕“摄像机”的概念进行定义和实现:
首先,我们以摄像机作为视点,将视点移动到当前坐标系中的任一固定位置,并以此位置为摄像机坐标系的原点。若要将已处在世界坐标系上的几何体转换到摄像机的坐标体系中,则需要先得到摄像机坐标系与世界坐标系的相对关系,然后在从世界坐标系中取得几何体,并对该目标几何体做出相应的相对关系处理。
在OpenGL中提供了接口:gluLookAt(GLdouble eyex, GLdouble eyey, GLdouble eyez, GLdouble centerx, GLdouble centery, GLdouble centerz, GLdouble upx, GLdouble upy, GLdouble upz)
2-2)光和颜色:
① 光照:
着色需要考虑光照反射光的情况,可以从单点光照模型的情况入手:
光照模型中,我们所能看到的光主要以目标物的反射光为主,先不考虑入射光(有色OR无色)情况,反射光由镜面反射和漫反射组成,除了镜反射光IJ、漫反射光IM外,考虑到外部环境影响,设置环境光为IE,环境反射光=环境光IE*环境反射系数pe。按照聚光灯模型,反射光IReturn会随着角度和距离的变化而衰减,所以:IReturn=(IJ+IM+IE*pe)*atten(光源到几何体面上某点的距离D)*衰减系数cos(β),再加上全局环境光IE_ALL与自发光e,总体反射光为:
IReturnAll = IReturn + IE_ALL*pe + e
在OpenGL中,最多可以定义8个光源,每个光源可以设置不同的属性:
- 定义光源:Void glLight*(Glenum light, Glenum pname, GLfloat param)
【light】对应光源
【pname】参数字段
【param】参数值
其中pname可用参数参照下表:
GL_AMBIENT | 环境光分量 |
GL_DIFFUSE | 漫反射分量 |
GL_SPECULAR | 镜面反射分量 |
GL_POSITION | 光照位置 |
GL_SPOT_CUTOFF | 聚光灯角度 |
GL_SPOT_DIRECTION | 聚光灯方向向量 |
GL_SPOT_EXPONENT | 比例系数e |
GL_CONSTANT_ATTENUATION | 聚光灯衰减系数 |
GL_LINEAR_ATTENUATION | 聚光灯衰减系数 |
GL_QUADRATIC_ATTENUATION | 聚光灯衰减系数 |
- 激活光源(默认不可用):glEnable(glLight*)
- 定义目标物的表皮材质:void glMaterialf( GLenum face, Glenum pname, Gfloat param)
② 上色:
OpenGL一般有两种着色模型:平面着色( Flat shading )和平滑着色( Smooth Shading),从效果来看,平滑着色在细节表现上要优于平面着色。而平滑着色又分为Gouraud着色和Phong着色,其中Gouraud着色采用了线性插值的淡化边方法,每个像素的颜色值都是通过线性插值的方式得到的,可以平滑的表达色彩和光照的过渡,但缺点在于不善于表达强光区。而Phong则是对法向量进行着色,当前OpenGL不支持该着色模型,即无法进行透视投影变换等后续操作。
在OpenGL中:
- 设置OpenGL着色模式的函数:
void glShadeModel(GL_SMOOTH/GL_FLAT)
- 提供了如下API接口以供颜色绘制:
void glColor3f( GLfloat red, GLfloat green, GLfloat blue)
void glColor4f( GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha)
(该着色过程发生在模型视图变换之后)
- 颜色清除API:
void glClearColor(GLfloat , GLfloat , GLfloat, GLfloat ) //用于指定空色
void glClear(GL_COLOR_BUFFER_BIT) //空色填充,清除画板
PS:值得一提的是,OpenGL3.0后均采用GLSL语言进行可编程渲染,通过着色器替代顶点处理管线、纹理化和色彩化环节【5】。着色器技术的可编程性可以实现各种图像效果而不受显卡固定管线的限制,提高了图像的画质。
2-3)投影变换(Porjection):
在完成了上述的视图模型变换之后,我们已经将几何体从世界坐标中转换到了摄像机坐标系中,接下来,为了在二维平面上表示该几何体,我们需要继续进行下一个步骤——投影变换。投影变换可以简单地理解为,将三维空间的几何体投影到摄像机视锥体的近平面和远平面之间。一般投影变换可以分为透视投影、正交投影和斜投影(本文以透视投影为例)。
图4 透视投影和平面投影(近裁面和远裁面之间的空间称之为frustum)【6】
透视投影遵循近大远小的原理,符合人们日常的视觉心理,具有较好的仿真性。在OpenGL中也提供了两个API接口:
- void glFrustum( GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble near, GLdouble far) 创建一个透视投影矩阵
【left/right, bottom/top, -near】左下角点( left, bottom, -near)和右上角点( right, top, near)
【far】远裁面的Z值(负数)
- gluPerspective( GLdouble fovy, GLdouble aspect, GLdouble zNear, GLdouble zFar )
【fovy】x-z平面的视角张角的范围
【aspect】实际窗口的长宽比x/y
【zNear】视锥体的近裁面
【zFar】视锥体的远裁面
2-4)裁剪、透视除法(clip)
经过上述处理,如果点位于Frustum的空间中,则投影变换后的几何体点坐标均介于[-1,1]之间,并将x∈[-1,1], y∈[-1,1], z∈[-1,1]的正方体定义为正规化可视空间CCV,裁剪的目的在于裁剪掉CCV外的图元。
透视除法:由于之前为了方便模型视图变换操作,我们用四维齐次坐标作为三维点坐标的代表进行矩阵操作,所以需要通过一定的操作将齐次坐标(x, y, z, q)转换回三维点坐标(x, y, z)。具体操作为:齐次坐标除以第四个分量q,然后丢弃第四个分量,转换回归一化的三维坐标(x/q, y/q, z/q)。
2-5)视口变换(view port)
此时我们已经得到了归一化坐标,即所有的点坐标都处于CCV中,所以需要通过缩放和位移使其在窗口(屏幕)输出渲染。
在OpenGL中提供了:
- void glViewport( Glint x, GLin y, GLsizei width, GLsizei height )
【x, y】视窗屏幕的左下角坐标
【width/height】视窗屏幕的实际宽高
PS:此处需要提醒一下视口变换与投影变换的区别:
图5 视口变换与投影变换的区别【7】
2-6)最后得到窗口坐标,进行光栅化处理。
【名词解释 & 参考文献】
【1】光栅化(Rasterize)【像素化或栅格化】的渲染方式:
图元(primitive)指点、线、三角形等基本几何图形,片元(fragment):在光栅化之后,裁剪后称之为片元,片元采用的是屏幕窗口坐标
【2】帧缓冲区:帧缓存OR显存,是屏幕显示画面的直接映像,逻辑上是一组由屏幕上所有像素组成的二维数组,每一个存储单元负责对应屏幕上的一个像素,整个帧缓冲区对应的一帧图像即为当前屏幕显示的画面。一个完整的帧缓冲应当包括颜色缓存、深度缓存、模版缓存、积累缓存以及多重采样缓存
【3】Shreiner Dave. Opengl Programming Guide: The Official Guide To Learning Opengl, Version 2.1, 6/E. Pearson Education India, 2008.
【4】仿射变换:在几何中,一个向量空间进行一次线性变换外加一次平移操作,变换成为另一个向量空间,可以认为线性变换 (向量y=A·向量x )是一次特殊的仿射变换 (向量y=A·向量x+ 向量B )
【5】【OPENGL】第三篇 着色器基础(一):https://www.cnblogs.com/MyGameAndYOU/p/4691081.html
【6】songho:OpenGL Projection Matrix( http://www.songho.ca/opengl/gl_projectionmatrix.html
【7】图片来自于 :http://www.cnblogs.com/liangliangh/p/4089582.html
【8】本文的一部分内容是来源这位大神的博客:http://www.twinklingstar.cn/