主题:[原创]OpenGL入门学习——第十二课
eastcowboy
[专家分:25370] 发布于 2007-10-07 11:14:00
大家好。现在因为参加工作的关系,又是长时间没有更新。趁着国庆的空闲,总算是又写出了一课。我感觉入门的知识已经快要介绍完毕,这课之后再有一课,就可以告一段落了。以后我可能会写一些自己在这方面的体会,做一份进阶课程。
现在即将放出的是第十二课的内容。
首先还是以前课程的连接:
[url=http://www.programfan.com/club/showbbs.asp?id=184355]第一课,编写第一个OpenGL程序[/url]
[url=http://www.programfan.com/club/showbbs.asp?id=184525]第二课,绘制几何图形[/url]
[url=http://www.programfan.com/club/showbbs.asp?id=184769]第三课,绘制几何图形的一些细节问题[/url]
[url=http://www.programfan.com/club/showbbs.asp?id=185032]第四课,颜色的选择[/url]
[url=http://www.programfan.com/club/showbbs.asp?id=196017]第五课,三维的空间变换[/url]
[url=http://www.programfan.com/club/showbbs.asp?id=196231]第六课,动画的制作[/url]
[url=http://www.programfan.com/club/showbbs.asp?id=218828]第七课,使用光照来表现立体感[/url]
[url=http://www.programfan.com/club/showbbs.asp?id=219518]第八课,使用显示列表[/url]
[url=http://www.programfan.com/club/showbbs.asp?id=224877]第九课,使用混合来实现半透明效果[/url]
[url=http://www.programfan.com/club/post-227694.html]第十课,BMP文件与像素操作[/url]
[url=http://www.programfan.com/club/post-244703.html]第十一课,纹理的使用入门[/url]
第十二课,OpenGL片断测试 ——→ [color=0000FF]本次课程的内容[/color]
片断测试其实就是测试每一个像素,只有通过测试的像素才会被绘制,没有通过测试的像素则不进行绘制。OpenGL提供了多种测试操作,利用这些操作可以实现一些特殊的效果。
我们在前面的课程中,曾经提到了“深度测试”的概念,它在绘制三维场景的时候特别有用。在不使用深度测试的时候,如果我们先绘制一个距离较近的物体,再绘制距离较远的物体,则距离远的物体因为后绘制,会把距离近的物体覆盖掉,这样的效果并不是我们所希望的。
如果使用了深度测试,则情况就会有所不同:每当一个像素被绘制,OpenGL就记录这个像素的“深度”(深度可以理解为:该像素距离观察者的距离。深度值越大,表示距离越远),如果有新的像素即将覆盖原来的像素时,深度测试会检查新的深度是否会比原来的深度值小。如果是,则覆盖像素,绘制成功;如果不是,则不会覆盖原来的像素,绘制被取消。这样一来,即使我们先绘制比较近的物体,再绘制比较远的物体,则远的物体也不会覆盖近的物体了。
实际上,只要存在深度缓冲区,无论是否启用深度测试,OpenGL在像素被绘制时都会尝试将深度数据写入到缓冲区内,除非调用了glDepthMask(GL_FALSE)来禁止写入。这些深度数据除了用于常规的测试外,还可以有一些有趣的用途,比如绘制阴影等等。
除了深度测试,OpenGL还提供了剪裁测试、Alpha测试和模板测试。
[color=0000FF]因为论坛开始支持附件,现在把程序源代码和所使用的图片一起打包上传[/color]
=====================未完,请勿跟帖=====================
最后更新于:2007-10-07 11:33:00
回复列表 (共19个回复)
11 楼
bruceteen [专家分:42660] 发布于 2007-10-07 19:21:00
不好意思,忘了不能跟帖
12 楼
小小C [专家分:4570] 发布于 2007-10-07 20:16:00
真的搞不懂,如何创建顶点缓冲和索引缓冲呢?顶点的骨骼权重如何设置?
13 楼
eastcowboy [专家分:25370] 发布于 2007-10-08 08:29:00
[quote]不好意思,忘了不能跟帖[/quote]
呵呵,本课已经发完,可以跟贴了呀。
[quote]真的搞不懂,如何创建顶点缓冲和索引缓冲呢?顶点的骨骼权重如何设置?[/quote]
顶点缓冲和索引缓冲是OpenGL 1.5版本所提供的功能,因此首先检查OpenGL版本是否达到1.5。因为Windows仅直接支持OpenGL 1.1的函数,更高版本的函数应该使用wglGetProcAddress函数来获得这些函数的指针,然后利用函数指针进行间接调用。而且这些函数所需要使用的常量在一个叫做glext.h的头文件中定义,可在google上搜索下载该文件的最新版本。
Vertex Buffer Object(顶点缓冲对象)所涉及的函数:
glGenBuffers:分配缓冲对象编号
glBindBuffer:绑定缓冲。可以绑定顶点缓冲和索引缓冲。
glBufferData,glBufferSubData:修改缓冲中的全部数据或部分数据。第一次修改时应该使用glBufferData,以后可以选择使用glBufferData或glBufferSubData。
glMapBuffer,glUnmapBuffer:glMapBuffer锁定缓冲数据并把缓冲数据映射到内存,然后可以用一个指针进行灵活的访问和修改,修改完成后利用glUnmapBuffer确认修改并解除锁定。
骨骼权重这个概念我并不怎么清楚,这个也就不能回答了。
14 楼
eastcowboy [专家分:25370] 发布于 2007-10-08 08:30:00
八点半上班,不好意思。
中午再给个顶点缓冲/索引缓冲的例子吧。
15 楼
eastcowboy [专家分:25370] 发布于 2007-10-08 12:32:00
顶点缓冲/索引缓冲使用示例。
注意:该程序使用C语言编写(不是C++)。使用了两个工具包,GLUT和GLEE。其中:GLUT的安装方法在本课程的第一课里面有描述。GLEE实际上就是两个文件glee.h和glee.c,从网上下载这两个文件的最新版本并放到工程中,和下面的代码一起编译。
代码过长,分开发送。
[code=c]#define WindowWidth 512
#define WindowHeight 512
#define WindowTitle "OpenGL -- Vertex Buffer Objects 测试"
#include "GLee.h"
#include <GL/glut.h>
#include <stdio.h>
static GLfloat gf_RotateAngle = 0.0f;
static int gi_Rotating = 1;
void display(void)
{
#define length_half (1.0f)
// 混合数组,用六个值表示一个顶点(前三项为颜色,后三项为顶点坐标),共8个顶点
static GLfloat vertex_list[6*8] =
{
0.0f, 0.0f, 0.0f, -length_half, -length_half, -length_half,
0.0f, 0.0f, 1.0f, -length_half, -length_half, length_half,
0.0f, 1.0f, 0.0f, -length_half, length_half, -length_half,
0.0f, 1.0f, 1.0f, -length_half, length_half, length_half,
1.0f, 0.0f, 0.0f, length_half, -length_half, -length_half,
1.0f, 0.0f, 1.0f, length_half, -length_half, length_half,
1.0f, 1.0f, 0.0f, length_half, length_half, -length_half,
1.0f, 1.0f, 1.0f, length_half, length_half, length_half
};
// 索引数组,每四个顶点表示一个平面,共六个平面
static GLuint index_list[4*6] =
{
0, 1, 3, 2,
4, 5, 7, 6,
0, 2, 6, 4,
1, 3, 7, 5,
0, 1, 5, 4,
2, 3, 7, 6
};
// 标记本函数是否为第一次调用,如果是,可进行初始化
static int isFirstCall = 1;
#undef length_half
[/code]
16 楼
eastcowboy [专家分:25370] 发布于 2007-10-08 12:33:00
接楼上。
[code=c] // 清除屏幕
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glEnable(GL_DEPTH_TEST);
// 设置视角
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluPerspective(60, 1, 1, 10);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
gluLookAt(3, 3, 3, 0, 0, 0, 0, 1, 0);
// 旋转
glRotatef(gf_RotateAngle, 0.0f, 1.0f, 1.0f);
// 初始化GLEE,GLEE会自动读取动态连接库中的1.1以上版本OpenGL函数(如果有的话)
GLeeInit();
// 根据OpenGL所支持VBO的情况,有三种方式执行渲染
if( _GLEE_VERSION_1_5 ) // 支持OpenGL 1.5,使用标准的VBO函数
{
if( isFirstCall )
{
GLuint iVertexBuffer = 0;
GLuint iIndexBuffer = 0;
// 设置顶点缓冲
glGenBuffers(1, &iVertexBuffer);
glBindBuffer(GL_ARRAY_BUFFER, iVertexBuffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertex_list),
vertex_list, GL_STATIC_DRAW);
// 设置索引缓冲
glGenBuffers(1, &iIndexBuffer);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, iIndexBuffer);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(index_list),
index_list, GL_STATIC_DRAW);
// 其它设置与顶点数组的设置方式类似,只有在设置指针时不同
// 不要直接使用指针,而使用偏移地址
// vertex_list中的数据已经被保存到iVertexBuffer所对应的缓冲对象中,因此使用零作为偏移地址
// 如果缓冲对象中有很多杂乱的数据,则通过指定不同的偏移地址即可选择不同数据,不需要重新绑定
glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_COLOR_ARRAY);
glInterleavedArrays(GL_C3F_V3F, 0, 0);
}
// 不要直接使用指针,而使用偏移地址
glDrawElements(GL_QUADS, 24, GL_UNSIGNED_INT, 0);
}
else if( _GLEE_ARB_vertex_buffer_object ) // 不支持OpenGL 1.5,但以ARB扩展的形式支持VBO
{
// 与标准形式的VBO几乎相同
// 只是函数名有无ARB后缀,常量名有无_ARB后缀这一点区别
if( isFirstCall )
{
GLuint iVertexBuffer = 0;
GLuint iIndexBuffer = 0;
glGenBuffersARB(1, &iVertexBuffer);
glBindBufferARB(GL_ARRAY_BUFFER_ARB, iVertexBuffer);
glBufferDataARB(GL_ARRAY_BUFFER_ARB, sizeof(vertex_list),
vertex_list, GL_STATIC_DRAW_ARB);
glGenBuffersARB(1, &iIndexBuffer);
glBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB, iIndexBuffer);
glBufferDataARB(GL_ELEMENT_ARRAY_BUFFER_ARB, sizeof(index_list),
index_list, GL_STATIC_DRAW_ARB);
glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_COLOR_ARRAY);
glInterleavedArrays(GL_C3F_V3F, 0, 0);
}
glDrawElements(GL_QUADS, 24, GL_UNSIGNED_INT, 0);
}
else // 不支持VBO,使用Vertex Array代替
{
if( isFirstCall )
{
printf("your system does not support the VBO.\n");
glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_COLOR_ARRAY);
glInterleavedArrays(GL_C3F_V3F, 0, vertex_list);
}
glDrawElements(GL_QUADS, 24, GL_UNSIGNED_INT, index_list);
}
// 交换缓冲
glutSwapBuffers();
isFirstCall = 0;
}
void idle(void)
{
if( gi_Rotating )
{
gf_RotateAngle += 0.1f;
if( gf_RotateAngle >= 360.0f )
gf_RotateAngle = 0.0f;
}
display();
}
void keyboard(unsigned char c, int x, int y)
{
gi_Rotating = !gi_Rotating;
}
int main(int argc, char* argv[])
{
// GLUT初始化
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH);
glutInitWindowPosition(100, 100);
glutInitWindowSize(WindowWidth, WindowHeight);
glutCreateWindow(WindowTitle);
glutDisplayFunc(&display);
glutIdleFunc(&idle);
glutKeyboardFunc(&keyboard);
// 开始显示
glutMainLoop();
return 0;
}[/code]
17 楼
eastcowboy [专家分:25370] 发布于 2007-10-08 12:56:00
顶点缓冲对象(Vertex Buffer Object, VBO)的使用步骤如下:
1、用glGenBuffers分配缓冲对象编号,该函数的使用方法与分配纹理对象编号类似。
2、用glBindBuffer绑定缓冲对象,可以用GL_ARRAY_BUFFER来绑定到顶点缓冲,也可以用GL_ELEMENT_ARRAY_BUFFER来绑定到索引缓冲。
3、用glBufferData指定缓冲中的数据,需要分别指定顶点缓冲的数据和索引缓冲的数据。注意该函数的最后一个参数,指明了该数据的用途,这个用途将帮助OpenGL决定如何进行优化。其中GL_STATIC_DRAW表示数据一旦确定,几乎不会更改,而且数据是用于绘制的。
成功使用以上三个函数后,很多本来接受指针的OpenGL函数在用法上就会发生变化。例如glColor3fv函数,本来接受一个指针,该指针指向三个连续的GLfloat类型的值,用于确定颜色。但因为现在绑定了GL_ARRAY_BUFFER,所以该函数接收一个偏移值offset,表示从已经绑定的缓冲中第offset个字节开始,取三个GLfloat类型的值,用于确定颜色。(注意:虽然偏移值是一个整数,但是你还是需要将它强制转换为glColor3fv所接受的指针类型,以确保编译能够顺利完成。)
在OpenGL 1.1版本中,提供了“顶点数组”的功能,可以把很多顶点数据放到数组中,然后通过调用很少的函数就完成绘制,而不必像OpenGL 1.0那样使用glBegin, glEnd以及大量的glColor*, glNormal*, glTexCoord*, glVertex*等函数。这些功能都是通过指针完成的。因此在绑定了顶点缓冲后,也可以用缓冲中的数据和指定偏移值的方式来代替原来的数组。数组是保存在内存中的,而缓冲数据则有可能是直接保存在显卡上,因此有望得到性能的优化。
本程序绘制一个旋转的立方体,按任意键可以开启/关闭旋转。
利用一个数组保存立方体中八个顶点的颜色和坐标,利用一个数组表示立方体六个面中每一面所包含的顶点的索引,然后利用顶点缓冲和顶点数组联合进行绘制。可以看到,除了第一次初始化外,以后只需要调用glDrawElements就可以完成绘制立方体所需要的所有动作。
程序有三种实现。当OpenGL版本为1.5或以上时,直接使用顶点缓冲对象;当OpenGL版本不足1.5,但支持ARB扩展形式的顶点缓冲对象时(此时也需要OpenGL 1.4版本),使用该形式的顶点缓冲对象;如果以上两者都不满足,则只好放弃使用顶点缓冲对象,而直接使用顶点数组。最后一种方式在性能上将会略低于前两种。
我测试用的是Intel的集成显卡,支持OpenGL 1.4以及ARB扩展形式的顶点缓冲对象。因此后两种方式我都测试并正确运行。至于第一种方式,就拜托有独立显卡(可能至少需要Geforce4 MX 440级别的显卡并安装最新驱动)的朋友去测试了。
18 楼
daoxiangcun [专家分:0] 发布于 2008-10-17 17:14:00
十分感谢eastcowboy的文章,对我们这些新手来说讲得深入浅出。
另外,eastcowboy能不能推荐一到两本自己认为比较好的进阶教程啊,谢谢!
19 楼
myhousepoor [专家分:0] 发布于 2009-03-08 23:16:00
收益匪浅,非常感谢,
我来回复