我是靠谱客的博主 活泼犀牛,最近开发中收集的这篇文章主要介绍OGL绘制相关函数总结1,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

一.绘制相关函数

1)glClear**

glClearColor,glClearDepth,  glClearIndex, glClearStencil, glClearAccum   目的是颜色后台缓存,深度缓存等设置为一个状态,不用每次绘制屏幕的时候都指定颜色。 也就是上面的函数只需要init中调用,不需要每次display中调用设置颜色和深度。
每帧display时候需要清理屏幕用glClear例如:
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT | GL_ACCUM_BUFFER_BIT);
GL_ACCUM_BUFFER_BIT是OGL拓展,是累积缓存区。

2)glColor

glColor是指定颜色,颜色是一个状态,绘制物体都会基于当前最新设置的颜色来进行绘制。
WINGDIAPI void APIENTRY glColor3f (GLfloat red, GLfloat green, GLfloat blue); // 参数要求是0-1之间的值。
WINGDIAPI void APIENTRY glColor3fv (const GLfloat *v);
WINGDIAPI void APIENTRY glColor4f (GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha);
WINGDIAPI void APIENTRY glColor4d (GLdouble red, GLdouble green, GLdouble blue, GLdouble alpha);
WINGDIAPI void APIENTRY glColor4fv (const GLfloat *v);
颜色的绘制可以使用颜色向量。

3)glFlush/glutSwapBuffers

CPU提交全部渲染命令,驱动GPU绘制(双缓存会交换链翻转绘制).
void display{
  glClear(GL_COLOR_BUFFER_BIT);
   glPushMatrix();
   glRotatef(spin, 0.0, 0.0, 1.0);
   glColor3f(1.0, 1.0, 0.0);
   glRectf(-25.0, -25.0, 25.0, 25.0);
   //glutSolidCube(5.0f);
   glPopMatrix();
   // 双缓存用SwapBuffer(异步的),单缓存用glFlush()(异步的),glFinish是同步的提交渲染完成才解锁CPU
   glutSwapBuffers();
}
在窗口系统中有鼠标键盘响应函数,不属于OGL,但是响应函数中调用了 glutPostRedisplay();会再次调用绘图函数, 也就是   glutDisplayFunc(display); 中指定的函数,这里是display。

二.绘制命令函数(生成绘制命令,glBegin glEnd(draw call), glFlush)

1)基本命令

绘制示例:
#define drawOneLine(x1,y1,x2,y2)  glBegin(GL_LINES);  
   glVertex2f ((x1),(y1)); glVertex2f ((x2),(y2)); glEnd();

glBegin标志着一个顶点数据列表的开始,它描述了一个几何图元。
glEnd标志着一个顶点数据列表的结束。

glBegin可以指定多种图元类型,glBegin和glEnd之间除了生成顶点的glVertex*命令,所有合法函数为:
glVertex*() 设置顶点坐标 
glIndex*() 设置当前颜色表 
glVertexAtrrib*()设置通用的坐标属性
glEdgeFlag*() 控制边界绘制
glArrayElement()提取顶点数组数据

glColor*() 设置当前颜色 
glSecondaryColor*()设置纹理应用后的辅助颜色


glNormal*() 设置法向坐标 
glMaterial*() 设置材质

glCoord*() 产生坐标 
glEvalCoord*(),glEvalPoint*()生成坐标
glFogCoord*()设置雾坐标
glTexCoord*() 设置纹理坐标
glMultiTexCoord*()为多重纹理设置坐标
glCallList(),glCallLists() 执行显示列表 
其他的OGL函数例如:glEnableClientState, glVertexPointer(), glX*等函数不能放置在glBegin, glEnd之间否则会产生难以发觉的错误。但是可以包含其他编程语言结构,例如C原因的for语句,cos, sin函数。
 其实上述如此多的顶点相关命令函数设置顶点,可以用顶点数组来设置提高程序的性能。

2).基本的绘制图元类型

#define GL_POINTS                         0x0000

#define GL_LINES                          0x0001
#define GL_LINE_LOOP                      0x0002
#define GL_LINE_STRIP                     0x0003

#define GL_TRIANGLES                      0x0004
#define GL_TRIANGLE_STRIP                 0x0005
#define GL_TRIANGLE_FAN                   0x0006

#define GL_QUADS                          0x0007
#define GL_QUAD_STRIP                     0x0008
#define GL_POLYGON                        0x0009
没有STRIP, LOOP的是每个单元之间是没有联系的,STRIP是满足单位个数相互之间紧密连接,LOOP不满足单位个数也可以之间相互连接尾头部也连接。
其中GL_TRIANGLE_STRIP用于绘制三角形带最合适,也最合适表现复杂物体的表面(建模中)。

3)控制图元绘制基本属性的函数

点绘制:
glPointSize()设置点的宽度,可以更好的表现粒子效果。
直线绘制:
glLineWidth设置直线绘制的宽度,默认是1,ogl 3.1取值只能为0-1.也就是只能表现更小的线。直线的实际绘制会受到抗锯齿和多重采样的影响。如果启用了抗锯齿功能那么直线的绘制可以是非整数的像素,斜率计算和边沿会更加平滑。
GL_ALISSED_LINE_WIDTH_RANGE, GL_SMOOTH_LINE_WIDTH_RANGE, GL_SMOOTH_LINE_WIDTH_GRANULARITY可以查询抗锯齿下绘制直线的 一些参数。

glLineStipple可以定义直线的类型,实线,虚线,点画线等。
glLineStipple (GLint factor, GLushort pattern); pattern是掩码画直线遇到它是1则绘制0不绘制,factor是因子用于将pattern扩大多少倍。如果pattern是1110011,factor是2,那么最终的模式为:11111100001111.需要: glEnable (GL_LINE_STIPPLE)启用。

多边形绘制:
glPolygonMode (GLenum face, GLenum mode); face是GL_FRONT_AND_BACK, GL_FRONT, GL_BACK控制正反面的绘制。
mode是GL_POINT, GL_LINE, GL_FILL 多边形的样式是点,线框,还是填充模式。

glFrontFace(GLenum mode)指定多边形的正面, GL_CCW是逆时针(默认是它), GL_CW是顺时针。

glCullFace表示那些多边形在转换到屏幕之前 就应该丢弃(在ndc坐标中丢弃)。GL_FRONT, GL_BACK, GL_FRONT_AND_BACK表示要丢弃的面。使用该功能必须要glEnable(GL_CULL_FACE)启用。

点画模式绘制多边形,是ogl的拓展:
 glEnable (GL_POLYGON_STIPPLE); // 启用点画多边形
  glPolygonStipple (fly); // fly是数组掩码,为1则绘制,0不绘制
  glRectf (125.0, 25.0, 225.0, 125.0); // 使用点画模式指定的掩码,对多边形进行绘制
指定多边形的着色模式:
glShadeModel (GL_FLAT); //  GL_SMOOTH

多边形共享边和法向量
glEdgeFlag用于控制多边形的顶点是否为共享边,避免多次绘制,但对于三角形带,四边形带指定的顶点无效。
glNormal3fv用于指定顶点的法向量值,具体法向量的指定一些建模软件导出模型中转换到图形引擎中会进行这样的解释。
计算光照前需要对法向量进行单位化。
( 1).如果一开始就是单位向量,后面只是进行平移和旋转,那么法向量不需要重新计算,也不需要重新规范化。
( 2).glEnable(GL_RESCALE_NORMAL) 对于开始有规范化的法向量,其中进行了均匀缩放的,那么需用GL_RESCALE_NORMAL重新规范化法向量,会比GL_NORMALIZE更快。如果是单位化了的法线向量(例如3ds max中导出的法线贴图),那么模型均匀缩放后,可以用GL_RESCALE_NORMAL重新规范化法向量。
( 3).glEnable(GL_NORMALIZE) 对于开始没有规范化的法向量,或者网格进行了非均匀缩放,那么需要重新程序员手动计算法向量的值(不要轻易非均匀缩放),且要进行GL_NORMALIZE规范化法向量。
Name

4)封装glBegin/glEnd的绘制函数

 glRectf (125.0, 25.0, 225.0, 125.0);

glRect(x1, y1, x2, y2) is exactly equivalent to the following sequence:

glBegin(GL_POLYGON);
glVertex2(x1, y1); // z等于0
glVertex2(x2, y1);
glVertex2(x2, y2);
glVertex2(x1, y2);
glEnd();

三.基本状态管理

绘制和OGL状态是紧密相连的,状态紧密的影响了绘制函数的表现,也是绘制函数的对外调整参数接口。
渲染时候使用纹理,光照,绘制正面设置,雾,融合,深度检测,stencil buffer设置等,都需要启动相应的状态,启用很多状态都比较消耗性能,但是可以获得更好的表现效果。
用glEnable,glDisable来启用和关闭。
glIsEnable来查询状态是否启用,使用glGetBooleanv,glGetIntegerv, glGetFloatv, glGetDoublev, glGetPointerv来查询状态更多的参数值(比如颜色值)。

四.更实用的高级绘制命令函数

传统绘制:基本的绘制命令是glBegin glEnd, 顶点设置函数频繁调用,不能区分共享顶点,数据放置在cpu ram中每次draw call需要传输数据到GPU.
顶点数组:动态修改数据是用顶点缓存索引缓存的顶点数组,减少函数调用,和共享顶点设置,数据放置在cpu ram中每次draw call需要传输数据到GPU。
displaylist:静态数据放置在显卡中不能动态修改是用displaylist。
缓存对象:数据放置在GPU显卡中,恰当时候会调度到AGP, CPU RAM中,可以动态修改顶点数据,且又有很好的传输性能。
缓存对象和Shader结合:OGL推荐的绘制方式。

1.顶点数组和绘制函数汇总

顶点数组使用步骤:

1)启用数组缓存(CPU)中的 glEnableClientState(),可以激活顶点坐标,法向量,颜色,辅助颜色雾坐标纹理坐标多边形的边界标志

#define GL_VERTEX_ARRAY 0x8074
#define GL_NORMAL_ARRAY 0x8075
#define GL_COLOR_ARRAY 0x8076
#define GL_INDEX_ARRAY 0x8077 // 这个是颜色索引,并不是索引缓存,和GL_COLOR_ARRAY 不能共存
#define GL_TEXTURE_COORD_ARRAY 0x8078
#define GL_EDGE_FLAG_ARRAY 0x8079
glDisableClientState()拥有关闭顶点数组,因为顶点数组状态是在客户端的所以不用glEnable glDisable(可以在GPU中)语义更加明确。

2)加载数据到数组顶点相关寄存器中,用glxxxPointer或用混合数组 glInterleavedArrays

void  glVertexPointer(GLint size,GLenum type,GLsizei stride,const GLvoid * pointer); // stride是在混合数组中本类型的字节跨度
// 顶点数据加载,这里从NULL地址中加载
 glVertexPointer(3, GL_FLOAT, 6 * sizeof(GLfloat), (const GLubyte *)NULL + 3 * sizeof(GLfloat));//BUFFER_OFFSET(3 * sizeof(GLfloat))
 // 颜色数据加载,这里从NULL地址中加载
glColorPointer(3, GL_FLOAT, 6 * sizeof(GLfloat), NULL);//BUFFER_OFFSET(0)
glSecondaryColorPointer
glIndexPointer
glNormalPointer();
glTexCoordPointer();
glFogCoordPointer
glEdgeFlagPointer
顶点着色器使用的其它顶点属性,也可以放置在顶点数组中。

3)glInterleavedArrays整合1)2):

GLAPI void GLAPIENTRY glInterleavedArrays (GLenum format, GLsizei stride, const GLvoid *pointer);相当于调用了多个glEnableClientState和glxxxPointer,实现启用数组缓存和加载数据的工作。
初始化顶点相关数组的全部8个数组,禁用format并没有指定的数组,并启用format参数所指定的数组。pointer是启用的相应数组的初始下标值列表,如果启用了多重纹理只是影响当前的活动纹理单元。stride为0那么认为数组是紧密相连的(例如顶点数组和颜色数组之间是紧密相连的)。
glInterleavedArrays(GL_C3F_V3F, 0, 0);  // 用来表示顶点数组中是否包含 纹理, 颜色,法向量, 顶点和规定对齐(跨距)
下面是format的合法值:
#define GL_V2F 0x2A20
#define GL_V3F 0x2A21
#define GL_C4UB_V2F 0x2A22
#define GL_C4UB_V3F 0x2A23
#define GL_C3F_V3F 0x2A24
#define GL_N3F_V3F 0x2A25
#define GL_C4F_N3F_V3F 0x2A26
#define GL_T2F_V3F 0x2A27
#define GL_T4F_V4F 0x2A28
#define GL_T2F_C4UB_V3F 0x2A29
#define GL_T2F_C3F_V3F 0x2A2A
#define GL_T2F_N3F_V3F 0x2A2B
#define GL_T2F_C4F_N3F_V3F 0x2A2C
#define GL_T4F_C4F_N3F_V4F 0x2A2D

4).draw call调用发送数据到GPU服务端,glArrayElement或glBegin封装后随机的glDrawXXX/glMultiDrawXXX

glDrawXXX/glMultiDrawXXX是:

glDrawElements,glMultiDrawElements(),glDrawRangeElements();顺序的glDrawArrays ,glMultiDrawArrays()

RestartIndex可用索引缓存重绘图元,遇到index值后面的索引缓存接着绘制新的。

glArrayElement需要在glBegin和glEnd之间调用,一个glArrayElement相当于用顶点索引对当前一个顶点的所有结构(存在的包括顶点颜色法向量纹理,雾坐标,边界标志)都调用一次各自的加载函数,加载一个完整顶点的的数据。

glDrawElements对一个索引缓存进行加载顶点数组,更加有效,且封装了glBegin glEnd。
GLAPI void GLAPIENTRY glDrawElements (GLenum mode, GLsizei count, GLenum type, const GLvoid *indices);
glDrawElements(GL_TRIANGLE_STRIP, NumStrips * (NumPointsPerStrip + 1), GL_UNSIGNED_SHORT, NULL);// 缓存对象使用null也可以在GPU缓存对象中取到数据。

glDrawElements 直接使用了索引缓存,且封装了glBegin glEnd 相当于:
glBegin(mode);
for(int i = 0; i < count; i++)
{
glArrayElement(indics[i]); 
}
glEnd();

glMultiDrawElements()是对多个索引缓存进行了封装,因为OGL每个索引缓存只是代表一个顶点结构里面的一个类型的数据的索引。
typedef void (GLAPIENTRY * PFNGLMULTIDRAWELEMENTSPROC) (GLenum mode, const GLsizei *count, GLenum type, const GLvoid **indices, GLsizei drawcount);

glMultiDrawElements()相当于:
for(int i = 0; i < count; i++)
{
    for(couint[i] > 0 )
   {
         glDrawElements(mode, count[i], type, indice[i]);
   }
}

glDrawRangeElements()也可以对数据进行随机访问,和glDrawElements几乎相同,就是多了索引缓存的start,end索引下标范围,有利于提高性能,但不是连续的顶点需要处理有些OGL实现会降低性能,因为处理了很多不必要的顶点。
查看OGL支持的最大顶点数量,和索引的最大值,可以用glGetIntegerV, GL_MAX_ELEMENTS_VERTICES和GL_MAX_ELEMENTS_INDICES查询。
OGL会对最近使用的顶点进行缓存,避免每次提交相同的顶点都进行渲染管道的变换和光照处理,这个是减少一个DrawCall内部要处理的顶点数。

typedef void (GLAPIENTRY * PFNGLDRAWRANGEELEMENTSPROC) (GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const GLvoid *indices);

glArrayElement,glDrawElements,glMultiDrawElements(),glDrawRangeElements()都可以对数据数组进行随机存取,但是下面的glDrawArrays, glMultiDrawArrays()只能对数组数据进行顺序访问。
glDrawArrays 对数组顶点数据进行顺序访问,函数原型
GLAPI void GLAPIENTRY glDrawArrays (GLenum mode, GLint first, GLsizei count);需要渲染的个数下标是从first数组下标,到first + count - 1的数组下标进行提交渲染。glDrawArrays相当于下面的代码:
glBegin(mode);
for(int i = 0; i < count; i++)
{
glArrayElement(first + i); 
}
glEnd();

glMultiDrawArrays()提供了对多个glDrawArrays函数的封装,类似glMultiDrawElements对glDrawElements的封装,只是glMultiDrawArrays是对多个顺序访问的数组数据传递的封装。
  glMultiDrawArrays的函数原型:
    typedef void (GLAPIENTRY * PFNGLMULTIDRAWARRAYSPROC) (GLenum mode, const GLint *first, const GLsizei *count, GLsizei drawcount);
drawcount是调用glDrawArrays的个数,first和count是多个first和count下标组成的数组。
相当于调用:
for(int i = 0; i < count; i++)
{
    for(couint[i] > 0 )
   {
         glDrawArrays(mode, first[i],count[i]);
   }
}


对于多次调用glMultiDraw绘制同一个索引缓存绘制同一个图元的情形,可以用 glPrimitiveRestartIndex()函数指定重启图元的索引下标index,当前面的绘制遇到index时则绘制结束,新的相同图元绘制从index开始。

5)Shader实例化绘制glDrawArraysInstanced类似glMultiDrawArrays,glDrawElementsInstanced类似glMultiDrawElements

OGL 3.1增加了对实例化绘制的支持,对于指定的每一组图元gl_InstanceID相应递增,该值在glsl中可以访问到。
glDrawArraysInstanced()类似glMultiDrawArrays是对数组的顺序访问,只不过要多设置gl_InstanceID值。
glDrawArraysInstanced原型: void glDrawArraysInstanced(GLenum mode,GLint first,GLsizei count, GLsizei primcount);
primcount是调用 glDrawArrays次数;first和count指定了传递给glDrawArrays数组下标的范围,每个glDrawArrays在本次调用中都是相同的,但是渲染时机不同下标也可能不同。
相当于调用:
for(int i = 0; i < primcount; i++)
{
    gl_InstanceID = i;
    glDrawArrays(mode, first,count);
}
 gl_InstanceID = 0;

glDrawElementsInstanced类似glDrawArraysInstanced(),只不过是可以随机存取的。
void glDrawElementsInstanced(    GLenum mode, GLsizei count, GLenum type,const void * indices,GLsizei primcount);

Specifies the type of the values in indices. Must be one of GL_UNSIGNED_BYTE, GL_UNSIGNED_SHORT, or GL_UNSIGNED_INT.
indices和count指定了传递给 glDrawElements的数组元素的范围。
相当于:
for(int i = 0; i < primcount; i++)
{
    gl_InstanceID = i;
    glDrawElements(mode, count, type, indicies);
}
 gl_InstanceID = 0;

2.缓存区对象,主要负责加载数据和将顶点相关寄存器关联到缓存区对象数据块,绘制还是用绘制函数。

将顶点数据直接放置在显存中,避免类似顶点数组将大量的顶点数据从CPU中的RAM经过IO传递到GPU显存中。且相比显示列表diplaylist提供了能够通过映射来修改显存中数据的能力。
OGL 1.5开始顶点数据可以放置在GPU服务器缓存区中,OGL 2.1增加了在缓存区对象中存储像素数据(纹理贴图和像素块的支持).
OGL 3.1增加了统一缓存区(uniform buffer object)以存储成块的用于着色器的统一变量数据。并不是所有的对象都是存放数据的,
纹理对象只是封装了和纹理贴图相关联的各种状态设置。顶点数组对象封装了使用顶点数组相关的状态参数。这些对象的使用可以允许我们用较少的函数调用就能够修改大量的状态设置。

利用缓存区对象加载和渲染数据的步骤:
1)创建缓存区对象glGenBuffer
void glGenBuffers(GLsizei n,GLuint * buffers);最好用OGL返回的buffers作为标识符,避免标识符冲突。
glIsBuffer可以判断一个标识符是否存在。

2)激活缓存区对象(绑定)glBindBuffer
void glBindBuffer(GLenum target,GLuint buffer);
A buffer object binding created with  glBindBuffer  remains active until a different buffer object name is bound to the same target, or until the bound buffer object is deleted with  glDeleteBuffers .证明了该类型缓存区只有一个处于激活状态。

激活(绑定)缓存区对象表示选择未来的操作(对数据进行初始化或者使用缓存区对象进行渲染)将影响那个缓存区对象。
因此可以解释了后面的缓存区加载数据,渲染数据都用NULL传入,其实就是调用当前激活的缓存区对象数据。多个缓存区对象则需要操作那个缓存区之前都要进行glBindBuffer绑定相应的缓存区对象( 该类型缓存区只有一个处于激活状态)。如果禁用缓存区对象则用glBindBuffer(0), 如果之前没有glGenBuffers那么创建一个缓存区对象。
target参数为:
GL_ARRAY_BUFFER    Vertex attributes
GL_ATOMIC_COUNTER_BUFFER    Atomic counter storage
GL_COPY_READ_BUFFER    Buffer copy source
GL_COPY_WRITE_BUFFER    Buffer copy destination
GL_DISPATCH_INDIRECT_BUFFER    Indirect compute dispatch commands
GL_DRAW_INDIRECT_BUFFER    Indirect command arguments
GL_ELEMENT_ARRAY_BUFFER    Vertex array indices
GL_PIXEL_PACK_BUFFER    Pixel read target
GL_PIXEL_UNPACK_BUFFER    Texture data source
GL_QUERY_BUFFER    Query result buffer
GL_SHADER_STORAGE_BUFFER    Read-write storage for shaders
GL_TEXTURE_BUFFER    Texture data buffer
GL_TRANSFORM_FEEDBACK_BUFFER    Transform feedback buffer
GL_UNIFORM_BUFFER    Uniform block storage
buffer参数为:缓存区对象标识符。

3)申请GPU显卡空间和初始化顶点和索引数据glBufferData
void glBufferData(GLenum target,
     GLsizeiptr size,
     const GLvoid * data,
     GLenum usage);
target的值同glBindBuffer中的target缓存区类型值,以前所有与当前绑定对象相关联的数据都将删除(缓存区类型和标识符相同)。
size是在GPU显存中分配的字节数。
data若为NULL则保留size个字节的空间供以后使用,需用glMapBuffer填充,glUnMapBuffer释放,glMapBufferRange或glCopyBufferSubData来填充。若非NULL则从CPU RAM内存中拷贝数据到GPU显存缓存区对象中。
usage表示数据的读取和写入方式,提供给OGL优化存取。
STREAM 加载一次少使用,STATIC加载一次多次使用,DYNAMIC修改多次多使用。
DRAW是表示数据从CPU RAM传递到GPU缓存区对象中用于渲染, READ表示数据从GPU缓存区对象读入到CPU中, COPY从GPU缓存区对象读取数据并作为渲染。

4)更新缓存区对象中的值
对一个连续部分的更新:
void  glBufferSubData(    GLenum      target,
     GLintptr      offset,
     GLsizeiptr      size,
     const GLvoid *      data);
target同上, 用data数据,更新缓存区对象中从offset开始size字节的数据, 如果offset + size大小超过缓存区对象大小那么产生错误。

glMapBuffer更加灵活的更新数据,就像操作数组一样,更新完后用glUnMapBuffer表示完成了对数据的更新(应该是刷新到显卡中),glMapBuffer一般用于对较大部分数据的更新。
例如:
GLfloat* vertices = (GLfloat*)glMapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY);
if (vertices == NULL) {
        exit(EXIT_FAILURE);
    }
    else {
        int i;
        int j;
        GLfloat dx = (GLfloat)(XEnd - XStart) / (NumXPoints - 1);
        GLfloat dy = (GLfloat)(YEnd - YStart) / (NumYPoints - 1);
         GLfloat* tmp = vertices;
        int n = 0;

        // 绘制NumYPoints * NumXPoints个顶点,包含了像素和顶点数据
        for (j = 0; j < NumYPoints; ++j) {
            GLfloat y = (GLfloat)(YStart + j * dy);// y最小为-0.8,最大为0.8
            for (i = 0; i < NumXPoints; ++i) {
                GLfloat x = (GLfloat)(XStart + i * dx);// x最小为-0.8,最大为0.8
                *tmp++ = color[(i + j) % 6][0]; // i,j是0到10
                *tmp++ = color[(i + j) % 6][1];
                *tmp++ = color[(i + j) % 6][2];
                *tmp++ = x;
                *tmp++ = y;
                *tmp++ = 0;
            }
        }
        //表示当前绑定缓冲区对象的更新已经完成,与glMapBuffer()结合使用
         glUnmapBuffer(GL_ARRAY_BUFFER);
        // 顶点数据加载,这里从NULL地址中加载
        glVertexPointer(3, GL_FLOAT, 6 * sizeof(GLfloat), (const GLubyte *)NULL + 3 * sizeof(GLfloat));//BUFFER_OFFSET(3 * sizeof(GLfloat))
        // 颜色数据加载,这里从NULL地址中加载
        glColorPointer(3, GL_FLOAT, 6 * sizeof(GLfloat), NULL);//BUFFER_OFFSET(0)

如果是对缓存区对象较少部分数据的更新,那么用glMapBufferRange效率更高。
void * glMapBufferRange(    GLenum target,
     GLintptr offset,
     GLsizeiptr length,
     GLbitfield access);
target同上,offset和length表示映射缓存区对象的范围, access表示访问更新方式,之前的丢弃,可以不连续访问等,详细见ogl文档。
glFlushMappedBufferRange对glMapBufferRange更新的数据进行刷新GPU缓存区对象上面的数据,参数同glMapBufferRange。
void glFlushMappedBufferRange(    GLenum target,
     GLintptr offset,
     GLsizeiptr length);

glCopyBufferSubData不需要缓存区数据拷贝到CPU内存中,就可以在缓存区之间拷贝数据。
void glCopyBufferSubData(    GLenum readTarget,
     GLenum writeTarget,
     GLintptr readOffset,
     GLintptr writeOffset,
     GLsizeiptr size);
在两个激活的readTarget和writeTarget之间拷贝,他们类型是不一样的,readOffset和writeOffset分别是起始位置,size都是他们的偏移大小 。

4)激活顶点相关寄存器和在顶点相关寄存器中关联缓存区对象数据,glVertexPointer等加载数据。用NULL指定当前激活的缓存区(没有CPU数据写入)或直接glDrawElements也是NULL传入指定为当前接货的缓存区对象。
启用顶点相关寄存器:
或者用glInterleavedArrays启用顶点相关寄存器和关联数据到顶点寄存器中。
例如:
glGenBuffers(NumVBOs, buffers);
glBindBuffer(GL_ARRAY_BUFFER, buffers[Vertices]);
glBufferData(GL_ARRAY_BUFFER, sizeof(coneVerts), coneVerts, GL_STATIC_DRAW);

glVertexPointer(3, GL_FLOAT, 0, BUFFER_OFFSET(0)); // 顶点寄存器关联缓存区对象中数据
glEnableClientState(GL_VERTEX_ARRAY);

5)用顶点数组绘制函数或者HLSL Shader绘制顶点相关寄存器数据
例如:
glDrawElements(GL_TRIANGLE_STRIP, NumStrips * (NumPointsPerStrip + 1), GL_UNSIGNED_SHORT, NULL);
NULL是将当前激活的缓存区对象中取得数据到顶点相关寄存器中,进行顶点变换和光照,绘制渲染。

6)glDeleteBuffers清除缓存区对象
void glDeleteBuffers(    GLsizei n,
     const GLuint * buffers);
物体删除不需要数据了,用glDeleteBuffers删除缓存区对象。

3.顶点数组对象VAO和缓存区对象VBO-主要负责关联缓存区数据块和顶点相关寄存器中且用顶点数组管理切换,绘制还是用绘制函数

     VAO是一个对象,其中包含一个或者更多的Vertex Buffer Objects。而VBO是Graphics Card中的一个内存缓冲区,用来保存顶点信息,颜色信息,法线信息,纹理坐标信息和索引信息等等。
     VAO在Graphics Card线性的存储几个对象信息,替代了以前发送我们需要的数据到Graphics Card上,这也是Direct3D没有立即模式情况下工作的方法, 这就意味着应用程序不需要传输数据到Graphics Card上而得到较高的性能。
     在缓存区中生成顶点数组对象名字,该名字标记可能不是连续的,但是一定是没有使用的,用顶点数组集合的默认状态来初始化。
     顶点数据加载状态的封装,方便调用(其实仅为状态索引结构的封装,关联缓存区数据块,本身并没有数据块)。
    可以用glBindVertexArray轻易的实现在绘制不同物体之间切换。
1)生成顶点数据对象标识符,可以关联缓存区对象顶点数据
enum { Cube, Cone, NumVAOs };
GLuint VAO[NumVAOs];
glGenVertexArrays(NumVAOs, VAO);
2)绑定VAO对象(创建一个新顶点数组对象并分配名字,如果之前创建过则激活,当前只能激活一个VAO任何之前激活的VAO都会失效,GL_ARRAY_BUFFER就像一个全局指针
/*Specifies the name of the vertex array to bind.

glBindVertexArray binds the vertex array object with name array. array is the name of a vertex array object previously returned from a call toglGenVertexArrays, or zero to break the existing vertex array object binding.

If no vertex array object with name array exists, one is created when array is first bound.

If the bind is successful no change is made to the state of the vertex array object, and any previous vertex array object binding is broken.
*/

glBindVertexArray(VAO[Cube]);
3)关联VAO和VBO
// 要清楚关联a buffer object和 a vertex attribute并不发生在glBindBuffer(GL_ARRAY_BUFFER),而是发生在glVertexAttribPointer();当你调用glVertexAttribPointer() ,
        // OpenGL 获取缓冲区绑定到GL_ARRAY_BUFFER 并且关联顶点属性,想象一下 GL_ARRAY_BUFFER就像一个全局指针
        glVertexPointer(3, GL_FLOAT, 0, BUFFER_OFFSET(0));
4) 激活VAO实现绘制或切换绘制
// 激活顶点数组对象,用于设置顶点数组状态,关联到制定的缓存区对象,然后绘制出来
        glBindVertexArray(VAO[i]);
        // 当有多个glDrawElements时候,就需要用glBindVertexArray绑定VAO对象,告知绘图函数调用那个顶点数组的索引缓存和顶点缓存
        glDrawElements(PrimType[i], NumElements[i], GL_UNSIGNED_BYTE, BUFFER_OFFSET(0));
5)删除顶点数组对象,它关联的缓存区对象不会被删除而是变为未绑定的
glDeleteVertexArrays(GLsize  n, GLuint * arrays);
实例代码:
#include <stdlib.h>
#include <stdio.h>
#define GLEW_STATIC
#include <GL/glew.h>
//#define FREEGLUT_STATIC
#include <GL/freeglut.h>
#define M_PI 3.1415926

#pragma comment(lib, "glew32s.lib")

#define BUFFER_OFFSET(offset) ((const GLubyte *) NULL + offset)  
#define NumberOf(array) (sizeof(array)/sizeof(array[0]))  
typedef struct {
    GLfloat x, y, z;
} vec3;
typedef struct {
    vec3 xlate; /* Translation */
    GLfloat angle;
    vec3 axis;
} XForm;
enum { Cube, Cone, NumVAOs };
GLuint VAO[NumVAOs];
GLenum PrimType[NumVAOs];
GLsizei NumElements[NumVAOs];
XForm Xform[NumVAOs] = {
    { { -2.0, 0.0, 0.0 }, 0.0,{ 0.0, 1.0, 0.0 } },
    { { 0.0, 0.0, 2.0 }, 0.0,{ 1.0, 0.0, 0.0 } }
};
GLfloat Angle = 0.0;
void
init()
{
    enum { Vertices, Colors, Elements, NumVBOs };
    GLuint buffers[NumVBOs];
    // VAO是一个对象,其中包含一个或者更多的Vertex Buffer Objects。而VBO是Graphics Card中的一个内存缓冲区,用来保存顶点信息,颜色信息,法线信息,纹理坐标信息和索引信息等等。
    //  VAO在Graphics Card线性的存储几个对象信息,替代了以前发送我们需要的数据到Graphics Card上,这也是Direct3D没有立即模式情况下工作的方法,
    // 这就意味着应用程序不需要传输数据到Graphics Card上而得到较高的性能。
    // 在缓存区中生成顶点数组对象名字,该名字标记可能不是连续的,但是一定是没有使用的,用顶点数组集合的默认状态来初始化。
    // generate vertex array object names
    glGenVertexArrays(NumVAOs, VAO);

    {
        GLfloat cubeVerts[][3] = {
            { -1.0, -1.0, -1.0 },
            { -1.0, -1.0, 1.0 },
            { -1.0, 1.0, -1.0 },
            { -1.0, 1.0, 1.0 },
            { 1.0, -1.0, -1.0 },
            { 1.0, -1.0, 1.0 },
            { 1.0, 1.0, -1.0 },
            { 1.0, 1.0, 1.0 },
        };
        GLfloat cubeColors[][3] = {
            { 0.0, 0.0, 0.0 },
            { 0.0, 0.0, 1.0 },
            { 0.0, 1.0, 0.0 },
            { 0.0, 1.0, 1.0 },
            { 1.0, 0.0, 0.0 },
            { 1.0, 0.0, 1.0 },
            { 1.0, 1.0, 0.0 },
            { 1.0, 1.0, 1.0 },
        };
        GLubyte cubeIndices[] = {
            0, 1, 3, 2,
            4, 6, 7, 5,
            2, 3, 7, 6,
            0, 4, 5, 1,
            0, 2, 6, 4,
            1, 5, 7, 3
        };
        // 绑定顶点数组对象名字,当前的顶点数组对象只能激活一个.
        // glBindVertexArray绑定VAO,一旦VAO绑定后,使glGenBuffers 创建一个Vertex Buffer Object, 当然仍然需要使用glBindBuffer绑定VBO;
        // 使用glBufferData来初始化和用刚VAO创建的数据分配数据给VBO,再告诉VBO的数据是从VAO而来,需要清理Vertex Attributr Array和Vertex Array Object
        // bind a vertex array object,
        // array is the name of a vertex array object previously returned from a call to glGenVertexArrays, 
        // or zero to break the existing vertex array object binding.
        //  If the bind is successful no change is made to the state of the vertex array object, and any previous vertex array object binding is broken.
        glBindVertexArray(VAO[Cube]);

        glGenBuffers(NumVBOs, buffers);
        // When a buffer object is bound to a target, the previous binding for that target is automatically broken.
        glBindBuffer(GL_ARRAY_BUFFER, buffers[Vertices]);
        glBufferData(GL_ARRAY_BUFFER, sizeof(cubeVerts),
            cubeVerts, GL_STATIC_DRAW);
        // 要清楚关联a buffer object和 a vertex attribute并不发生在glBindBuffer(GL_ARRAY_BUFFER),而是发生在glVertexAttribPointer();当你调用glVertexAttribPointer() ,
        // OpenGL 获取缓冲区绑定到GL_ARRAY_BUFFER 并且关联顶点属性,想象一下GL_ARRAY_BUFFER就像一个全局指针。
        glVertexPointer(3, GL_FLOAT, 0, BUFFER_OFFSET(0));
        glEnableClientState(GL_VERTEX_ARRAY);

        // 前面的GL_ARRAY_BUFFER绑定失效,GL_ARRAY_BUFFER就像一个全局指针
        glBindBuffer(GL_ARRAY_BUFFER, buffers[Colors]);
        glBufferData(GL_ARRAY_BUFFER, sizeof(cubeColors),
            cubeColors, GL_STATIC_DRAW);
        // 取得当前激活的缓存区对象偏移
        glColorPointer(3, GL_FLOAT, 0, BUFFER_OFFSET(0));
        glEnableClientState(GL_COLOR_ARRAY);

        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,buffers[Elements]);
        glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(cubeIndices), cubeIndices, GL_STATIC_DRAW);
        PrimType[Cube] = GL_QUADS;
        NumElements[Cube] = NumberOf(cubeIndices);

        //glBufferSubData()
    }

    {
        int i, idx;
        float dTheta;
#define NumConePoints 36  
        /* We add one more vertex for the cone's apex */
        GLfloat coneVerts[NumConePoints + 1][3] = {
            { 0.0, 0.0, 1.0 }
        };
        GLfloat coneColors[NumConePoints + 1][3] = {
            { 1.0, 1.0, 1.0 }
        };
        GLubyte coneIndices[NumConePoints + 1];
        dTheta = 2 * M_PI / (NumConePoints - 1);
        idx = 1;
        for (i = 0; i < NumConePoints; ++i, ++idx) {
            float theta = i*dTheta;
            coneVerts[idx][0] = cos(theta);
            coneVerts[idx][1] = sin(theta);
            coneVerts[idx][2] = 0.0;
            coneColors[idx][0] = cos(theta);
            coneColors[idx][1] = sin(theta);
            coneColors[idx][2] = 0.0;
            coneIndices[idx] = idx;
        }
        glBindVertexArray(VAO[Cone]);

        glGenBuffers(NumVBOs, buffers);
        glBindBuffer(GL_ARRAY_BUFFER, buffers[Vertices]);
        glBufferData(GL_ARRAY_BUFFER, sizeof(coneVerts),
            coneVerts, GL_STATIC_DRAW);
        glVertexPointer(3, GL_FLOAT, 0, BUFFER_OFFSET(0));
        glEnableClientState(GL_VERTEX_ARRAY);

        glBindBuffer(GL_ARRAY_BUFFER, buffers[Colors]);
        glBufferData(GL_ARRAY_BUFFER, sizeof(coneColors),
            coneColors, GL_STATIC_DRAW);
        glColorPointer(3, GL_FLOAT, 0, BUFFER_OFFSET(0));
        glEnableClientState(GL_COLOR_ARRAY);

        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,
            buffers[Elements]);
        glBufferData(GL_ELEMENT_ARRAY_BUFFER,
            sizeof(coneIndices), coneIndices, GL_STATIC_DRAW);
        PrimType[Cone] = GL_TRIANGLE_FAN;
        NumElements[Cone] = NumberOf(coneIndices);
    }
    //glEnable(GL_DEPTH_TEST);
}


void mouse(int button, int state, int x, int y) {
    switch (button)
    {
    case GLUT_LEFT_BUTTON:
        if (state == GLUT_DOWN) {
            glutIdleFunc(NULL);
        }
        break;
    case GLUT_RIGHT_BUTTON:
        if (state == GLUT_DOWN) {
            glutIdleFunc(NULL);
        }
        break;
    default:
        break;
    }
}

void display()
{
    int i;
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glPushMatrix();
    glRotatef(Angle, 0.0, 1.0, 0.0);
    for (i = 0; i < NumVAOs; ++i) {
        glPushMatrix();
        glTranslatef(Xform[i].xlate.x, Xform[i].xlate.y,
            Xform[i].xlate.z);
        glRotatef(Xform[i].angle, Xform[i].axis.x,
            Xform[i].axis.y, Xform[i].axis.z);
        // 激活顶点数组对象,用于设置顶点数组状态,关联到制定的缓存区对象,然后绘制出来
        glBindVertexArray(VAO[i]);
        // 当有多个glDrawElements时候,就需要用glBindVertexArray绑定VAO对象,告知绘图函数调用那个顶点数组的索引缓存和顶点缓存
        glDrawElements(PrimType[i], NumElements[i], GL_UNSIGNED_BYTE, BUFFER_OFFSET(0));
        glPopMatrix();
    }
    glPopMatrix();
    glutSwapBuffers();
}

int main(int argc, char** argv)
{
    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB);
    glutInitWindowSize(250, 250);

    glutInitWindowPosition(100, 100);
    glutInitContextVersion(3, 1);

    glutCreateWindow("hello");
    glewInit();
    init();
    int nAttriNumS, nAttriNumC= 0;
    glGetIntegerv(GL_MAX_ATTRIB_STACK_DEPTH, &nAttriNumS);
    glGetIntegerv(GL_MAX_ATTRIB_STACK_DEPTH, &nAttriNumC);
    glPopAttrib();
    glutDisplayFunc(display);
    //glutReshapeFunc(reshape);
    glutMouseFunc(mouse);
    //glutMotionFunc(move);
    //glutKeyboardFunc(keyboardfunc);
    glutMainLoop();

    return 0;
}

4.属性组-渲染状态组

我们可以设置和查询单个的渲染状态,也可以用命令保存和恢复一组相关的命令状态。
例如:GL_LINE_BIT属性包含了直线宽度,GL_LINE_STIPPLE启用状态,直线点画模式,直线点画重复计数和GL_LINE_SMOOTH启用状态。 GL_LIGHTING_BIT表示所有与光照有关的状态变量,包括当前的材料表面颜色,环境光,散射光,镜面光和发射光(光照的Material),被启用的光源列表以及聚灯光的方向。

OGL维护两个属性堆栈:一个是GPU端的属性(渲染状态)堆栈,一个是CPU客户端的属性(渲染状态)堆栈。
保存和恢复GPU中的一组渲染状态:用glPushAttrib和glPopAttrib
保存和恢复CPU一组渲染状态用:glPushClientAttrib和glPopClientAttrib
属性堆栈的深度可以用glGetIntegerv的GL_MAX_ATTRIB_STACK_DEPTH和GL_MAX_CLIENT_ATTRIB_STACK_DEPTH来查询。
有些属性会存在多个属性组中,但是也不影响,因为恢复的是上一个保存的属性组 。

void glPushAttrib(  GLbitfield mask);
void glPopClientAttrib( void); // 恢复上一次保存的
void glPushClientAttrib(    GLbitfield      mask);
void glPopClientAttrib(         void);
更多参数见:
https://www.opengl.org/sdk/docs/man2/xhtml/glPushAttrib.xml

5.创建多边形网格的技巧

1)要使用三角形,因为四边形不能保证在同一个平面上,绘制会有问题。
2)三角形或多边形的环绕方向要一致。
3)在显示速度和图像质量之间权衡,必要使用使用抗锯齿,使用LOD技术。
4)边缘使用更精细的划分比在多边形内部精细划分有效得多,表面法向量和观察向量垂直也就是向量点积为0时就是边缘。
5)避免模型中出现T交叉,都用独立的三角形表示,避免出现间歇性开裂。
6)如果创建闭合的多边形,确保多边形的首尾连接,而不是用近似。
曲面细分 技术,一般使用递归细分,细分一次绘制4个三角形(或者一个),达到设定的细分深度值或曲率条件则停止细分。
DX11就在驱动层支持了 Tessellation技术。
OGL 4.0 也支持了 Tessellation Shader着色器。


最后

以上就是活泼犀牛为你收集整理的OGL绘制相关函数总结1的全部内容,希望文章能够帮你解决OGL绘制相关函数总结1所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(71)

评论列表共有 0 条评论

立即
投稿
返回
顶部