3.An OpenGL Toolbox
声明:该代码来自:Computer Graphics Through OpenGL From Theory to Experiments,仅用作学习参考
3.1 Vertex Arrays and Their Drawing Commands
顶点数组及其绘制命令:将几何数据存储在一个位置,方便用户访问。
3.2 Vertex Buffer Objects
顶点缓存对象:在图形服务器上存储与顶点相关的数据,以节省客户机到服务器的传输时间。
opengl客户端-服务器模型意味着每次服务器GPU需要顶点数据, 例如坐标,颜色或诸如此类的数据来执行,例如,调用glDrawElements(), 它必须从客户端CPU中获取。
在PC上,数据通过连接CPU(持有应用程序和数据的客户端)到GPU(图形处理单元,作为绘图的服务器)的总线的传输。通过总线访问数据通常要比本地访问慢很多倍。
为了提高效率,对象允许程序员显式地要求将一些特定的数据集(通常是与顶点相关的数据集,例如顶点数组)从客户机CPU传送到服务器GPU并存储在服务器GPU上以供将来使用。
存储顶点数据的网格对象,这些对象被称为顶点网格对象(VBO,Vertex Buffer Object)
例子:绘制一个环形(Annulus)
与VBO相关的代码
#define VERTICES 0
#define INDICES 1
// Begin globals.
// Vertex co-ordinate vectors.
//vertices[]和colors[]存储在buffer[VERTICES]中
static float vertices[] =
{
30.0, 30.0, 0.0,
10.0, 10.0, 0.0,
70.0, 30.0, 0.0,
90.0, 10.0, 0.0,
70.0, 70.0, 0.0,
90.0, 90.0, 0.0,
30.0, 70.0, 0.0,
10.0, 90.0, 0.0
};
// Vertex color vectors.
static float colors[] =
{
0.0, 0.0, 0.0,
1.0, 0.0, 0.0,
0.0, 1.0, 0.0,
0.0, 0.0, 1.0,
1.0, 1.0, 0.0,
1.0, 0.0, 1.0,
0.0, 1.0, 1.0,
1.0, 0.0, 0.0
};
// Triangle strip vertex indices in order.
static unsigned int stripIndices[] = { 0, 1, 2, 3, 4, 5, 6, 7, 0, 1 }; //存储在buffer[INDICES]中
static unsigned int buffer[2]; // Array of buffer ids.
// End globals.
// Drawing routine.
void drawScene(void)
{
glClear(GL_COLOR_BUFFER_BIT);
// Get a pointer to the vertex buffer.
// float* bufferData = (float*)glMapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY);
// Randomly change the color values.
// for (int i = 0; i < sizeof(colors) / sizeof(float); i++)
// bufferData[sizeof(vertices) / sizeof(float) + i] = (float)rand() / (float)RAND_MAX;
// Release the vertex buffer.
// glUnmapBuffer(GL_ARRAY_BUFFER);
// Draw square annulus.
glDrawElements(GL_TRIANGLE_STRIP, 10, GL_UNSIGNED_INT, 0);
glutSwapBuffers();
}
// Initialization routine.
void setup(void)
{
// 设置清除颜色为白色
glClearColor(1.0, 1.0, 1.0, 0.0);
// 生成两个缓冲区对象的ID
glGenBuffers(2, buffer);
//将指定的缓冲区对象绑定到目标缓冲区类型
//目标缓冲区类型GL_ARRAY_BUFFER
//buffer[VERTICES]是之前通过glGenBuffers生成的缓冲区对象ID之一
glBindBuffer(GL_ARRAY_BUFFER, buffer[VERTICES]); //buffer[VERTICES]存储顶点的坐标和颜色
//为当前绑定到GL_ARRAY_BUFFER的缓冲区对象分配存储空间
//sizeof(vertices) + sizeof(colors)指定了缓冲区的大小,即顶点数据和颜色数据的总和
//第三个参数NULL表示我们只预留空间,不立即提供数据
//GL_STATIC_DRAW是一个提示,告诉OpenGL我们打算如何使用这个数据。GL_STATIC_DRAW表示数据将不会被频繁更改,主要用于静态绘制
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices) + sizeof(colors), NULL, GL_STATIC_DRAW);
//将顶点坐标数据复制到当前绑定的顶点缓冲区对象的前半部分
//GL_ARRAY_BUFFER指定了目标缓冲区类型
//0表示数据复制的起始偏移量,即从缓冲区的起始位置开始复制
//sizeof(vertices)指定了要复制的数据大小,即顶点坐标数据的大小
//vertices是指向顶点坐标数据的指针(数组名是第一个元素的地址)
glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(vertices), vertices);
//将顶点颜色数据复制到当前绑定的顶点缓冲区对象的后半部分。
//GL_ARRAY_BUFFER指定了目标缓冲区类型。
//sizeof(vertices)表示数据复制的起始偏移量,即从缓冲区的中间位置(顶点坐标数据之后)开始复制。
//sizeof(colors)指定了要复制的数据大小,即顶点颜色数据的大小。
//colors是指向顶点颜色数据的指针(数组名是第一个元素的地址)
glBufferSubData(GL_ARRAY_BUFFER, sizeof(vertices), sizeof(colors), colors);
//将指定的缓冲区对象绑定到目标缓冲区类型GL_ELEMENT_ARRAY_BUFFER。
//buffer[INDICES]是之前通过glGenBuffers生成的缓冲区对象ID之一。
//绑定后,所有对GL_ELEMENT_ARRAY_BUFFER的操作都会影响到这个缓冲区对象
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer[INDICES]);//buffer[INDICES]顺序存储顶点(包含顶点坐标和颜色)的下标
//当前绑定到GL_ELEMENT_ARRAY_BUFFER的缓冲区对象分配存储空间并初始化数据。
//GL_ELEMENT_ARRAY_BUFFER指定了目标缓冲区类型。
//sizeof(stripIndices)指定了要复制的数据大小,即索引数据的大小。
//stripIndices是指向索引数据的指针。
//最后一个参数GL_STATIC_DRAW是一个提示,告诉OpenGL我们打算如何使用这个数据。GL_STATIC_DRAW表示数据将不会被频繁更改,主要用于静态绘制
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(stripIndices), stripIndices, GL_STATIC_DRAW);
// 启用顶点数组和颜色数组
//启用顶点数组功能。启用后,OpenGL将使用顶点数组中的数据进行绘制。GL_VERTEX_ARRAY指定了要启用的顶点数组类型,即顶点坐标数组
glEnableClientState(GL_VERTEX_ARRAY);
//启用颜色数组功能。启用后,OpenGL将使用颜色数组中的数据进行绘制。GL_COLOR_ARRAY指定了要启用的顶点数组类型,即顶点颜色数组
glEnableClientState(GL_COLOR_ARRAY);
//指定顶点指针,指向顶点数据的起始位置
//3表示每个顶点有3个分量(x, y, z)。
//GL_FLOAT表示每个分量的数据类型是浮点数。
//0表示顶点之间的间隔(步幅),这里为0表示顶点数据是紧密排列的。
//最后一个0表示顶点数据在缓冲区中的起始位置偏移量。
glVertexPointer(3, GL_FLOAT, 0, 0);
//指定颜色指针,指向颜色数据的起始位置
//3表示每个颜色有3个分量(r, g, b)。
//GL_FLOAT表示每个分量的数据类型是浮点数。
//0表示颜色之间的间隔(步幅),这里为0表示颜色数据是紧密排列的。
//(void *)(sizeof(vertices))表示颜色数据在缓冲区中的起始位置偏移量,这里偏移量为sizeof(vertices),即顶点数据之后的位置。
glColorPointer(3, GL_FLOAT, 0, (void *)(sizeof(vertices)));
// 设置定时器函数,每5毫秒调用一次animate函数
glutTimerFunc(5, animate, 1);
}
3.3 Vertex Array Objects
顶点数组对象:封装了一组定义对象顶点数组的调用。
一个有许多对象的繁忙场景,每个对象都用自己的顶点数组编码,可能也在VBO中,可能需要在这些数组和数组集之间多次切换,导致诸如glBindBuffer()和glVertexPointer()之类的调用激增。
从3.0版本开始,OpenGL提供了一种简洁的机制来处理这个问题:顶点数组对象(VAO)是一个容器,用于保存指定一个或多个顶点数组的所有调用。因此,一旦所有这些指定特定对象顶点数组的调用都与VAO相关联,只需要在绘制对象之前激活该VAO;换句话说,可以认为VAO封装了与对象相关的存储状态。
VBO和VAO的区别
VBO(顶点缓冲对象):
• VBO是一个缓冲区对象,用于存储顶点数据(如顶点坐标、颜色、法线、纹理坐标等)。
• VBO将顶点数据从CPU内存传输到GPU内存,以便GPU可以高效地访问这些数据进行渲染。
• 可以通过glGenBuffers生成VBO,通过glBindBuffer绑定VBO,通过glBufferData或glBufferSubData将数据传输到VBO
VAO(顶点数组对象):
• VAO是一个对象,用于存储与顶点数组相关的状态,包括VBO的绑定状态和顶点属性指针。
• VAO本身不存储顶点数据,而是存储顶点数据的布局和格式信息。
• 通过VAO,可以一次性绑定所有相关的VBO和顶点属性指针,简化了渲染时的状态设置。
• 可以通过glGenVertexArrays生成VAO,通过glBindVertexArray绑定VAO。
• VAO管理和记录VBO的绑定状态和顶点属性指针。
• 当绑定一个VAO时,所有对VBO的绑定和顶点属性指针的设置都会记录在该VAO中。
• 通过绑定不同的VAO,可以快速切换不同的顶点数据和布局,而无需重新设置VBO和顶点属性指针。
例子:绘制一个环形(Annulus)和一个三角形(Triangle)
与VAO相关的代码
#define VERTICES 0
#define INDICES 1
#define ANNULUS 0
#define TRIANGLE 1
// Begin globals.
// Vertex co-ordinate vectors for the annulus.环
static float vertices1[] =
{
30.0, 30.0, 0.0,
10.0, 10.0, 0.0,
70.0, 30.0, 0.0,
90.0, 10.0, 0.0,
70.0, 70.0, 0.0,
90.0, 90.0, 0.0,
30.0, 70.0, 0.0,
10.0, 90.0, 0.0
};
// Vertex color vectors for the annulus.环
static float colors1[] =
{
0.0, 0.0, 0.0,
1.0, 0.0, 0.0,
0.0, 1.0, 0.0,
0.0, 0.0, 1.0,
1.0, 1.0, 0.0,
1.0, 0.0, 1.0,
0.0, 1.0, 1.0,
1.0, 0.0, 0.0
};
// Vertex co-ordinate vectors for the triangle.三角形
static float vertices2[] =
{
40.0, 40.0, 0.0,
60.0, 40.0, 0.0,
60.0, 60.0, 0.0
};
// Vertex color vectors for the triangle.三角形
static float colors2[] =
{
0.0, 1.0, 1.0,
1.0, 0.0, 0.0,
0.0, 1.0, 0.0
};
// Triangle strip vertex indices in order.
static unsigned int stripIndices[] = { 0, 1, 2, 3, 4, 5, 6, 7, 0, 1 };
static unsigned int buffer[2]; // Array of buffer ids.
static unsigned int vao[2]; // Array of VAO ids.
// End globals.
// Drawing routine.
void drawScene(void)
{
glClear(GL_COLOR_BUFFER_BIT);
// Draw annulus.
//绑定存储环形顶点数组和缓冲区的VAO。
glBindVertexArray(vao[ANNULUS]);
//使用顶点索引绘制环形
//GL_TRIANGLE_STRIP表示使用三角形条带绘制,
//10表示绘制10个顶点,GL_UNSIGNED_INT表示索引数据类型为无符号整数,0表示索引缓冲区的起始位置
glDrawElements(GL_TRIANGLE_STRIP, 10, GL_UNSIGNED_INT, 0);
// Draw triangle.
//绑定存储三角形顶点数组和缓冲区的VAO。
glBindVertexArray(vao[TRIANGLE]);
//直接绘制三角形
//GL_TRIANGLES表示使用三角形绘制,0表示从第一个顶点开始,3表示绘制3个顶点。
glDrawArrays(GL_TRIANGLES, 0, 3);
glFlush();
}
// 初始化例程。
// Initialization routine.
//1.设置清除颜色。
//2.生成和绑定VAO。
//3.生成和绑定VBO。
//4.将顶点和颜色数据复制到VBO。
//5.启用顶点数组并指定顶点和颜色指针
void setup(void)
{
// 设置清除颜色为白色。
glClearColor(1.0, 1.0, 1.0, 0.0);
// 生成两个VAO id。
glGenVertexArrays(2, vao);
// 绑定VAO id vao[ANNULUS],以下的顶点数组调用将应用于此VAO。
glBindVertexArray(vao[ANNULUS]);//存储环形的顶点数组和缓冲区的定义调用。
// 生成两个缓冲区对象。
glGenBuffers(2, buffer);
// 绑定顶点缓冲区并预留空间。
//将指定的缓冲区对象绑定到目标缓冲区类型
//目标缓冲区类型GL_ARRAY_BUFFER
//buffer[VERTICES]是之前通过glGenBuffers生成的缓冲区对象ID之一
glBindBuffer(GL_ARRAY_BUFFER, buffer[VERTICES]);//buffer[VERTICES]存储环形的顶点坐标和颜色
//为当前绑定到GL_ARRAY_BUFFER的缓冲区对象分配存储空间
//sizeof(vertices) + sizeof(colors)指定了缓冲区的大小,即顶点数据和颜色数据的总和
//第三个参数NULL表示我们只预留空间,不立即提供数据
//GL_STATIC_DRAW是一个提示,告诉OpenGL我们打算如何使用这个数据。GL_STATIC_DRAW表示数据将不会被频繁更改,主要用于静态绘制
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices1) + sizeof(colors1), NULL, GL_STATIC_DRAW);
//将顶点坐标数据复制到当前绑定的顶点缓冲区对象的前半部分
//GL_ARRAY_BUFFER指定了目标缓冲区类型
//0表示数据复制的起始偏移量,即从缓冲区的起始位置开始复制
//sizeof(vertices)指定了要复制的数据大小,即顶点坐标数据的大小
//vertices是指向顶点坐标数据的指针(数组名是第一个元素的地址)
glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(vertices1), vertices1);
// 将顶点颜色数据复制到顶点缓冲区的后半部分。
glBufferSubData(GL_ARRAY_BUFFER, sizeof(vertices1), sizeof(colors1), colors1);
// 绑定并填充索引缓冲区。
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer[INDICES]);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(stripIndices), stripIndices, GL_STATIC_DRAW);
// 启用两个顶点数组:坐标和颜色。
glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_COLOR_ARRAY);
// 指定顶点和颜色指针到相应数据的起始位置。
glVertexPointer(3, GL_FLOAT, 0, 0);
glColorPointer(3, GL_FLOAT, 0, (void*)(sizeof(vertices1)));
// 结束绑定VAO id vao[ANNULUS]。
//=======================================================
// 绑定VAO id vao[TRIANGLE],以下的顶点数组调用将应用于此VAO。
glBindVertexArray(vao[TRIANGLE]);//存储三角形的顶点数组和缓冲区的定义调用。
// 生成一个缓冲区对象。
glGenBuffers(1, buffer);
// 绑定顶点缓冲区并预留空间。
glBindBuffer(GL_ARRAY_BUFFER, buffer[VERTICES]);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices2) + sizeof(colors2), NULL, GL_STATIC_DRAW);
// 将顶点坐标数据复制到顶点缓冲区的前半部分。
glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(vertices2), vertices2);
// 将顶点颜色数据复制到顶点缓冲区的后半部分。
glBufferSubData(GL_ARRAY_BUFFER, sizeof(vertices2), sizeof(colors2), colors2);
// 启用两个顶点数组:坐标和颜色。
glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_COLOR_ARRAY);
//指定顶点指针,指向顶点数据的起始位置
//3表示每个顶点有3个分量(x, y, z)。
//GL_FLOAT表示每个分量的数据类型是浮点数。
//0表示顶点之间的间隔(步幅),这里为0表示顶点数据是紧密排列的。
//最后一个0表示顶点数据在缓冲区中的起始位置偏移量。
glVertexPointer(3, GL_FLOAT, 0, 0);
//指定颜色指针,指向颜色数据的起始位置
//3表示每个颜色有3个分量(r, g, b)。
//GL_FLOAT表示每个分量的数据类型是浮点数。
//0表示颜色之间的间隔(步幅),这里为0表示颜色数据是紧密排列的。
//(void *)(sizeof(vertices))表示颜色数据在缓冲区中的起始位置偏移量,这里偏移量为sizeof(vertices),即顶点数据之后的位置。
glColorPointer(3, GL_FLOAT, 0, (void*)(sizeof(vertices2)));
// 结束绑定VAO id vao[TRIANGLE]。
}
3.4 Display Lists
显示列表:用于存储经常调用的代码片段的宏
在OpenGL中,显示列表(Display List)是一种优化绘图性能的机制。它允许你将一系列OpenGL命令预先编译并存储起来,然后在需要时通过调用显示列表来执行这些命令。显示列表的主要作用包括:
- 性能优化:通过将一系列绘图命令预先编译并存储在显示列表中,可以减少在每次绘图时解析和执行这些命令的开销,从而提高绘图性能。
- 代码简化:使用显示列表可以简化代码结构,因为你可以将复杂的绘图命令封装在一个显示列表中,然后通过简单的调用来执行这些命令。
- 减少冗余操作:对于需要重复绘制的图形对象,使用显示列表可以避免每次绘制时重复发送相同的命令,从而减少冗余操作
例子:绘制多个螺旋
与显示列表相关的代码
// Globals.
//一个全局变量,用于存储显示列表的索引
static unsigned int aHelix; // List index.
// Initialization routine.
void setup(void)
{
float t; // Angle parameter.
//生成一个显示列表索引
aHelix = glGenLists(1); //列表的索引,一个非零无符号整数
// Begin create a display list.
//开始创建显示列表
glNewList(aHelix, GL_COMPILE);
// Draw a helix.
glBegin(GL_LINE_STRIP);
for (t = -10 * M_PI; t <= 10 * M_PI; t += M_PI / 20.0)
glVertex3f(20 * cos(t), 20 * sin(t), t);
glEnd();
glEndList();
// End create a display list.
glClearColor(1.0, 1.0, 1.0, 0.0);
}
// Drawing routine.
void drawScene(void)
{
glClear(GL_COLOR_BUFFER_BIT);
glColor3f(1.0, 1.0, 1.0);
//使用不同的颜色和变换矩阵绘制多个螺旋线
//每个螺旋线通过 glCallList(aHelix); 调用显示列表来绘制
glColor3f(1.0, 0.0, 0.0);
//用于保存当前的变换矩阵。
glPushMatrix();
glTranslatef(0.0, 0.0, -70.0);//平移
glCallList(aHelix); // Execute display list.
//用于恢复当前的变换矩阵。
glPopMatrix();
glColor3f(0.0, 1.0, 0.0);
glPushMatrix();
glTranslatef(30.0, 0.0, -70.0);
glScalef(0.5, 0.5, 0.5);//缩放
glCallList(aHelix); // Execute display list.
glPopMatrix();
glColor3f(0.0, 0.0, 1.0);
glPushMatrix();
glTranslatef(-25.0, 0.0, -70.0);
glRotatef(90.0, 0.0, 1.0, 0.0);//旋转
glCallList(aHelix); // Execute display list.
glPopMatrix();
glColor3f(1.0, 1.0, 0.0);
glPushMatrix();
glTranslatef(0.0, -20.0, -70.0);
glRotatef(90.0, 0.0, 0.0, 1.0);
glCallList(aHelix); // Execute display list.
glPopMatrix();
glColor3f(1.0, 0.0, 1.0);
glPushMatrix();
glTranslatef(-40.0, 40.0, -70.0);
glScalef(0.5, 0.5, 0.5);
glCallList(aHelix); // Execute display list.
glPopMatrix();
glColor3f(0.0, 1.0, 1.0);
glPushMatrix();
glTranslatef(30.0, 30.0, -70.0);
glRotatef(90.0, 1.0, 0.0, 0.0);
glCallList(aHelix); // Execute display list.
glPopMatrix();
glFlush();
}
3.5 Drawing Text
绘制文本
图形文本可以有两种类型:位图(也称为栅格)和笔画(也称为矢量)。
位图文本中的字符是用矩形块中的位元创建的,而笔画文本中的字符是用直线基元创建的。
FreeGLUT库支持位图字符和笔画字符。调用glutBitmapCharacter(*font, character)和glutStrokeCharacter(*font, character)以指定的字体渲染字符。
3.6 Programming the Mouse
编程鼠标按钮点击,转动滚轮和鼠标运动。
// Mouse callback routine.
void mouseControl(int button, int state, int x, int y)
{
if (button == GLUT_LEFT_BUTTON && state == GLUT_DOWN)
// Store the clicked point in the points array after correcting from event to OpenGL co-ordinates.
points.push_back( Point(x, height - y, pointSize) );
if (button == GLUT_RIGHT_BUTTON && state == GLUT_DOWN) exit(0);
glutPostRedisplay();
}
3.7 Programming Non-ASCII Keys
编程非ascii键
非ASCII键是指那些在标准ASCII字符集(American Standard Code for Information Interchange)之外的键。标准ASCII字符集包含128个字符,包括常见的英文字母(大小写)、数字、标点符号和一些控制字符(如换行、回车等)。非ASCII键则包括:
- 扩展ASCII字符:这些字符的编码范围是128到255,包含一些特殊符号、外语字符等。
- 功能键:如F1到F12键、方向键、Home、End、Page Up、Page Down等。
- 修饰键:如Shift、Ctrl、Alt、Caps Lock等。
- 其他特殊键:如Insert、Delete、Print Screen、Pause/Break等。
在处理键盘输入时,非ASCII键通常需要通过特定的键盘事件处理函数来捕获和处理,而不是通过简单的字符输入处理函数
在迄今为止的各种程序中,我们已经通过键盘输入与OpenGL窗口进行交互,方法是通过调用glutKeyboardFunc(keyboard_handling_func)在主例程中注册一个处理函数keyboard_handling_func()。要与非ascii键(如箭头键、F键和上下翻页键)交互,同样需要通过调用glutSpecialFunc(special_key_handling_func)注册一个处理函数special_key_handling_func()。
例子:定义方向键对应的特定事件
// Callback routine for non-ASCII key entry.
void specialKeyInput(int key, int x, int y)
{
if (key == GLUT_KEY_UP) Yvalue += 0.1;
if (key == GLUT_KEY_DOWN) Yvalue -= 0.1;
if (key == GLUT_KEY_LEFT) Xvalue -= 0.1;
if (key == GLUT_KEY_RIGHT) Xvalue += 0.1;
glutPostRedisplay();
}
3.8 Menus
编程弹出菜单
与弹出菜单相关的代码
// The top menu callback function.
void top_menu(int id)
{
if (id == 1) exit(0);
}
// The sub-menu callback function.
void color_menu(int id)
{
if (id == 2)
{
square_color[0] = 1.0; square_color[1] = 0.0; square_color[2] = 0.0;
}
if (id == 3)
{
square_color[0] = 0.0; square_color[1] = 0.0; square_color[2] = 1.0;
}
glutPostRedisplay();
}
// Routine to make the menu.
void makeMenu(void)
{
// The sub-menu is created first (because it should be visible when the top
// menu is created): its callback function is registered and menu entries added.
int sub_menu;
sub_menu = glutCreateMenu(color_menu);
glutAddMenuEntry("Red", 2);
glutAddMenuEntry("Blue", 3);
// The top menu is created: its callback function is registered and menu entries,
// including a submenu, added.
glutCreateMenu(top_menu);
glutAddSubMenu("Color", sub_menu);
glutAddMenuEntry("Quit", 1);
// The menu is attached to a mouse button.
glutAttachMenu(GLUT_RIGHT_BUTTON);
}
3.9 Line Stipples
线条点画:将图案应用到线条上。
与linestipple相关的代码
// Globals.
static int stippleID = 0; // Stipple ID.
// Drawing routine.
void drawScene(void)
{
glClear(GL_COLOR_BUFFER_BIT);
glColor3f(0.0, 0.0, 0.0);
glEnable(GL_LINE_STIPPLE); // Enable line stippling.
// Speficy the stipple pattern and write a label.
glRasterPos3f(30.0, 40.0, 0.0);
switch (stippleID)
{
case 0:
glDisable(GL_LINE_STIPPLE);
writeBitmapString((void*)font, "No stipple");
break;
case 1:
glLineStipple(1, 0x5555);
writeBitmapString((void*)font, "glLineStipple(1, 0x5555)");
break;
case 2:
glLineStipple(1, 0x0101);
writeBitmapString((void*)font, "glLineStipple(1, 0x0101)");
break;
case 3:
glLineStipple(1, 0x00FF);
writeBitmapString((void*)font, "glLineStipple(1, 0x00FF)");
break;
case 4:
glLineStipple(5, 0x5555);
writeBitmapString((void*)font, "glLineStipple(5, 0x5555)");
break;
default:
break;
}
// Draw one straight line segment.
glBegin(GL_LINES);
glVertex3f(25.0, 50.0, 0.0);
glVertex3f(75.0, 50.0, 0.0);
glEnd();
glDisable(GL_LINE_STIPPLE); // Disable line stippling.
glFlush();
}
// Keyboard input processing routine.
void keyInput(unsigned char key, int x, int y)
{
switch (key)
{
case ' ': //输入空格
if (stippleID < 4) stippleID++;
else stippleID = 0;
glutPostRedisplay();
break;
case 27:
exit(0);
break;
default:
break;
}
}
3.10 FreeGLUT Objects
FreeGLUT对象:现成的库对象
在FreeGLUT库中,solid和wireframe是指对象的渲染模式。具体来说:
• Solid(实心):对象以实心方式渲染,表面是填充的,看起来是一个完整的三维形状。
• Wireframe(线框):对象以线框方式渲染,只显示对象的边缘线,看起来像是一个三维的网格。
FreeGLUT提供了一些函数来绘制常见的三维对象,这些对象可以以实心或线框方式渲染
// Drawing routine.
void drawScene()
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glLoadIdentity();
// Position the objects for viewing.
gluLookAt(0.0, 3.0, 12.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0);
glLineWidth(2.0); // Thicken the wireframes.
// Commands to turn the objects.
glPushMatrix();
glRotatef(Zangle, 0.0, 0.0, 1.0);
glRotatef(Yangle, 0.0, 1.0, 0.0);
glRotatef(Xangle, 1.0, 0.0, 0.0);
// Draw objects.
switch (objID)
{
case 1:
glutSolidSphere(5.0, 40, 40);
objName = "Solid Sphere";
break;
case 2:
glutWireSphere(5.0, 40, 40);
objName = "Wire Sphere";
break;
case 3:
glutSolidCube(7.0);
objName = "Solid Cube";
break;
case 4:
glutWireCube(7.0);
objName = "Wire Cube";
break;
case 5:
glutSolidCone(3.0, 8.0, 30, 30);
objName = "Solid Cone";
break;
case 6:
glutWireCone(3.0, 8.0, 30, 30);
objName = "Wire Cone";
break;
case 7:
glutSolidTorus(1.0, 4.0, 30, 30);
objName = "Solid Torus";
break;
case 8:
glutWireTorus(1.0, 4.0, 30, 30);
objName = "Wire Torus";
break;
case 9:
glScalef(3.0, 3.0, 3.0);
glutSolidDodecahedron();
objName = "Solid Dodecahedron";
break;
case 10:
glScalef(3.0, 3.0, 3.0);
glutWireDodecahedron();
objName = "Wire Dodecahedron";
break;
case 11:
glScalef(5.0, 5.0, 5.0);
glutSolidOctahedron();
objName = "Solid Octahecron";
break;
case 12:
glScalef(5.0, 5.0, 5.0);
glutWireOctahedron();
objName = "Wire Octahedron";
break;
case 13:
glScalef(6.0, 6.0, 6.0);
glutSolidTetrahedron();
objName = "Solid Tetrahedron";
break;
case 14:
glScalef(6.0, 6.0, 6.0);
glutWireTetrahedron();
objName = "Wire Tetrahedron";
break;
case 15:
glScalef(5.0, 5.0, 5.0);
glutSolidIcosahedron();
objName = "Solid Icosahedron";
break;
case 16:
glScalef(5.0, 5.0, 5.0);
glutWireIcosahedron();
objName = "Wire Icosahedron";
break;
case 17:
glutSolidTeapot(4.0);
objName = "Solid Teapot";
break;
case 18:
glutWireTeapot(4.0);
objName = "Wire Teapot";
break;
default:
break;
}
glPopMatrix();
// Write label after disabling lighting.
glDisable(GL_LIGHTING);
glColor3f(0.0, 0.0, 0.0);
writeObjectName();
glEnable(GL_LIGHTING);
glutSwapBuffers();
}
// Initialization routine.
void setup(void)
{
// Material property vectors.
float matSpec[] = { 0.0, 1.0, 1.0, 1.0 };
float matShine[] = { 50.0 };
float matAmbAndDif[] = { 0.0, 0.1, 1.0, 1.0 };
// Light property vectors.
float lightAmb[] = { 0.0, 0.1, 1.0, 1.0 };
float lightDifAndSpec[] = { 0.0, 0.1, 1.0, 1.0 };
float lightPos[] = { 0.0, 7.0, 3.0, 0.0 };
float globAmb[] = { 0.2, 0.2, 0.2, 1.0 };
// Material properties of the objects.
glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, matSpec);
glMaterialfv(GL_FRONT_AND_BACK, GL_SHININESS, matShine);
glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, matAmbAndDif);
// Light0 properties.
glLightfv(GL_LIGHT0, GL_AMBIENT, lightAmb);
glLightfv(GL_LIGHT0, GL_DIFFUSE, lightDifAndSpec);
glLightfv(GL_LIGHT0, GL_SPECULAR, lightDifAndSpec);
glLightfv(GL_LIGHT0, GL_POSITION, lightPos);
// Poperties of the ambient light.
glLightModelfv(GL_LIGHT_MODEL_AMBIENT, globAmb); // Global ambient light.
glEnable(GL_LIGHTING); // Enable lighting calculations.
glEnable(GL_LIGHT0); // Enable particular light source.
glEnable(GL_DEPTH_TEST); // Enable depth testing.
glEnable(GL_NORMALIZE); // Enable automatic normalization of normals.
glClearColor(1.0, 1.0, 1.0, 0.0);
}
3.11 Clipping Planes
裁剪平面:除了捆绑观看盒或视锥台的自动六个平面外,用来裁剪场景的平面。程序员可以指定额外的裁剪平面。
透视投影中视锥体的六个裁剪平面
指定裁剪平面
glEnale的话 <0的部分被裁剪
glDisable的话 >=的部分被裁剪
// Drawing routine.
void drawScene(void)
{
float angle;
int i;
double eqn0[4] = { 0.0, 0.0, -1.0, 0.25 }; // Data for clipping plane 0.
//Ax+By+Cz+D
double eqn1[4] = { 1.0, 0.5, 0.0, -60.0 }; // Data for clipping plane 1.
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glPolygonMode(GL_FRONT, GL_FILL);
glClipPlane(GL_CLIP_PLANE0, eqn0); // Specify clipping plane 0.
glClipPlane(GL_CLIP_PLANE1, eqn1); // Specify clipping plane 1.
if (isClip0) glEnable(GL_CLIP_PLANE0); // Clip points s.t. z > 0.25.
else glDisable(GL_CLIP_PLANE0);
//glEnale的话 <0的部分被裁剪
//glDisable的话 >=的部分被裁剪
if (isClip1) glEnable(GL_CLIP_PLANE1); // Clip points s.t. x > 75.0.
else glDisable(GL_CLIP_PLANE1);
// Upper left circular annulus: the white disc overwrites the red disc.
glColor3f(1.0, 0.0, 0.0);
drawDisc(20.0, 25.0, 75.0, 0.0);
glColor3f(1.0, 1.0, 1.0);
drawDisc(10.0, 25.0, 75.0, 0.0);
// Upper right circular annulus: the white disc floats above the red disc blocking it.
glEnable(GL_DEPTH_TEST); // Enable depth testing.
glColor3f(1.0, 0.0, 0.0);
drawDisc(20.0, 75.0, 75.0, 0.0);
glColor3f(1.0, 1.0, 1.0);
drawDisc(10.0, 75.0, 75.0, 0.5); // Compare this z-value with that of the red disc.
glDisable(GL_DEPTH_TEST); // Disable depth testing.
// Lower circular annulus: with a true hole.
if (isWire) glPolygonMode(GL_FRONT, GL_LINE);else glPolygonMode(GL_FRONT, GL_FILL);
glColor3f(1.0, 0.0, 0.0);
glBegin(GL_TRIANGLE_STRIP);
for (i = 0; i <= N; ++i)
{
angle = 2 * M_PI * i / N;
glVertex3f(50 + cos(angle) * 10.0, 30 + sin(angle) * 10.0, 0.0);
glVertex3f(50 + cos(angle) * 20.0, 30 + sin(angle) * 20.0, 0.0);
}
glEnd();
// Write labels.
glColor3f(0.0, 0.0, 0.0);
glRasterPos3f(15.0, 51.0, 0.0);
writeBitmapString((void*)font, "Overwritten");
glRasterPos3f(69.0, 51.0, 0.0);
writeBitmapString((void*)font, "Floating");
glRasterPos3f(38.0, 6.0, 0.0);
writeBitmapString((void*)font, "The real deal!");
glFlush();
}
3.12 Frustum,Differently
Frustum,不同的是:gluPerspective()比glFrustum()更直观地指定了一个查看的截锥体,并且参数更少。
参数fovy,称为视场角,是沿yz平面在金字塔顶端的夹角(截锥体是截尾);
纵横比aspect是aspect=截锥体正面的宽度width/高度height;
glFrustum(left, right, bottom, top, near, far)
请注意 OpenGL 的一个小怪癖,即 near 和 far 值在符号上颠倒。
给函数glFrustum输入near,far为正数,实际上为
z
=
−
near
,
z
=
−
far
\text{z}=-\text{near}, \text{z}=-\text{far}
z=−near,z=−far
截锥体的八个顶点关于z轴对称,即
left
=
−
right
\text{left} = -\text{right}
left=−right、
bottom
=
−
top
\text{bottom} =-\text{top}
bottom=−top
gluPerspective(fovy, aspect, near, far)
width = right − left height = top − bottom aspect = width / height tan fovy 2 = top |near| \text{width}=\text{right}-\text{left}\\ ~\\ \text{height}=\text{top}-\text{bottom}\\ ~\\ \text{aspect}=\text{width}/\text{height}\\ ~\\ \tan\frac{\text{fovy}}{2}=\frac{\text{top}}{\text{|near|}} width=right−left height=top−bottom aspect=width/height tan2fovy=|near|top
假设给定glFrustum(-5.0, 5.0,-5.0, 5.0, 5.0, 100.0);
计算gluPerspective(fovy, aspect, near, far) 中fovy和aspect
glFrustum(left, right, bottom, top, near, far)
left=-5、right=5、bottom=-5、top=5、near=5、far=100
width = right - left = 5-(-5) = 10、height = top - bottom = 5-(-5) = 10
aspect = width / height = 10 / 10 = 1
tan(fovy/2) = top/near= 5/5 =1
fovy/2=45°,fovy=90°
3.13 Viewports
视口:OpenGL窗口中渲染绘图的特定部分。
场景的视区是 OpenGL 窗口中绘制场景的区域。默认情况下,它是整个窗口。但是,glViewPort() 调用可用于绘制到较小的矩形子区域。
调用 glViewport(x, y, w, h) 将视区指定为 OpenGL 窗口的矩形子区域,该窗口的左下角位于点 (x, y) 处,宽度为 w,高度为 h。单位为像素,OpenGL 窗口中的坐标为:原点位于左下角,x 轴的增加方向为向右,y 轴的增加方向为向上。
3.14 Multiple Windows
多窗口:多个顶级OpenGL窗口
FreeGLUT 库的 glutCreateWindow() 调用可以在主例程中多次调用,以创建多个顶级 OpenGL 窗口。每个顶级窗口的 display routine、resize 例程等属性可以独立指定。