An OpenGL Toolbox

news2025/1/26 15:02:24

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命令预先编译并存储起来,然后在需要时通过调用显示列表来执行这些命令。显示列表的主要作用包括:

  1. 性能优化:通过将一系列绘图命令预先编译并存储在显示列表中,可以减少在每次绘图时解析和执行这些命令的开销,从而提高绘图性能。
  2. 代码简化:使用显示列表可以简化代码结构,因为你可以将复杂的绘图命令封装在一个显示列表中,然后通过简单的调用来执行这些命令。
  3. 减少冗余操作:对于需要重复绘制的图形对象,使用显示列表可以避免每次绘制时重复发送相同的命令,从而减少冗余操作

例子:绘制多个螺旋

与显示列表相关的代码

// 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键则包括:

  1. 扩展ASCII字符:这些字符的编码范围是128到255,包含一些特殊符号、外语字符等。
  2. 功能键:如F1到F12键、方向键、Home、End、Page Up、Page Down等。
  3. 修饰键:如Shift、Ctrl、Alt、Caps Lock等。
  4. 其他特殊键:如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=rightleft height=topbottom 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 例程等属性可以独立指定。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2283497.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

R语言学习笔记之高效数据操作

一、概要 数据操作是R语言的一大优势&#xff0c;用户可以利用基本包或者拓展包在R语言中进行复杂的数据操作&#xff0c;包括排序、更新、分组汇总等。R数据操作包&#xff1a;data.table和tidyfst两个扩展包。 data.table是当前R中处理数据最快的工具&#xff0c;可以实现快…

Linux的权限和一些shell原理

目录 shell的原理 Linux权限 sudo命令提权 权限 文件的属性 ⽂件类型&#xff1a; 基本权限&#xff1a; chmod改权限 umask chown 该拥有者 chgrp 改所属组 最后&#xff1a; 目录权限 粘滞位 shell的原理 我们广义上的Linux系统 Linux内核Linux外壳 Linux严格…

LearnOpenGL——光照

教程地址&#xff1a;简介 - LearnOpenGL CN 前言 这篇开始光照的学习。 颜色 原文链接&#xff1a; 颜色 - LearnOpenGL CN总结&#xff1a; 重新搭建了一个简单场景&#xff0c;为后面的学习做准备。 现实世界中有无数种颜色&#xff0c;每一个物体都有它们自己的颜色。我…

步入响应式编程篇(二)之Reactor API

步入响应式编程篇&#xff08;二&#xff09;之Reactor API 前言回顾响应式编程Reactor API的使用Stream引入依赖Reactor API的使用流源头的创建 reactor api的背压模式发布者与订阅者使用的线程查看弹珠图查看形成新流的日志 前言 对于响应式编程的基于概念&#xff0c;以及J…

利用Redis实现数据缓存

目录 1 为啥要缓存捏&#xff1f; 2 基本流程&#xff08;以查询商铺信息为例&#xff09; 3 实现数据库与缓存双写一致 3.1 内存淘汰 3.2 超时剔除&#xff08;半自动&#xff09; 3.3 主动更新&#xff08;手动&#xff09; 3.3.1 双写方案 3.3.2 读写穿透方案 3.3.…

【动态规划】--- 斐波那契数模型

Welcome to 9ilks Code World (๑•́ ₃ •̀๑) 个人主页: 9ilk (๑•́ ₃ •̀๑) 文章专栏&#xff1a; 算法Journey &#x1f3e0; 第N个泰波那契数模型 &#x1f4cc; 题目解析 第N个泰波那契数 题目要求的是泰波那契数&#xff0c;并非斐波那契数。 &…

php-phar打包避坑指南2025

有很多php脚本工具都是打包成phar形式&#xff0c;使用起来就很方便&#xff0c;那么如何自己做一个呢&#xff1f;也找了很多文档&#xff0c;也遇到很多坑&#xff0c;这里就来总结一下 phar安装 现在直接装yum php-cli包就有phar文件&#xff0c;很方便 可通过phar help查看…

java提取系统应用的日志中的sql获取表之间的关系

为了获取到对应的sql数据&#xff0c;分了三步骤 第一步&#xff0c;获取日志文件&#xff0c;解析日志文件中的查询sql&#xff0c;递归解析sql&#xff0c;获取表关系集合 递归解析sql&#xff0c;获取表与表之间的关系 输出得到的对应关联关系数据 第二步&#xff0c;根据获…

PyQt6医疗多模态大语言模型(MLLM)实用系统框架构建初探(下.代码部分)

医疗 MLLM 框架编程实现 本医疗 MLLM 框架结合 Python 与 PyQt6 构建,旨在实现多模态医疗数据融合分析并提供可视化界面。下面从数据预处理、模型构建与训练、可视化界面开发、模型 - 界面通信与部署这几个关键部分详细介绍编程实现。 6.1 数据预处理 在医疗 MLLM 框架中,多…

IMX6ull项目环境配置

文件解压缩&#xff1a; .tar.gz 格式解压为 tar -zxvf .tar.bz2 格式解压为 tar -jxvf 2.4版本后的U-boot.bin移植进SD卡后&#xff0c;通过串口启动配置开发板和虚拟机网络。 setenv ipaddr 192.168.2.230 setenv ethaddr 00:04:9f:…

Gradle buildSrc模块详解:集中管理构建逻辑的利器

文章目录 buildSrc模块二 buildSrc的使命三 如何使用buildSrc1. 创建目录结构2. 配置buildSrc的构建脚本3. 编写共享逻辑4. 在模块中引用 四 典型使用场景1. 统一依赖版本管理2. 自定义Gradle任务 3. 封装通用插件4. 扩展Gradle API 五 注意事项六 与复合构建&#xff08;Compo…

六、深入了解DI

依赖注入是⼀个过程&#xff0c;是指IoC容器在创建Bean时,去提供运⾏时所依赖的资源&#xff0c;⽽资源指的就是对象. 在上⾯程序案例中&#xff0c;我们使⽤了 Autowired 这个注解&#xff0c;完成了依赖注⼊的操作. 简单来说,就是把对象取出来放到某个类的属性中。 关于依赖注…

【论文阅读】HumanPlus: Humanoid Shadowing and Imitation from Humans

作者&#xff1a;Zipeng Fu、Qingqing Zhao、Qi Wu、Gordon Wetstein、Chelsea Finn 项目共同负责人&#xff0c;斯坦福大学 项目网址&#xff1a;https://humanoid-ai.github.io 摘要 制造外形与人类相似的机器人的一个关键理由是&#xff0c;我们可以利用大量的人类数据进行…

第25篇 基于ARM A9处理器用C语言实现中断<一>

Q&#xff1a;怎样理解基于ARM A9处理器用C语言实现中断的过程呢&#xff1f; A&#xff1a;同样以一段使用C语言实现中断的主程序为例介绍&#xff0c;和汇编语言实现中断一样这段代码也使用了定时器中断和按键中断。执行该主程序会在DE1-SoC的红色LED上显示流水灯&#xf…

Spring WebSocket 与 STOMP 协议结合实现私聊私信功能

目录 后端pom.xmlConfig配置类Controller类DTO 前端安装相关依赖websocketService.js接口javascripthtmlCSS 效果展示简单测试连接&#xff1a; 报错解决方法1、vue3 使用SockJS报错 ReferenceError: global is not defined 功能补充拓展1. 安全性和身份验证2. 异常处理3. 消息…

RabbitMQ5-死信队列

目录 死信的概念 死信的来源 死信实战 死信之TTl 死信之最大长度 死信之消息被拒 死信的概念 死信&#xff0c;顾名思义就是无法被消费的消息&#xff0c;一般来说&#xff0c;producer 将消息投递到 broker 或直接到queue 里了&#xff0c;consumer 从 queue 取出消息进…

[JavaScript] 面向对象编程

JavaScript 是一种多范式语言&#xff0c;既支持函数式编程&#xff0c;也支持面向对象编程。在 ES6 引入 class 语法后&#xff0c;面向对象编程在 JavaScript 中变得更加易于理解和使用。以下将详细讲解 JavaScript 中的类&#xff08;class&#xff09;、构造函数&#xff0…

Windows上通过Git Bash激活Anaconda

在Windows上配置完Anaconda后&#xff0c;普遍通过Anaconda Prompt激活虚拟环境并执行Python&#xff0c;如下图所示&#xff1a; 有时需要连续执行多个python脚本时&#xff0c;直接在Anaconda Prompt下可以通过在以下方式&#xff0c;即命令间通过&&连接&#xff0c;…

主机监控软件WGCLOUD使用指南 - 如何设置主题背景色

WGCLOUD运维监控系统&#xff0c;从v3.5.7版本开始支持设置不同的主题背景色&#xff0c;如下 更多主题查看说明 如何设置主题背景色 - WGCLOUD

C语言教程——文件处理(2)

目录 前言 一、顺序读写函数&#xff08;续&#xff09; 1.1fprintf 1.2fscanf 1.3fwrite 1.4fread 二、流和标准流 2.1流 2.2标准流 2.3示例 三、sscanf和sprintf 3.1sprintf 3.2sscanf 四、文件的随机读写 4.1fseek 4.2ftell 4.3rewind 五、文件读取结束的…