【OpenGL】(1) 环境搭建:运行简单的 OpenGL 教学示例程序

  • 💭 写在前面:我们尽可能地让大家以 最简单粗暴且无脑的方式,带大家配置好 OpenGL 环境,并跑出我们第一个示例程序。再次声明,本专栏所有教学都是基于 Windows上使用 VS2022 (X64) 的。本专栏主要内容是关于 3D 计算机图形技术的学习,重点是学习与此技术相关的 3D 实时渲染 (3D real-time rendering) 技术。我们会以 "理论 + 实践" 的方式进行讲解,将重点介绍基于光栅化的 3D 渲染管线的计算结构,如 OpenGL / DirectX / Vulkan / Metal 等,并使用 OpenGL API 接口实现应用程序。

Step1:解压

① 首先,在你电脑的 C 盘 "C:\" 下创建一个 usr 文件夹,然后解压这个 OpenGL_Files.zip

② 打开它,将里的 OpenGL 目录拖拽(解压)到我们刚刚创建好的  C:\usr 目录下:

③ 此时,C:\usr\OpenGL 目录下应当会有 "include", "lib", "dll" 这三个文件:

Step2:VS, 启动!

启动 Visual Studio 2022 并选择 "创建新项目 (N) ",然后点击 下一步:

在 "项目名称 (J) "中,输入您要创建的项目名称。

这里我们下面的例子打算叫 3.0.Simple2DTransArrowMouse_GL。

然后在 "位置 (L) "中,选择要保存解决方案的目录位置:

Step3:解压 “教学示例程序”

将附件 3.0.Simple2DTransArrowMouse_GL.zip 解压:

将里面的东西解压出来,这么放进去:

① 将 Simple2DTransformation.cpp 和 Shaders/LoadShaders.cpp 加到" 源文件" 里。

② 将头文件 LoadShaders.h 加到 "头文件" 里。

③ 将其余的着色器文件(以 "vert "和 "frag "结尾的文件)放到 "资源文件" 里。

放进去后就像这样:

Step4:VS 内属性设置

① 在解决方案资源管理器中 右键 项目名(如图所示),然后点击 属性(R) 

② 然后选择 VC++目录,在 包含目录 的输入框中输入 C:\usr\OpenGL\include

📌 注意:打开 "编辑" 时 一定要注意 从父级或项目默认设置继承 有没有勾选上 !

③ 紧接着在 库目录 的输入框中输入:C:\usr\OpenGL\lib\x64

这里同样是要注意勾选一下 从父级或项目默认设置继承

(有坑!不勾选可能会引发 1>LINK : fatal error LNK1104: 无法打开文件“kernel32.lib” 的悲剧)

④ 然后点 链接器输入,在 附加依赖项 中添加 "glew32.lib "和 "freeglut.lib",然后单击 应用。(在后面教学的纹理映射相关代码中,我们还会在这里添加 "FreeImage.lib")。

(PS:文件名输错了会怎么样?会 100% 触发报错大礼包!沃日!烦死你!)

(这就是手残的后果!!!半天才发现哪里不对劲!)

⑤ 点击 调试环境 并输入:

PATH=C:\usr\OpenGL\dll\x64;%PATH%;

然后点击 应用

Step5:编译并运行程序

按住鼠标左键并移动鼠标,试着移动平面对象,然后按下 "ESC "键退出程序。

至此,我们的教学演示环境也就准备好了!

💬 代码演示:

#include <stdio.h> 
#include <stdlib.h> 
#include <GL/glew.h>               // GLEW库
#include <GL/freeglut.h>           // FreeGLUT 库

#include "Shaders/LoadShaders.h"   // 包含加载着色器的头文件

GLuint h_ShaderProgram;            // 着色器程序的句柄
GLint loc_ModelViewProjectionMatrix, loc_primitive_color;     // uniform变量的索引

// 只在必要时包含glm/*.hpp
//#include <glm/glm.hpp> 

#include <glm/gtc/matrix_transform.hpp>     // 包含矩阵变换函数,如平移、旋转、缩放、正交等
glm::mat4 ModelViewProjectionMatrix;
glm::mat4 ViewMatrix, ProjectionMatrix, ViewProjectionMatrix;

#define TO_RADIAN 0.01745329252f                           // 弧度转换常数
#define TO_DEGREE 57.295779513f                            // 角度转换常数
#define BUFFER_OFFSET(offset) ((GLvoid *) (offset))        // 用于偏移的宏

#define LOC_VERTEX 0                                       // 顶点坐标的位置索引

int win_width = 0, win_height = 0;                         // 窗口宽度和高度
float centerx = 0.0f, centery = 0.0f, rotate_angle = 0.0f; // 中心点坐标及旋转角度

GLfloat axes[4][2];                                        // 坐标轴的顶点坐标
GLfloat axes_color[3] = { 0.0f, 0.0f, 0.0f };              // 坐标轴颜色
GLuint VBO_axes, VAO_axes;                                 // 坐标轴的顶点缓冲对象和顶点数组对象


/* 函数申明(共23个函数)*/
void prepare_axes(void);                     // 准备坐标轴
void update_axes(void);                      // 更新坐标轴
void draw_axes(void);                        // 绘制坐标轴

void prepare_line(void);                     // 准备线条
void update_line(void);                      // 更新线条
void draw_line(void);                        // 绘制线条

void prepare_airplane(void);                 // 准备飞机
void draw_airplane(void);                    // 绘制飞机

void display(void);                                // 显示函数
void keyboard(unsigned char key, int x, int y);    // 键盘响应函数
void special(int key, int x, int y);               // 特殊键盘响应函数
void mouse(int button, int state, int x, int y);   // 鼠标响应函数
void motion(int x, int y);                         // 鼠标移动响应函数
void reshape(int width, int height);               // 窗口大小变化响应函数
void cleanup(void);                                // 清理函数
void register_callbacks(void);                     // 注册回调函数
void prepare_shader_program(void);                 // 准备着色器程序
void initialize_OpenGL(void);                      // 初始化OpenGL
void prepare_scene(void);                          // 准备场景
void initialize_renderer(void);                    // 初始化渲染器
void initialize_glew(void);                        // 初始化GLEW
void greetings(char* program_name, char messages[][256], int n_message_lines);    // 打印问候语


/* 主函数 */
#define N_MESSAGE_LINES 2
int main(int argc, char* argv[]) {
	char program_name[64] = "Simple2DTransformation_GLSL_3.0";   // 程序名称
	char messages[N_MESSAGE_LINES][256] = {      // 消息内容
		"    - Keys used: 'ESC', four arrows",   // 使用的键盘按键
		"    - Mouse used: L-click and move"     // 使用的鼠标操作
	};

	glutInit(&argc, argv);                              // 初始化GLUT库
	glutInitDisplayMode(GLUT_RGBA | GLUT_MULTISAMPLE);  // 设置显示模式
	glutInitWindowSize(1200 * 0.95, 800 * 0.95);        // 设置窗口大小
	glutInitContextVersion(4, 0);                       // 设置OpenGL上下文版本
	glutInitContextProfile(GLUT_CORE_PROFILE);          // 设置OpenGL核心模式
	glutCreateWindow(program_name);                     // 创建窗口并设置标题

	greetings(program_name, messages, N_MESSAGE_LINES); // 打印欢迎消息
	initialize_renderer();                              // 初始化渲染器

	glutSetOption(GLUT_ACTION_ON_WINDOW_CLOSE, GLUT_ACTION_GLUTMAINLOOP_RETURNS);   // 设置窗口关闭时的动作
	glutMainLoop();       // 进入主循环,开始渲染和事件处理
}


void prepare_axes(void) {   // 在模型坐标系中绘制坐标轴
	axes[0][0] = -win_width / 2.5f; axes[0][1] = 0.0f;    // x轴起点
	axes[1][0] = win_width / 2.5f; axes[1][1] = 0.0f;     // x轴终点
	axes[2][0] = 0.0f; axes[2][1] = -win_height / 2.5f;   // y轴起点
	axes[3][0] = 0.0f; axes[3][1] = win_height / 2.5f;    // y轴终点

	// 初始化顶点缓冲对象
	glGenBuffers(1, &VBO_axes);

	glBindBuffer(GL_ARRAY_BUFFER, VBO_axes);
	glBufferData(GL_ARRAY_BUFFER, sizeof(axes), axes, GL_STATIC_DRAW);

	// 初始化顶点数组对象
	glGenVertexArrays(1, &VAO_axes);
	glBindVertexArray(VAO_axes);

	glBindBuffer(GL_ARRAY_BUFFER, VBO_axes);
	glVertexAttribPointer(LOC_VERTEX, 2, GL_FLOAT, GL_FALSE, 0, BUFFER_OFFSET(0));

	glEnableVertexAttribArray(0);
	glBindBuffer(GL_ARRAY_BUFFER, 0);
	glBindVertexArray(0);
}


void update_axes(void) { // 更新坐标轴数据
	axes[0][0] = -win_width / 2.5f; axes[1][0] = win_width / 2.5f; // 更新x轴坐标
	axes[2][1] = -win_height / 2.5f; // 更新y轴起点
	axes[3][1] = win_height / 2.5f; // 更新y轴终点

	glBindBuffer(GL_ARRAY_BUFFER, VBO_axes); // 绑定顶点缓冲对象
	glBufferData(GL_ARRAY_BUFFER, sizeof(axes), axes, GL_STATIC_DRAW); // 将更新后的坐标数据传输到GPU
	glBindBuffer(GL_ARRAY_BUFFER, 0); // 解绑顶点缓冲对象
}

void draw_axes(void) { // 绘制坐标轴
	glUniform3fv(loc_primitive_color, 1, axes_color); // 设置坐标轴颜色
	glBindVertexArray(VAO_axes); // 绑定顶点数组对象
	glDrawArrays(GL_LINES, 0, 4); // 绘制线段
	glBindVertexArray(0); // 解绑顶点数组对象
}

GLfloat line[2][2]; // 线段的顶点坐标
GLfloat line_color[3] = { 1.0f, 0.0f, 0.0f }; // 线段颜色
GLuint VBO_line, VAO_line; // 线段的顶点缓冲对象和顶点数组对象

void prepare_line(void) { 	// 准备线段数据
	line[0][0] = (1.0f / 4.0f - 1.0f / 2.5f) * win_height; // 线段起点x坐标
	line[0][1] = (1.0f / 4.0f - 1.0f / 2.5f) * win_height - win_height / 4.0f; // 线段起点y坐标
	line[1][0] = win_width / 2.5f; // 线段终点x坐标
	line[1][1] = win_width / 2.5f - win_height / 4.0f; // 线段终点y坐标

	// 初始化顶点缓冲对象
	glGenBuffers(1, &VBO_line);

	glBindBuffer(GL_ARRAY_BUFFER, VBO_line);
	glBufferData(GL_ARRAY_BUFFER, sizeof(line), line, GL_STATIC_DRAW);

	// 初始化顶点数组对象
	glGenVertexArrays(1, &VAO_line);
	glBindVertexArray(VAO_line);

	glBindBuffer(GL_ARRAY_BUFFER, VBO_line);
	glVertexAttribPointer(LOC_VERTEX, 2, GL_FLOAT, GL_FALSE, 0, BUFFER_OFFSET(0));

	glEnableVertexAttribArray(0);
	glBindBuffer(GL_ARRAY_BUFFER, 0);
	glBindVertexArray(0);
}


void update_line(void) { 	// 更新线段数据,表示 y = x - win_height/4
	line[0][0] = (1.0f / 4.0f - 1.0f / 2.5f) * win_height; // 计算线段起点x坐标
	line[0][1] = (1.0f / 4.0f - 1.0f / 2.5f) * win_height - win_height / 4.0f; // 计算线段起点y坐标
	line[1][0] = win_width / 2.5f; // 设置线段终点x坐标
	line[1][1] = win_width / 2.5f - win_height / 4.0f; // 设置线段终点y坐标

	glBindBuffer(GL_ARRAY_BUFFER, VBO_line); // 绑定顶点缓冲对象
	glBufferData(GL_ARRAY_BUFFER, sizeof(line), line, GL_STATIC_DRAW); // 将更新后的线段数据传输到GPU
	glBindBuffer(GL_ARRAY_BUFFER, 0); // 解绑顶点缓冲对象
}

void draw_line(void) { // 在模型坐标系中绘制线段
	// 设置线段颜色
	glUniform3fv(loc_primitive_color, 1, line_color);
	glBindVertexArray(VAO_line); // 绑定顶点数组对象
	glDrawArrays(GL_LINES, 0, 2); // 绘制线段
	glBindVertexArray(0); // 解绑顶点数组对象
}



#define AIRPLANE_BIG_WING 0 // 定义大翼索引
#define AIRPLANE_SMALL_WING 1 // 定义小翼索引
#define AIRPLANE_BODY 2 // 定义机身索引
#define AIRPLANE_BACK 3 // 定义机尾索引
#define AIRPLANE_SIDEWINDER1 4 // 定义侧风雷1索引
#define AIRPLANE_SIDEWINDER2 5 // 定义侧风雷2索引
#define AIRPLANE_CENTER 6 // 定义中心索引

GLfloat big_wing[6][2] = { // 大翼顶点坐标数组
	{ 0.0, 0.0 },
	{ -20.0, 15.0 },
	{ -20.0, 20.0 },
	{ 0.0, 23.0 },
	{ 20.0, 20.0 },
	{ 20.0, 15.0 }
};

GLfloat small_wing[6][2] = { // 小翼顶点坐标数组
	{ 0.0, -18.0 },
	{ -11.0, -12.0 },
	{ -12.0, -7.0 },
	{ 0.0, -10.0 },
	{ 12.0, -7.0 },
	{ 11.0, -12.0 }
};

GLfloat body[5][2] = { // 机身顶点坐标数组
	{ 0.0, -25.0 },
	{ -6.0, 0.0 },
	{ -6.0, 22.0 },
	{ 6.0, 22.0 },
	{ 6.0, 0.0 }
};

GLfloat back[5][2] = { // 机尾顶点坐标数组
	{ 0.0, 25.0 },
	{ -7.0, 24.0 },
	{ -7.0, 21.0 },
	{ 7.0, 21.0 },
	{ 7.0, 24.0 }
};

GLfloat sidewinder1[5][2] = { // 侧风雷1顶点坐标数组
	{ -20.0, 10.0 },
	{ -18.0, 3.0 },
	{ -16.0, 10.0 },
	{ -18.0, 20.0 },
	{ -20.0, 20.0 }
};

GLfloat sidewinder2[5][2] = { // 侧风雷2顶点坐标数组
	{ 20.0, 10.0 },
	{ 18.0, 3.0 },
	{ 16.0, 10.0 },
	{ 18.0, 20.0 },
	{ 20.0, 20.0 }
};

GLfloat center[1][2] = { // 中心顶点坐标数组
	{ 0.0, 0.0 }
};

GLfloat airplane_color[7][3] = { // 飞机各部分颜色数组
	{ 150 / 255.0f, 129 / 255.0f, 183 / 255.0f },  // 大翼颜色
	{ 245 / 255.0f, 211 / 255.0f,   0 / 255.0f },  // 小翼颜色
	{ 111 / 255.0f,  85 / 255.0f, 157 / 255.0f },  // 机身颜色
	{ 150 / 255.0f, 129 / 255.0f, 183 / 255.0f },  // 机尾颜色
	{ 245 / 255.0f, 211 / 255.0f,   0 / 255.0f },  // 侧风雷1颜色
	{ 245 / 255.0f, 211 / 255.0f,   0 / 255.0f },  // 侧风雷2颜色
	{ 255 / 255.0f,   0 / 255.0f,   0 / 255.0f }   // 中心颜色
};




GLuint VBO_airplane, VAO_airplane; // 飞机的顶点缓冲对象和顶点数组对象

void prepare_airplane() {
	GLsizeiptr buffer_size = sizeof(big_wing) + sizeof(small_wing) + sizeof(body) + sizeof(back)
		+ sizeof(sidewinder1) + sizeof(sidewinder2) + sizeof(center);

	// 初始化顶点缓冲对象
	glGenBuffers(1, &VBO_airplane);

	glBindBuffer(GL_ARRAY_BUFFER, VBO_airplane);
	glBufferData(GL_ARRAY_BUFFER, buffer_size, NULL, GL_STATIC_DRAW); // 分配缓冲区对象的内存空间

	// 将顶点数据分别存入缓冲区对象
	glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(big_wing), big_wing);
	glBufferSubData(GL_ARRAY_BUFFER, sizeof(big_wing), sizeof(small_wing), small_wing);
	glBufferSubData(GL_ARRAY_BUFFER, sizeof(big_wing) + sizeof(small_wing), sizeof(body), body);
	glBufferSubData(GL_ARRAY_BUFFER, sizeof(big_wing) + sizeof(small_wing) + sizeof(body), sizeof(back), back);
	glBufferSubData(GL_ARRAY_BUFFER, sizeof(big_wing) + sizeof(small_wing) + sizeof(body) + sizeof(back),
		sizeof(sidewinder1), sidewinder1);
	glBufferSubData(GL_ARRAY_BUFFER, sizeof(big_wing) + sizeof(small_wing) + sizeof(body) + sizeof(back)
		+ sizeof(sidewinder1), sizeof(sidewinder2), sidewinder2);
	glBufferSubData(GL_ARRAY_BUFFER, sizeof(big_wing) + sizeof(small_wing) + sizeof(body) + sizeof(back)
		+ sizeof(sidewinder1) + sizeof(sidewinder2), sizeof(center), center);

	// 初始化顶点数组对象
	glGenVertexArrays(1, &VAO_airplane);
	glBindVertexArray(VAO_airplane);

	glBindBuffer(GL_ARRAY_BUFFER, VBO_airplane);
	glVertexAttribPointer(LOC_VERTEX, 2, GL_FLOAT, GL_FALSE, 0, BUFFER_OFFSET(0));

	glEnableVertexAttribArray(0);
	glBindBuffer(GL_ARRAY_BUFFER, 0);
	glBindVertexArray(0);
}


void draw_airplane() { // 在模型坐标系中绘制飞机
	glBindVertexArray(VAO_airplane); // 绑定顶点数组对象

	// 绘制大翼
	glUniform3fv(loc_primitive_color, 1, airplane_color[AIRPLANE_BIG_WING]);
	glDrawArrays(GL_TRIANGLE_FAN, 0, 6);

	// 绘制小翼
	glUniform3fv(loc_primitive_color, 1, airplane_color[AIRPLANE_SMALL_WING]);
	glDrawArrays(GL_TRIANGLE_FAN, 6, 6);

	// 绘制机身
	glUniform3fv(loc_primitive_color, 1, airplane_color[AIRPLANE_BODY]);
	glDrawArrays(GL_TRIANGLE_FAN, 12, 5);

	// 绘制机尾
	glUniform3fv(loc_primitive_color, 1, airplane_color[AIRPLANE_BACK]);
	glDrawArrays(GL_TRIANGLE_FAN, 17, 5);

	// 绘制侧风雷1
	glUniform3fv(loc_primitive_color, 1, airplane_color[AIRPLANE_SIDEWINDER1]);
	glDrawArrays(GL_TRIANGLE_FAN, 22, 5);

	// 绘制侧风雷2
	glUniform3fv(loc_primitive_color, 1, airplane_color[AIRPLANE_SIDEWINDER2]);
	glDrawArrays(GL_TRIANGLE_FAN, 27, 5);

	// 绘制中心点
	glUniform3fv(loc_primitive_color, 1, airplane_color[AIRPLANE_CENTER]);
	glPointSize(5.0);
	glDrawArrays(GL_POINTS, 32, 1);
	glPointSize(1.0);

	glBindVertexArray(0); // 解绑顶点数组对象
}


// 绘制飞机 1-4
void display(void) {
	int i;
	float x, r, s, delx, delr, dels;
	glm::mat4 ModelMatrix;

	glClear(GL_COLOR_BUFFER_BIT);
	
	ModelMatrix = glm::mat4(1.0f);
	ModelViewProjectionMatrix = ViewProjectionMatrix * ModelMatrix;
	glUniformMatrix4fv(loc_ModelViewProjectionMatrix, 1, GL_FALSE, &ModelViewProjectionMatrix[0][0]);
	draw_axes();
 	draw_line();
	draw_airplane();
	 
	ModelMatrix = glm::translate(glm::mat4(1.0f), glm::vec3(centerx, centery, 0.0f));
	ModelMatrix = glm::rotate(ModelMatrix, rotate_angle, glm::vec3(0.0f, 0.0f, 1.0f));
	ModelViewProjectionMatrix = ViewProjectionMatrix * ModelMatrix;
	glUniformMatrix4fv(loc_ModelViewProjectionMatrix, 1, GL_FALSE, &ModelViewProjectionMatrix[0][0]);
 	draw_airplane(); // 0
	
	ModelMatrix = glm::translate(glm::mat4(1.0f), glm::vec3(-win_width / 4.0f, -win_height / 4.0f, 0.0f));
	ModelMatrix = glm::rotate(ModelMatrix, 90.0f*TO_RADIAN, glm::vec3(0.0f, 0.0f, 1.0f));
	ModelMatrix = glm::scale(ModelMatrix, glm::vec3(3.0f, 3.0f, 1.0f));
	ModelViewProjectionMatrix = ViewProjectionMatrix * ModelMatrix;
	glUniformMatrix4fv(loc_ModelViewProjectionMatrix, 1, GL_FALSE, &ModelViewProjectionMatrix[0][0]);
	draw_airplane();  // 1
	
	ModelMatrix = glm::translate(glm::mat4(1.0f), glm::vec3(win_width / 2.5f, -win_height / 8.0f, 0.0f));
	ModelMatrix = glm::rotate(ModelMatrix, 270.0f*TO_RADIAN, glm::vec3(0.0f, 0.0f, 1.0f));
	ModelViewProjectionMatrix = ViewProjectionMatrix * ModelMatrix;
	glUniformMatrix4fv(loc_ModelViewProjectionMatrix, 1, GL_FALSE, &ModelViewProjectionMatrix[0][0]);
	draw_airplane();  // 2
 
	ModelMatrix = glm::translate(glm::mat4(1.0f), glm::vec3(win_height / 4.0f, 0.0f, 0.0f));
	ModelMatrix = glm::rotate(ModelMatrix, 45.0f*TO_RADIAN, glm::vec3(0.0f, 0.0f, 1.0f));
	ModelMatrix = glm::scale(ModelMatrix, glm::vec3(1.0f, -1.0f, 1.0f));
	ModelMatrix = glm::rotate(ModelMatrix, -45.0f*TO_RADIAN, glm::vec3(0.0f, 0.0f, 1.0f));
	ModelMatrix = glm::translate(ModelMatrix, glm::vec3(-win_height / 4.0f, 0.0f, 0.0f));
	ModelMatrix = glm::translate(ModelMatrix, glm::vec3(win_width / 2.5f, -win_height / 8.0f, 0.0f));
	ModelMatrix = glm::scale(ModelMatrix, glm::vec3(2.0f, 2.0f, 1.0f));
	ModelMatrix = glm::translate(ModelMatrix, glm::vec3(-win_width / 2.5f, win_height / 8.0f, 0.0f));
 
	ModelMatrix = glm::translate(ModelMatrix, glm::vec3(win_width / 2.5f, -win_height / 8.0f, 0.0f));
	ModelMatrix = glm::rotate(ModelMatrix, 270.0f*TO_RADIAN, glm::vec3(0.0f, 0.0f, 1.0f));
	ModelViewProjectionMatrix = ViewProjectionMatrix * ModelMatrix;
	glUniformMatrix4fv(loc_ModelViewProjectionMatrix, 1, GL_FALSE, &ModelViewProjectionMatrix[0][0]);
	draw_airplane();  // 3
	 
	delx = win_width/14.0f; delr = 15.0f; dels = 1.1f;
	x = -delx; r = delr; s = dels;
	for (i = 0; i < 5; i++, x -= delx, r += delr, s *= dels) {
		ModelMatrix = glm::translate(glm::mat4(1.0f), glm::vec3(x, 15.0f*sqrtf(-x), 0.0f));
		ModelMatrix = glm::rotate(ModelMatrix, r*TO_RADIAN, glm::vec3(0.0f, 0.0f, 1.0f));
			glTranslatef(x, 15.0f*sqrtf(-x), 0.0f);
			ModelMatrix = glm::scale(ModelMatrix, glm::vec3(s, s, 1.0f));
			ModelViewProjectionMatrix = ViewProjectionMatrix * ModelMatrix;
			glUniformMatrix4fv(loc_ModelViewProjectionMatrix, 1, GL_FALSE, &ModelViewProjectionMatrix[0][0]);
			draw_airplane();  // 4
	}
	glFlush();	
}   
void keyboard(unsigned char key, int x, int y) {
	switch (key) {
	case 27: // ESC键
		glutLeaveMainLoop(); // 调用退出主循环,触发清理回调函数
		break;
	}
}

void special(int key, int x, int y) {
#define SENSITIVITY 2.0 // 灵敏度
	switch (key) {
	case GLUT_KEY_LEFT: // 左箭头键
		centerx -= SENSITIVITY; // 水平移动
		glutPostRedisplay(); // 重新绘制窗口
		break;
	case GLUT_KEY_RIGHT: // 右箭头键
		centerx += SENSITIVITY; // 水平移动
		glutPostRedisplay(); // 重新绘制窗口
		break;
	case GLUT_KEY_DOWN: // 下箭头键
		centery -= SENSITIVITY; // 垂直移动
		glutPostRedisplay(); // 重新绘制窗口
		break;
	case GLUT_KEY_UP: // 上箭头键
		centery += SENSITIVITY; // 垂直移动
		glutPostRedisplay(); // 重新绘制窗口
		break;
	}
}

int leftbuttonpressed = 0; // 左键是否按下

void mouse(int button, int state, int x, int y) {
	if ((button == GLUT_LEFT_BUTTON) && (state == GLUT_DOWN)) // 鼠标左键按下
		leftbuttonpressed = 1; // 设置左键按下标志为1
	else if ((button == GLUT_LEFT_BUTTON) && (state == GLUT_UP)) // 鼠标左键释放
		leftbuttonpressed = 0; // 设置左键按下标志为0
}

void motion(int x, int y) {
	static int delay = 0; // 鼠标移动延迟计数器
	static float tmpx = 0.0, tmpy = 0.0; // 临时存储鼠标位置的变量
	float dx, dy; // 鼠标移动的水平和垂直距离

	if (leftbuttonpressed) { // 如果左键按下
		centerx = x - win_width / 2.0f; // 计算鼠标的x坐标
		centery = (win_height - y) - win_height / 2.0f; // 计算鼠标的y坐标

		if (delay == 8) {	// 如果鼠标移动计数器为8
			dx = centerx - tmpx; // 计算水平移动距离
			dy = centery - tmpy; // 计算垂直移动距离

			if (dx > 0.0) { // 如果水平移动距离为正
				rotate_angle = atan(dy / dx) + 90.0f * TO_RADIAN; // 计算旋转角度
			}
			else if (dx < 0.0) { // 如果水平移动距离为负
				rotate_angle = atan(dy / dx) - 90.0f * TO_RADIAN; // 计算旋转角度
			}
			else if (dx == 0.0) { // 如果水平移动距离为0
				if (dy > 0.0) // 如果垂直移动距离为正
					rotate_angle = 180.0f * TO_RADIAN; // 设置旋转角度为180度
				else  // 如果垂直移动距离为负
					rotate_angle = 0.0f; // 设置旋转角度为0度
			}
			tmpx = centerx; // 更新临时存储的鼠标x坐标
			tmpy = centery; // 更新临时存储的鼠标y坐标
			delay = 0; // 重置鼠标移动延迟计数器
		}
		glutPostRedisplay(); // 重新绘制窗口
		delay++; // 增加鼠标移动延迟计数器
	}
}



void reshape(int width, int height) {
	win_width = width; // 更新窗口宽度
	win_height = height; // 更新窗口高度

	glViewport(0, 0, win_width, win_height); // 设置视口
	ProjectionMatrix = glm::ortho(-win_width / 2.0, win_width / 2.0,
		-win_height / 2.0, win_height / 2.0, -1000.0, 1000.0); // 设置正交投影矩阵
	ViewProjectionMatrix = ProjectionMatrix * ViewMatrix; // 更新视图投影矩阵

	update_axes(); // 更新坐标轴
	update_line(); // 更新线段

	glutPostRedisplay(); // 重新绘制窗口
}

void cleanup(void) {
	glDeleteVertexArrays(1, &VAO_axes); // 删除坐标轴顶点数组对象
	glDeleteBuffers(1, &VBO_axes); // 删除坐标轴顶点缓冲对象

	glDeleteVertexArrays(1, &VAO_line); // 删除线段顶点数组对象
	glDeleteBuffers(1, &VBO_line); // 删除线段顶点缓冲对象

	glDeleteVertexArrays(1, &VAO_airplane); // 删除飞机顶点数组对象
	glDeleteBuffers(1, &VBO_airplane); // 删除飞机顶点缓冲对象
}

void register_callbacks(void) {
	glutDisplayFunc(display); // 注册显示回调函数
	glutKeyboardFunc(keyboard); // 注册键盘按键回调函数
	glutSpecialFunc(special); // 注册特殊键回调函数
	glutMouseFunc(mouse); // 注册鼠标点击回调函数
	glutMotionFunc(motion); // 注册鼠标移动回调函数
	glutReshapeFunc(reshape); // 注册窗口大小变化回调函数
	glutCloseFunc(cleanup); // 注册窗口关闭回调函数
}

void prepare_shader_program(void) {
	ShaderInfo shader_info[3] = { // 着色器信息
		{ GL_VERTEX_SHADER, "Shaders/simple.vert" }, // 顶点着色器
		{ GL_FRAGMENT_SHADER, "Shaders/simple.frag" }, // 片段着色器
		{ GL_NONE, NULL } // 结束标记
	};

	h_ShaderProgram = LoadShaders(shader_info); // 加载着色器程序
	glUseProgram(h_ShaderProgram); // 使用着色器程序

	loc_ModelViewProjectionMatrix = glGetUniformLocation(h_ShaderProgram, "u_ModelViewProjectionMatrix"); // 获取模型视图投影矩阵的位置
	loc_primitive_color = glGetUniformLocation(h_ShaderProgram, "u_primitive_color"); // 获取基本颜色的位置
}

void initialize_OpenGL(void) {
	glEnable(GL_MULTISAMPLE); // 启用多重采样
	glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); // 设置多边形渲染模式为填充

	glClearColor(44 / 255.0f, 180 / 255.0f, 49 / 255.0f, 1.0f); // 设置清除颜色为深绿色
	ViewMatrix = glm::mat4(1.0f); // 初始化视图矩阵
}

void prepare_scene(void) {
	prepare_axes(); // 准备坐标轴
	prepare_line(); // 准备线段
	prepare_airplane(); // 准备飞机模型
}

void initialize_renderer(void) {
	register_callbacks(); // 注册回调函数
	prepare_shader_program(); // 准备着色器程序
	initialize_OpenGL(); // 初始化OpenGL状态
	prepare_scene(); // 准备场景对象
}

void initialize_glew(void) {
	GLenum error;

	glewExperimental = GL_TRUE; // 启用实验性的GLEW功能

	error = glewInit(); // 初始化GLEW
	if (error != GLEW_OK) { // 如果初始化失败
		fprintf(stderr, "Error: %s\n", glewGetErrorString(error)); // 打印错误信息
		exit(-1); // 退出程序
	}
	fprintf(stdout, "*********************************************************\n");
	fprintf(stdout, " - GLEW version supported: %s\n", glewGetString(GLEW_VERSION)); // 打印支持的GLEW版本
	fprintf(stdout, " - OpenGL renderer: %s\n", glGetString(GL_RENDERER)); // 打印OpenGL渲染器信息
	fprintf(stdout, " - OpenGL version supported: %s\n", glGetString(GL_VERSION)); // 打印支持的OpenGL版本
	fprintf(stdout, "*********************************************************\n\n");
}

void greetings(char* program_name, char messages[][256], int n_message_lines) {
	fprintf(stdout, "**************************************************************\n\n");
	fprintf(stdout, "  PROGRAM NAME: %s\n\n", program_name); // 打印程序名称

	for (int i = 0; i < n_message_lines; i++)
		fprintf(stdout, "%s\n", messages[i]); // 逐行打印消息内容
	fprintf(stdout, "\n**************************************************************\n\n");

	initialize_glew(); // 初始化GLEW
}

📌 [ 笔者 ]   雷向明
📃 [ 更新 ]   2024.3.31
❌ [ 勘误 ]   /* 暂无 */
📜 [ 声明 ]   由于作者水平有限,本文有错误和不准确之处在所难免,
              本人也很想知道这些错误,恳望读者批评指正!

📜 参考文献:

– J. Hughes et al., Computer Graphics: Principles and Practice(3rd ed.), Addison-Wesley, 2013.

– S. Marscner et al., Fundamentals of Computer Graphics(4th ed.), CRC Press, 2015.

– E. Angel, Interactive Computer Graphics: A Top-Down Approach with Shader-Based OpenGL (7th ed.), Addison-Wesley, 2014.

– T. Akenine-Möller et al., Real-Time Rendering(4th ed.), AK Peters/CRC Press, 2018.

– D. Shreiner et al., OpenGL Programming Guide(9th ed.): The Official Guide to Learning OpenGL, Versions 4.5 with SPIR-V, 2016.

– G. Sellers and R. Wright Jr., OpenGL Superbible: Comprehensive Tutorial and reference(7th ed.), Addison-Wesley Professional, 2015.

– J. de Vries, Learn OpenGL – Graphics Programming, Kendall & Welling, 2020.

– D. Wolff, OpenGL 4 Shading Language Cookbook(3nd ed.), Packt Publishing, 2018.

– D. Ginsburg et al., OpenGL ES 3.0 Programming Guide(2nd ed.), Addison-Wesley, 2014

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

用数组模拟单链表、双链表、栈、单调栈、队列、循环队列、单调队列

本文用于记录个人算法竞赛学习&#xff0c;仅供参考 目录 一.模拟单链表 二.双链表 三.栈 四.单调栈 五.队列 六.循环队列 七.单调队列 为什么要用数组模拟而不用现成的STL&#xff0c;因为用数组模拟效率会快一点&#xff0c;比如用结构体指针的方式创建链表&#xff0…

C++ 二叉树OJ题

&#x1f493;博主CSDN主页:麻辣韭菜-CSDN博客&#x1f493;   ⏩专栏分类&#xff1a;C知识分享⏪   &#x1f69a;代码仓库:C高阶&#x1f69a;   &#x1f339;关注我&#x1faf5;带你学习更多C知识   &#x1f51d;&#x1f51d; 前言 C二叉搜索树 这篇讲解了搜索二叉…

动态规划1

动态规划问题的五步操作&#xff1a; 动态规划就是把dp表填满&#xff0c;则最终一定有一个数据是我们所需要的数据 下面以一道简单的题目进行讲解 本题其实就是斐波那契数列的一个plus版 &#xff0c;就是利用递推关系求值的过程 1.前期准备&#xff1a;创建dp表(使用一维…

GRE和MGRE

思维导图 综合实验 配置IP地址 R1&#xff1a; [R1]int g0/0/0 [R1-GigabitEthernet0/0/0]ip add 192.168.1.1 24 [R1-GigabitEthernet0/0/0]int s3/0/0 [R1-Serial3/0/0]ip add 15.1.1.1 24 R2: [R2]int g 0/0/0 [R2-GigabitEthernet0/0/0]ip ad 192.168.2.2 24 [R2-G…

基于四足机器人和机械臂的运动控制系统(二)

文章目录 前言一、四足步态二、视觉抓取三、远程遥控 谢绝转载&#xff0c;无作者许可不可用做其他用途&#xff08;如教育展示产品、课程设计或毕业设计等&#xff09; 前言 衔接上一篇文章&#xff0c;这篇文章主要来介绍项目的初步实现 一、四足步态 可以知道&#xff0…

常用的几种排序算法小结

目录 1.冒泡排序 2.堆排序 2.1堆的基础知识和特性 2.2向上调整算法和向下调整算法 2.3堆排序实现 3.插入排序 4.希尔排序 5.选择排序 5.1选择排序递归版 5.2选择排序非递归版 6.快速排序 6.1 Hoare版本之递归 6.1.1普通版 6.1.2随机数版 6.1.3三数取中版 6.1.4小区间优化…

前端虚拟滚动列表 vue虚拟列表

前端虚拟滚动列表 在大型的企业级项目中经常要渲染大量的数据&#xff0c;这种长列表是一个很普遍的场景&#xff0c;当列表内容越来越多就会导致页面滑动卡顿、白屏、数据渲染较慢的问题&#xff1b;大数据量列表性能优化&#xff0c;减少真实dom的渲染 看图&#xff1a;绿色…

安装最新的wxPython和Python3并保证二者兼容

要安装最新的wxPython和Python3并保证二者兼容&#xff0c;你可以按照以下步骤进行操作&#xff1a; 安装Python3&#xff1a; 访问Python官方网站下载适合你操作系统的最新版Python3安装包。运行安装程序&#xff0c;确保在安装过程中将Python添加到系统环境变量中。安装完成…

【Java】:static成员和代码块

目录 1.static成员 1.1再谈学生类 1.2static修饰成员变量 1.3static修饰成员方法 1.4static成员变量初始化 1.4.1就地初始化 1.4.2静态代码块初始化 2.代码块 2.1代码块概念以及分类 2.2普通代码块 2.3构造代码块 2.4静态代码块 1.static成员 1.1再谈学生类 使用类…

MATLAB 点云随机渲染赋色(51)

MATLAB 点云随机渲染赋色(51) 一、算法介绍二、算法实现1.代码2.效果总结一、算法介绍 为点云中的每个点随机赋予一种颜色,步骤和效果如图: 1、读取点云 (ply格式) 2、随机为每个点的RGB颜色字段赋值 3、保存结果 (ply格式) 二、算法实现 1.代码 代码如下(示例):…

gin基础学习笔记--参数验证

用gin框架的数据验证&#xff0c;可以不用解析数据&#xff0c;减少if else&#xff0c;会简洁许多。 package mainimport ("fmt""time""github.com/gin-gonic/gin""github.com/gorilla/sessions" )// 初始化一个cookie存储对象 // s…

基于STM32的武警哨位联动报警系统设计,支持以太网和WIFI通信

1.功能 本文提出的武警报警信息系统终端&#xff0c;可实现报警和联动响应&#xff0c;支持以太网和WIFI两种通信模式&#xff0c;可实现移动哨位报警和固定哨位报警&#xff0c;语音和显示报警信息用户可自行定制。 本终端主要由STM32F103处理器模块和C8051F340处理器模块构…

P-MapNet:Far-seeing Map Generator Enhanced by both SDMap and HDMap Priors

主页&#xff1a;homepage 参考代码&#xff1a;P-MapNet 动机与出发点 在感知系统中引入先验信息是可以提升静态元素感知网络的上限的&#xff0c;这篇文章对SD地图采用栅格化表示&#xff08;也就是图像形式&#xff09;&#xff0c;之后用CNN网络去抽取栅格化SD地图的信息&…

linux ubuntu 在保存文件不被允许,但是root权限

现象&#xff1a;MobaXterm_Personal_2登录到服务器&#xff0c;切换到root用户&#xff0c;然后使用MobaXterm_Personal_2自带的编辑器&#xff0c;编写文件&#xff0c;进行保存不被允许&#xff1b;查看目录root是有权限进行修改文件的&#xff0c;然后使用vim进行修改保存&…

网络安全-内网渗透2

一、MIC 将我们上次未描述完的MIC在这里详细解释一下 咱们所抓的第二个包会给返回一个服务端的challenge 之后服务器回包的第三个包会回复一个client challenge 所以咱们客户端和服务端现在分别有两个challenge&#xff0c;相当于客户端和服务端互相交换了一下challenge 因此…

本地搭建多人协作ONLYOFFICE文档服务器并结合Cpolar内网穿透实现公网访问远程办公

文章目录 1. 安装Docker2. 本地安装部署ONLYOFFICE3. 安装cpolar内网穿透4. 固定OnlyOffice公网地址 本篇文章讲解如何使用Docker在本地服务器上安装ONLYOFFICE&#xff0c;并结合cpolar内网穿透实现公网访问。 Community Edition允许您在本地服务器上安装ONLYOFFICE文档&…

高精度(大整数)

本文用于记录个人算法竞赛学习&#xff0c;仅供参考 一.什么是大整数 当一个数的位数已经很大了&#xff08;比如有10^6&#xff09;&#xff0c;常规的数据类型已经存不下了&#xff0c;那么这个时候就可以用数组来存&#xff0c;数组的每个元素代表数的每一位&#xff0c;且…

Base64编码的全面介绍

title: Base64编码的全面介绍 date: 2024/3/31 18:55:49 updated: 2024/3/31 18:55:49 tags: Base64编码网络传输文本转换数据膨胀非加密性质应用场景安全传输 1. Base64的定义和作用 Base64是一种用64个字符表示二进制数据的编码方式&#xff0c;通常用于在网络传输中将二进…

什么是Redis数据一致性?如何解决?

在系统中缓存最常用的策略是&#xff1a;服务端需要同时维护DB和cache&#xff0c;并且是以DB的结果为准–Cache-Aside Pattern&#xff08;缓存分离模式、旁路缓存&#xff09; 读数据 单纯的读数据是不会产生数据不一致&#xff0c;只有并发下读和写才会存在数据不一致。 写…

安装即启动?探索流氓App的自启动“黑科技” (Android系统内鬼之ContentProvider篇)

前段时间发现了一个神奇的app&#xff0c;它居然可以在安装之后立即自启动&#xff1a; 看到没有&#xff0c;在提示安装成功大概1到2秒后&#xff0c;就直接弹出Toast和通知了&#xff01; 好神奇啊&#xff0c;在没有第三方app帮忙唤醒的前提下&#xff0c;它是怎么做到首次安…