工具介绍
OpenGL:一个跨语言的图形API,用于渲染2D和3D图形。它提供了绘制图形所需的底层功能。
GLUT:OpenGL的一个工具库,简化了窗口创建、输入处理和其他与图形环境相关的任务。
使用的函数
1. glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
原理:此函数用于清除当前的颜色缓冲区和深度缓冲区。颜色缓冲区存储着每个像素的颜色信息,而深度缓冲区用于存储每个像素的深度值,以确保在3D场景中正确渲染物体的可见性。每次绘制新帧时,必须清除前一帧的数据,以避免旧内容影响新渲染的图像。清除颜色缓冲区确保背景色是统一的,而清除深度缓冲区允许重新计算物体的深度关系。
2. glLoadIdentity()
原理:此函数重置当前的模型观察矩阵为单位矩阵。模型观察矩阵用于转换物体的位置、旋转和缩放。在设置新的视图或模型转换之前,重置矩阵是必要的,以确保新的变换不会受到之前变换的影响。使用单位矩阵作为基础,可以确保后续的变换(如移动相机)是从一个已知的状态开始的。
3. gluLookAt(2.0, 2.0, 2.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0)
原理:此函数设置相机的位置、观察点和上方向。它通过创建视图矩阵来定义相机的视角。
① eye (相机位置):(2.0, 2.0, 2.0),表示相机位于三维空间中的位置。
② center (观察点):(0.0, 0.0, 0.0),表示相机注视的目标点。
③ up (上方向):(0.0, 0.0, 1.0),定义相机的上方向,通常用来确定视图的“上”方向。
4. glutSwapBuffers()
原理:在双缓冲模式下,glutSwapBuffers 函数用于交换前后缓冲区。前缓冲区显示当前渲染的内容,后缓冲区用于下一帧的绘制。通过交换缓冲区,可以避免画面闪烁和撕裂现象,提供更平滑的视觉效果。这使得用户在屏幕上看到的是完整的一帧,而不是正在绘制的部分。
5. glutMainLoop()
原理:此函数进入GLUT的事件处理循环,持续处理窗口事件和重绘请求。这是程序运行的核心循环,确保应用程序能够响应用户输入、窗口变化等事件。它使得OpenGL程序能够持续运行,并在需要时重绘场景。
实验过程
(0)打开 Visual Studio,在项目栏的 [管理Nuget程序包] 下载安装必要库:
(1)Drawing a Cube
源代码:cube.cpp
#include <GL/glut.h> // 包含OpenGL和GLUT库的头文件
// 定义正方体的顶点坐标(边长为0.7)
GLfloat vertices[][3] = {
{0.0f, 0.0f, 0.0f}, {0.7f, 0.0f, 0.0f}, {0.7f, 0.7f, 0.0f}, {0.0f, 0.7f, 0.0f},
{0.0f, 0.0f, 0.7f}, {0.7f, 0.0f, 0.7f}, {0.7f, 0.7f, 0.7f}, {0.0f, 0.7f, 0.7f}
};
// 定义正方体的边,每条边由两个顶点索引定义
int edges[][2] = {
{0, 1}, {1, 2}, {2, 3}, {3, 0},
{4, 5}, {5, 6}, {6, 7}, {7, 4},
{0, 4}, {1, 5}, {2, 6}, {3, 7}
};
// 绘制坐标轴
void drawAxes() {
glBegin(GL_LINES); // 开始绘制线段
// X轴(红色)
glColor3f(1.0, 0.0, 0.0); // 设置颜色为红色
glVertex3f(0.0, 0.0, 0.0); // X轴起点,坐标为 (-2.0, 0.0, 0.0)
glVertex3f(1.5, 0.0, 0.0); // X轴终点,坐标为 (2.0, 0.0, 0.0)
// Y轴(绿色)
glColor3f(0.0, 1.0, 0.0); // 设置颜色为绿色
glVertex3f(0.0, 0.0, 0.0); // Y轴起点,坐标为 (0.0, -2.0, 0.0)
glVertex3f(0.0, 1.5, 0.0); // Y轴终点,坐标为 (0.0, 2.0, 0.0)
// Z轴(蓝色)
glColor3f(0.0, 0.0, 1.0); // 设置颜色为蓝色
glVertex3f(0.0, 0.0, 0.0); // Z轴起点,坐标为 (0.0, 0.0, -2.0)
glVertex3f(0.0, 0.0, 1.5); // Z轴终点,坐标为 (0.0, 0.0, 2.0)
glEnd(); // 结束绘制线段
}
// 绘制正方体的函数
void drawCube() {
glColor3f(0.0, 0.0, 0.0); // 设置颜色为黑色
glBegin(GL_LINES); // 开始绘制线段
for (int i = 0; i < 12; i++) {
int v1 = edges[i][0]; // 边的第一个顶点
int v2 = edges[i][1]; // 边的第二个顶点
glVertex3fv(vertices[v1]); // 绘制第一个顶点
glVertex3fv(vertices[v2]); // 绘制第二个顶点
}
glEnd(); // 结束绘制线段
}
// 显示回调函数,用于绘制场景
void display() {
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // 清除颜色缓冲区和深度缓冲区
glLoadIdentity(); // 重置当前的模型观察矩阵
// 设置观察点和方向
gluLookAt(2.0, 2.0, 2.0, // 相机位置(eye)
0.0, 0.0, 0.0, // 观察点(center)
0.0, 0.0, 1.0); // 上方向(up)
drawAxes(); // 绘制坐标轴
drawCube(); // 调用绘制正方体的函数
glutSwapBuffers(); // 交换前后缓冲区
}
// 初始化函数,设置清除颜色和启用深度测试
void init() {
glClearColor(1.0, 1.0, 1.0, 1.0); // 设置背景颜色为白色
glEnable(GL_DEPTH_TEST); // 启用深度测试
glMatrixMode(GL_PROJECTION); // 选择投影矩阵
glLoadIdentity(); // 重置投影矩阵
gluPerspective(45.0, 640.0 / 480.0, 0.1, 100.0); // 设置透视投影
glMatrixMode(GL_MODELVIEW); // 切换回模型视图矩阵
}
int main(int argc, char** argv) {
glutInit(&argc, argv); // 初始化GLUT库
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH); // 设置显示模式
glutInitWindowSize(640, 480); // 设置窗口大小
glutCreateWindow("Cube"); // 创建窗口
init(); // 调用初始化函数
glutDisplayFunc(display); // 设置显示回调函数
glutMainLoop(); // 进入GLUT事件处理循环
return 0;
}
①源代码的实验结果:
改变参数,可以得到不同视角的投影:
②替换 drawCube() 函数中的绘制逻辑:
// 绘制正方体的函数
void drawCube() {
glBegin(GL_QUADS); // 开始绘制四边形
// 正面 (Z=0.0)
glColor3f(0.5, 0.5, 0.5); // 设置颜色为灰色
glVertex3fv(vertices[0]); // 底左
glVertex3fv(vertices[1]); // 底右
glVertex3fv(vertices[2]); // 顶右
glVertex3fv(vertices[3]); // 顶左
// 背面 (Z=0.7)
glVertex3fv(vertices[4]); // 底左
glVertex3fv(vertices[5]); // 底右
glVertex3fv(vertices[6]); // 顶右
glVertex3fv(vertices[7]); // 顶左
// 左面 (X=0.0)
glVertex3fv(vertices[0]); // 前底
glVertex3fv(vertices[3]); // 前顶
glVertex3fv(vertices[7]); // 后顶
glVertex3fv(vertices[4]); // 后底
// 右面 (X=0.7)
glVertex3fv(vertices[1]); // 前底
glVertex3fv(vertices[5]); // 前顶
glVertex3fv(vertices[6]); // 后顶
glVertex3fv(vertices[2]); // 后底
// 上面 (Y=0.7)
glVertex3fv(vertices[3]); // 左顶
glVertex3fv(vertices[2]); // 右顶
glVertex3fv(vertices[6]); // 后顶
glVertex3fv(vertices[7]); // 后左顶
// 下面 (Y=0.0)
glVertex3fv(vertices[0]); // 左底
glVertex3fv(vertices[1]); // 右底
glVertex3fv(vertices[5]); // 后底
glVertex3fv(vertices[4]); // 后左底
glEnd(); // 结束绘制四边形
}
运行结果:
(2)Drawing a Hexagon
源代码:hexagon.cpp
#include <GL/glut.h>
#include <math.h>
// 窗口大小调整的回调函数
void reshape(int width, int height) {
glViewport(0, 0, width, height); // 设置视口
glMatrixMode(GL_PROJECTION); // 选择投影矩阵
glLoadIdentity(); // 重置投影矩阵
// 保持纵横比
if (width <= height) {
gluOrtho2D(-1.0, 1.0, -1.0 * (GLfloat)height / (GLfloat)width, 1.0 * (GLfloat)height / (GLfloat)width);
}
else {
gluOrtho2D(-1.0 * (GLfloat)width / (GLfloat)height, 1.0 * (GLfloat)width / (GLfloat)height, -1.0, 1.0);
}
glMatrixMode(GL_MODELVIEW); // 切换回模型视图矩阵
}
// 绘制正六边形及其对角线的函数
void drawHexagon() {
// 设置六边形的顶点
GLfloat vertices[6][2];
float sideLength = 0.5f; // 边长设置为0.3
double M_PI = 3.14159265358979323846f;
for (int i = 0; i < 6; i++) {
float angle = 2.0f * M_PI * i / 6 + M_PI / 2; // 顶角在上
vertices[i][0] = sideLength * cos(angle); // X坐标
vertices[i][1] = sideLength * sin(angle); // Y坐标
}
// 绘制正六边形
glBegin(GL_LINE_LOOP); // 开始绘制六边形的边
for (int i = 0; i < 6; i++) {
glVertex2fv(vertices[i]); // 添加顶点
}
glEnd(); // 结束绘制六边形
// 绘制六边形的对角线
glBegin(GL_LINES); // 开始绘制线段
for (int i = 0; i < 6; i++) {
for (int j = i + 2; j < 6; j++) { // 只绘制非相邻的顶点之间的线
glVertex2fv(vertices[i]); // 第一端点
glVertex2fv(vertices[j]); // 第二端点
}
}
glEnd(); // 结束绘制对角线
}
// 显示回调函数
void display() {
glClear(GL_COLOR_BUFFER_BIT); // 清除颜色缓冲区
glLoadIdentity(); // 重置当前的模型观察矩阵
drawHexagon(); // 绘制正六边形及其对角线
glutSwapBuffers(); // 交换前后缓冲区
}
// 初始化函数
void init() {
glClearColor(1.0, 1.0, 1.0, 1.0); // 设置背景颜色为白色
glColor3f(0.0, 0.0, 0.0); // 设置绘制颜色为黑色
glMatrixMode(GL_PROJECTION); // 选择投影矩阵
glLoadIdentity(); // 重置投影矩阵
gluOrtho2D(-1.0, 1.0, -1.0, 1.0); // 使用正交投影
glMatrixMode(GL_MODELVIEW); // 切换回模型视图矩阵
}
// 主程序入口
int main(int argc, char** argv) {
glutInit(&argc, argv); // 初始化GLUT库
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB); // 设置显示模式
glutInitWindowSize(640, 480); // 设置窗口大小
glutCreateWindow("Hexagon"); // 创建窗口
init(); // 调用初始化函数
glutDisplayFunc(display); // 设置显示回调函数
glutReshapeFunc(reshape); // 设置窗口大小调整回调函数
glutMainLoop(); // 进入GLUT事件处理循环
return 0;
}
注意:正六边形看起来扁平的原因通常与视口的纵横比(宽高比)有关。如果窗口的宽度和高度比例不一致,例如宽度比高度大,那么在正交投影下绘制的形状可能会在视觉上变得扁平。
调整:
a.保持窗口的纵横比:
在窗口调整大小时,保持宽高比一致,确保 OpenGL 的视口与窗口大小相匹配。
b.调整正交投影参数:
根据窗口的宽高比调整 gluOrtho2D 的参数,使得在不同的窗口尺寸下,六边形的显示不受影响。
运行结果: