Opengl编程指南第二章:状态管理、几何绘图

1、绘图基础

清除窗口

glClearColor(0.0, 0.0, 0.0, 0.0);
glClear(GL_COLOR_BUFFER_BIT);

第一句代码设置”清除颜色“,第二句代码将当前颜色缓冲区重置为“清除颜色”。

图形硬件除了color buffer(颜色缓冲区、对应屏幕上的像素颜色),还有其他缓冲区,比如Depth,Stencil,Accumulation,设置这些缓冲区的清除值分别使用:glClearDepth(), glClearStencil(), glClearAccum();清除的命令也是glClear, 参数分别为: GL_DEPTH_BUFFER_BIT,GL_STENCIL_BUFFER_BIT,GL_ACCUM_BUFFER_BIT。

你可以,从效率出发也应该尽量,一次清除多个缓冲区,glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT)。


指定颜色

在opengl中,绘制具体的几何图元,和指定颜色是相互独立的。你应该先设置好颜色,再绘制图元,除非这个颜色被设置为其他值,否则opengl会一直使用该颜色来绘制图元。

指定颜色使用函数 glColor3f(),glColor3fv()等。


2、点、线、多边形

由一个包含3个浮点数的向量表示,又叫vertex(顶点)。opengl使用“齐次坐标“,因此在内部使用4个浮点数的向量表示,(x,y,z,w)。如果w!=0,相当于(x/w,y/w,z/w)。

百度百科"齐次坐标":

所谓齐次坐标就是将一个原本是n维的向量用一个n+1维向量来表示。例如,二维点(x,y)的齐次坐标表示为(hx,hy,h)。由此可以看出,一个向量的齐次表示是不唯一的,齐次坐标的h取不同的值都表示的是同一个点,比如齐次坐标(8,4,2)、(4,2,1)表示的都是二维点(4,2)。

  许多图形应用涉及到几何变换,主要包括平移、旋转、缩放。以矩阵表达式来计算这些变换时,平移是矩阵相加,旋转和缩放则是矩阵相乘,综合起来可以表示为p' = m1*p + m2(m1旋转缩放矩阵, m2为平移矩阵, p为原向量 ,p'为变换后的向量)。引入齐次坐标的目的主要是合并矩阵运算中的乘法和加法,表示为p' = M*p的形式。即它提供了用矩阵运算把二维、三维甚至高维空间中的一个点集从一个坐标系变换到另一个坐标系的有效方法。
  其次,它可以表示无穷远的点。n+1维的齐次坐标中如果h=0,实际上就表示了n维空间的一个无穷远点

线由两个vertex定义

多边形由一组依次相连闭合的线段组成,可以由一组有序的vertex定义。opengl要求多边形是”简单多边形“:组成多边形的边不相交;同时要求多边形是凸多边形:连接任意两个节点的线段全部处于多边形的内部。

如果你要绘制的多边形不满足这两个条件,总是能分割成满足条件的多边形的。

由于坐标是三维的,所以四个顶点的多边形并不能保证所有点都在同一个平面内;这样的话,一个简单四边形,经过旋转、缩放、投影之后得到的可能不在是一个简单四边形。因此,当多复杂多边形进行分割的时候,一般分割成三角形。

曲线可以用短线段来接近表示,曲面可以由大量的小多边形来接近表示。


3、绘制图元

任何几何图元都是有一组顶点来定义的,opengl通过函数glVertex*()来指定顶点。

绘制图元的代码如下所示:
glBegin(GL_POINTS)
glVertex*(v1)
glVertex*(v2)
...
glEnd()

glBegin(GL_POINTS)表示要绘制的图元是点,glEnd()表示图元的顶点数据结束。要绘制其他图元,把GL_POINTS换成GL_LINES,  GL_LINE_STRIP,  GL_LINE_LOOP,  GL_TRIANGLES, GL_TRIANGLE_STRIP, GL_TRIANGLE_FAN,  GL_QUADS, GL_QUAD_STRIP,  GL_POLYGON。

要想指定顶点的颜色,只要在glVertex*指点调用glColor*即可。


4、基本状态管理

glColor*指定颜色,这就是一种opengl状态。图元最终的渲染效果受到很多状态的影响:纹理、光照、雾等。默认情况下,很多状态都是关闭的,关闭和激活这些状态使用glDisable(GLenum capability)和glEnable(GLenum capability),通过glIsEnabled(GLenum capability)可以查询状态是否激活。

有些状态的值比(true/false)更复杂,通过专门的函数来设置,可以通过下面的函数查询。

void glGetBooleanv(GLenum pname, GLboolean *params);
void glGetIntegerv(GLenum pname, GLint *params);
void glGetFloatv(GLenum pname, GLfloat *params);
void glGetDoublev(GLenum pname, GLdouble *params);
void glGetPointerv(GLenum pname, GLvoid **params);
比如glColor3f设置颜色,glGetFloatv(GL_CURRENT_COLOR,param)可以查询。


5、图元样式

点:glPointSize(GLfloat size)设置点的大小,opengl实现对点的大小范围有限制,可以通过相关API查询。

线:glLineWidth(GLfloat width)设置线的宽度,其范围同样是有限制的。线还可以通过某种模式进行刻画,glEnable(GL_LINE_STIPPLE)打开刻画功能,glLineStipple(GLint factor, GLushort pattern)设定刻画模式,第二个参数是一个16位的short,当作一个位序列来用,1表示当前像素需要刻画,0表示略过;第一个参数是一个扩展因子,pattern的每一位被扩展factor倍。

多边形:多边形有两面:front和back,这俩面可以采用不同的模式来绘制。glPolygonMode(GLenum face, GLenum mode),face可以是GL_FRONT_AND_BACK,  GL_FRONT;mode可以是GL_POINT, GL_LINE, or GL_FILL。

一般来说,如果多边形的顶点呈现逆时针方向,则多边形是面向front。“可定向流形”固体(反例是克莱因瓶和莫比乌斯带)都可以通过面向一致的多变形来构造。数学上,多变形的朝向可以由下面公式计算:a>0则front-face,否则back-face


多变形也可以通过一个32位*32位的pattern来刻画,首先调用glEnable(GL_POLYGON_STIPPLE),再调用glPolygonStipple(const GLubyte *mask)。

如果将一个非简单多边形划分成n个简单多边形进行绘制,此时需要隐藏某些多边形的某条边,glEdgeFlag*()命令使得以后续顶点为起点的边被隐藏。

法线:法线是垂直于表面的向量,对平面来说,所有点的法线方向一致,对曲面来说每个点的法线可能都不一样。opengl中,只能对每个顶点设置法线,就像设置颜色一样。对一个物体来说,法线决定了表面各处的空间方向,这对“光照”效果的渲染非常重要。设定法线使用函数glNormal*。

对面上的点来说,有两个向量垂直于面,一般来说法线是指向外部的那个向量。因此如果你想把“外部”和“内部”反转,只需要把法线(x,y,z)变成(-x,-y,-z)即可。

法线是用来表示方向的,其长度是无关紧要的,所以它最终会被规范化(normalized):长度为1。


6、顶点数组

使用glVertex*来指定顶点,绘制图元的方式非常的繁琐,需要大量的函数调用。顶点数组可以改变这种状况,使用顶点数组需要三个步骤:

激活顶点数组:glEnableClientState(GLenum array),参数array是要激活的那个数组,可能的参数值有GL_VERTEX_ARRAY, GL_COLOR_ARRAY,  GL_SECONDARY_COLOR_ARRAY, GL_INDEX_ARRAY,  GL_NORMAL_ARRAY, GL_FOG_COORD_ARRAY,  GL_TEXTURE_COORD_ARRAY, GL_EDGE_FLAG_ARRAY.
这个函数名称暗示着顶点数组其实存储在client端。glDisableClientState(GLenum array)可以关闭对应数组。

注意:顶点数组从含义上可理解为顶点相关数据的数组,包括坐标,颜色、法线等,上述参数指明了具体数组类型

为数组赋值:每种数组有一个赋值函数,已vertex为例:
glVertexPointer(GLint size, GLenum type, GLsizei stride,const GLvoid *pointer)
pointer--数据的内存地址
type--数据元素的类型,GL_SHORT,GL_INT,GL_FLOAT,GL_DOUBLE
size--顶点坐标的分量个数,为2,3,4
stride--连续顶点数组之间的偏移量,如果为零,则意味着顶点数据是紧密排列的
其他数组赋值函数请参考原书或opengl文档

使用数组:glArrayElement(GLint ith)使用指定位置的顶点数组数据,它同时使用所有激活数组的对应位置数据:颜色、法线、顶点坐标;glDrawElements(GLenum mode, GLsizei count, GLenum type,const GLvoid *indices)使用一个索引数组来使用顶点数组绘制图元,mode用来指定图元,count是顶点数量,type是索引数组的元素类型,indice是索引数组的首地址;glDrawArrays(GLenum mode, GLint first, GLsizei count)从数组first位置开始,使用count个顶点绘制图元mode。


7、缓冲区对象buffer object

为了避免内存-显卡之间频繁的数据传输,可以使用buffer object。相比顶点数组,buffer object可以存储更多的数据类型。

创建BO:glGenBuffers(GLsizei n, GLuint *buffers)分配了n个BO ID,存储在buffer里面。glIsBuffer(GLuint buffer)可以查询某个ID是否是有效的BO ID;glDeleteBuffers可以删除BO,回收资源包括BO ID。

激活BO:glBindBuffer(GLenum target, GLuint buffer),buffer是BO ID,target是表示用途的,如 GL_ARRAY_BUFFER,  GL_ELEMENT_ARRAY_BUFFER等。如果参数buffer为0,则表明不使用BO。

初始化BO:glBufferData(GLenum target, GLsizeiptr size, const GLvoid *data,GLenum usage)可以为BO分配空间,并初始化数据;usage是BO的用途,仅作为opengl优化性能的提示。

更新数据:glBufferSubData(GLenum target, GLintptr offset, GLsizeiptr size,const GLvoid *data)更新BO的部分数据;
GLvoid *glMapBuffer(GLenum target, GLenum access)将BO映射为一个本地内存地址,访问结束后需要调用glUnmapBuffer(GLenum target)来结束访问;glMapBufferRange提供更加精确的map功能。

BO之间拷贝数据:glCopyBufferSubData(GLenum readbuffer, GLenum writebuffer,GLintptr readoffset, GLintptr writeoffset,GLsizeiptr size)可以在BO之间直接拷贝数据。

使用顶点数组BO:分配绑定BO至GL_ARRAY_BUFFER初始化数据,然后按上节使用顶点数组的流程,唯一的区别在于glVertexPointer,glColorPointer的最后一个参数是BO的偏移值。

注意:GL_ARRAY_BUFFER代表数据数组buffer,如果激活了该类型的BO,则所有原本从本地数据array取数据的操作会指向BO。

Vertex-Array对象:对复杂程序来说,可能需要在很多组数据array之间切换,glVertexPointer()这样的操作会非常频繁。Vertex-array对象将对顶点数据的绑定操作汇集起来,简化操作。详细使用方式请参考原书。


原文地址:https://www.cnblogs.com/longhuihu/p/10423342.html