1.OpenGL的环境配置:
集成开发环境Visual Studio Community 2019的安装:
在Windows一栏选择使用C++的桌面开发;再转到“单个组件”界面,在“编译器、生成工具和运行时”一栏选择用于“Windows的C++ CMake工具”;然后转到“语言包”勾选“英语”。
2.CMake的安装:
下载对应平台的CMake安装包,打开安装包,按流程安装CMake。
3.Git的安装:
下载对应平台的Git安装包,打开安装包,按流程安装Git,其中有选择编辑器的选项,有安装VSCode的建议选择VSCode作为默认的编辑工具。
4.Vcpkg的安装:
在github使用Git克隆仓库到安装目录;进入到vcpkg文件,使用管理员身份打开Powershell;运行bootstrap引导脚本,执行 .\bootstrap-vcpkg.bat ,构建vcpkg;构建完成后,执行.\vcpkg integrate install命令,将vcpkg聚合到visual stuido。
5.系统环境变量设置:
验证路径是否添加成功,随便一个文件夹内开一个终端输入:vcpkg,执行后没用跳出错误就说明vcpkg环境配置成功。
6.OpenGL库安装:GLFW,GLAD,GLM
7.构建并运行实验1.1:
在项目文件夹下打开命令行,然后执行 cmake -B . ,执行后会在当前项目文件夹内生成main.sln文件,点击打开;在出现的VS界面中,可以看到“解决方案管理器”里面右键点击 “main”项目,将其设置为启动项,之后即可编译运行程序。
8.绘制出参考图片样式的二维图形:
增加顶点数组对象VAO,由3个增到5个(包括新增的圆形和椭圆)。
GLuint vao[5], program;//由3增到5(加了圆形和椭圆)
- VAO是顶点数组对象(Vertex Array Object)的缩写。它是OpenGL中的一个对象,用于存储一组顶点属性的状态。简单来说,VAO能够保存多个VBO(顶点缓冲对象)和EBO(元素缓冲对象)的配置,使得在绘制图形时,只需绑定相应的VAO即可直接使用之前设置好的顶点数据和属性配置,从而简化了绘图过程。
- GLuint vao[5]: 声明了一个长度为5的无符号整数数组vao,用于存储5个顶点数组对象的句柄(即vao),这些对象可以用于绘制几何图形。最初只有3个VAO用来绘制三角形、矩形、线,现在增加到5个,其中包括了用于绘制圆形和椭圆的VAO。
- GLuint program: 声明了一个无符号整数变量program,用于存储一个OpenGL着色器程序的句柄,用于控制图形的渲染。
在init初始函数中定义生成椭圆和圆形的点。
// 定义椭圆的点
glm::vec2 ellipse_vertices[ELLIPSE_NUM_POINTS];
glm::vec3 ellipse_colors[ELLIPSE_NUM_POINTS];
// 定义圆形的点
glm::vec2 circle_vertices[CIRCLE_NUM_POINTS];
glm::vec3 circle_colors[CIRCLE_NUM_POINTS];
- glm::vec2表示一个二维向量,用于存储二维坐标(顶点位置)。
- glm::vec3表示一个三维向量,包含红、绿、蓝三个分量,用于存储颜色值(RGB)。
- 定义数组来存储绘制椭圆和圆形所需的顶点位置和颜色数据,数组的大小由预先定义的常量ELLIPSE_NUM_POINTS(椭圆顶点数量)和CIRCLE_NUM_POINTS(圆形顶点数量)决定。
定义完后,调用生成椭圆和圆形形状顶点位置的函数。
- 函数 generateEllipsePoints 用于生成椭圆或圆的顶点位置和颜色。
void generateEllipsePoints(glm::vec2 vertices[], glm::vec3 colors[], int startVertexIndex, int numPoints,glm::vec2 center, double scale, double verticalScale)
函数的参数包括:
- glm::vec2 vertices[]用于存储生成的椭圆或圆形的顶点位置。
- glm::vec3 colors[]用于存储每个顶点的颜色信息。
- int startVertexIndex为顶点数组的起始索引,即从数组的哪个位置开始填充顶点数据。
- int numPoints是要生成的椭圆或圆形的顶点数量。
- glm::vec2 center是椭圆或圆形的中心位置。
- double scale是椭圆的水平半轴长度或圆的半径。
- double verticalScale是椭圆的垂直半轴长度。如果 verticalScale 为 1.0,则生成的形状是一个圆;如果 verticalScale 不等于 1.0,则生成的是一个椭圆。
// 调用生成椭圆和圆形形状顶点位置的函数
generateEllipsePoints(ellipse_vertices, ellipse_colors, 0, ELLIPSE_NUM_POINTS, glm::vec2(-0.5, 0.7), 0.2, 0.5);
generateEllipsePoints(circle_vertices, circle_colors, 0, CIRCLE_NUM_POINTS, glm::vec2(0.6, 0.7), 0.2, 1.0);
- 椭圆的生成:一个中心在 (-0.5, 0.7),水平半轴为 0.2,垂直半轴为 0.5 的椭圆的顶点位置,并将这些顶点及其对应的颜色值存储到 ellipse_vertices 和 ellipse_colors 数组中。
- 圆形的生成:一个中心在 (0.6, 0.7),半径为 0.2 的圆形的顶点位置,并将这些顶点及其颜色值存储到 circle_vertices 和 circle_colors 数组中。
初始化圆和椭圆的数据。
生成和配置顶点数组对象 (VAO) 和顶点缓冲对象 (VBO),以将顶点数据(如位置和颜色)上传到 GPU,即在渲染中,GPU能够正确地访问和使用这些数据进行绘制。
- 生成并绑定 VAO:
glGenVertexArrays(1, &vao[3]); // 生成 VAO ID
glBindVertexArray(vao[3]); // 绑定 VAO,使其成为当前操作的目标
- 生成并绑定 VBO:
glGenBuffers(1, &vbo[0]); // 生成位置数据的 VBO
glBindBuffer(GL_ARRAY_BUFFER, vbo[0]); // 绑定位置 VBO
glBufferData(GL_ARRAY_BUFFER, sizeof(ellipse_vertices), ellipse_vertices, GL_STATIC_DRAW); // 将位置数据上传到 GPU
- 配置顶点属性指针:
location = glGetAttribLocation(program, "vPosition");// 获取位置属性在着色器中的位置
glEnableVertexAttribArray(location); // 启用位置属性
glVertexAttribPointer(
location, // 属性位置
2, // 每个顶点包含两个分量 (x, y)
GL_FLOAT, // 数据类型为浮点数
GL_FALSE, // 不需要归一化
sizeof(glm::vec2),// 步幅,即每两个顶点之间的字节间隔
BUFFER_OFFSET(0) ); // 数据从 VBO 开头开始读取
- 类似的步骤也用于颜色数据的配置:
glGenBuffers(1, &vbo[1]); // 生成颜色数据的 VBO
glBindBuffer(GL_ARRAY_BUFFER, vbo[1]); // 绑定颜色 VBO
glBufferData(GL_ARRAY_BUFFER, sizeof(ellipse_colors), ellipse_colors, GL_STATIC_DRAW); // 上传颜色数据
cLocation = glGetAttribLocation(program, "vColor"); // 获取颜色属性在着色器中的位置
glEnableVertexAttribArray(cLocation); // 启用颜色属性
glVertexAttribPointer(
cLocation, // 属性位置
3, // 每个颜色包含三个分量 (r, g, b)
GL_FLOAT, // 数据类型为浮点数
GL_FALSE, // 不需要归一化
sizeof(glm::vec3),// 步幅
BUFFER_OFFSET(0));// 数据从 VBO 开头开始读取
- 重复上述步骤进行其他对象的初始化:
针对不同的形状(包括椭圆和圆形),需要分别生成和配置它们的 VAO 和 VBO。可以通过重复上述步骤来完成这些操作。
在display函数中绘制椭圆和圆形。
// 画椭圆
glBindVertexArray(vao[3]);
glDrawArrays(GL_TRIANGLE_FAN, 0, ELLIPSE_NUM_POINTS);
// 画圆形
glBindVertexArray(vao[4]);
glDrawArrays(GL_TRIANGLE_FAN, 0, CIRCLE_NUM_POINTS);
- glBindVertexArray绑定 VAO,将编号为 vao[3] 的 VAO 绑定为椭圆,将编号为 vao[4] 的 VAO 绑定为圆,这两个VAO 是之前初始化椭圆和圆顶点和颜色数据时创建的,包含了椭圆和圆的顶点属性配置。
- glDrawArrays绘制椭圆和圆,其中GL_TRIANGLE_FAN指定绘制的图元类型为三角形扇形(Triangle Fan),即顶点数组中的第一个顶点将作为扇形的中心点,后续的顶点将围绕这个中心点形成多个相邻的三角形。对于椭圆和圆形,使用 GL_TRIANGLE_FAN 可以绘制出一个封闭的形状。0则表示从顶点数组的第一个顶点开始绘制。最后一个参数则指定绘制的顶点数量。
代码填充后,点击运行,结果如下图所示。
整体程序代码运行的整体流程:
- 初始化: 设置GLFW和OpenGL,生成图形数据,配置渲染管道。调用glfwInit()来初始化GLFW库,调用init()函数生成图形的顶点和颜色数据,并将这些数据发送到GPU,初始化OpenGL资源。
- 渲染循环: 持续绘制图形并响应用户输入。main()函数中包含一个while循环,持续运行直到用户关闭窗口。在循环内,程序会处理输入,调用display()函数进行绘制,交换缓冲区,处理事件。
- 清理和退出: 释放资源,结束程序。渲染循环结束后,程序退出,释放所有分配的资源,包括VAO、VBO和着色器程序。使用glfwTerminate()关闭GLFW并清理所有分配的资源。当用户关闭窗口或手动退出循环时,程序结束运行,返回到操作系统。
9.自行设计不同的图形颜色效果:
整设计思路:
- 首先将背景分成左上(浅蓝色)、右上(浅黄色)、左下(浅紫色)、右下(浅绿色)四块。
- 左上是一条鱼的形状:一个黄色三角形是尾巴,一个红色菱形是身体,一个白色小圆形是眼睛。
- 右上是一棵树:从上往下是三层规模递增的绿色半圆形,树桩是一个棕色小矩形状。
- 左下是一个胡萝卜:三个倾斜的绿色小矩形是胡萝卜叶子,一个橘色倾斜三角形是胡萝卜肉体。
- 右下是一个棒棒糖:由多个渐变圆环形成糖果,一个灰色矩形是棒棒糖棍。
背景绘制:
- 定义和生成背景分割的四个矩形的点以及颜色。
- 在init()函数中初始化背景数据,包括点以及颜色。
- 在display()函数中绘制背景。(背景是四个不同颜色的矩形,每个矩形可以由6个三角形绘制而成)。
//绘制背景
glBindVertexArray(vao[0]);
glDrawArrays(GL_TRIANGLES, 0, 6 * NUM_RECTANGLES);
10.左上鱼的绘制:
- 定义和生成鱼眼睛(圆形)、鱼尾巴(三角形)、鱼身体(菱形)的点以及颜色。
- 在进行鱼尾巴的绘制时,我在原本生成三角形的每个顶点generateTrianglePoints这个函数,增添了rotationAngle旋转角度这个参数,使得绘制的三角形能够进行旋转。
- 旋转的原理:首先定义旋转矩阵:,用于计算点绕原点的旋转变换;然后应用旋转:,将顶点坐标通过旋转矩阵转换到新的位置;最后平移,将旋转后的顶点平移到目标中心位置。
// 获得三角形的每个顶点
void generateTrianglePoints(glm::vec2 vertices[], glm::vec3 colors[], int startVertexIndex, glm::vec2 center, glm::vec2 scale, glm::vec3 vertexColors[], double rotationAngle)
{
for (int i = 0; i < 3; ++i) {
double currentAngle = getTriangleAngle(i);
glm::vec2 vertex = glm::vec2(sin(currentAngle), cos(currentAngle)) * scale;
// 应用旋转矩阵
double rotatedX = vertex.x * cos(rotationAngle) - vertex.y * sin(rotationAngle);
double rotatedY = vertex.x * sin(rotationAngle) + vertex.y * cos(rotationAngle);
// 计算旋转后的顶点位置
vertices[startVertexIndex + i] = glm::vec2(rotatedX, rotatedY) + center;
colors[startVertexIndex + i] = vertexColors[i];
}
}
同样的,在进行鱼身体的绘制时,我也在原本生成矩形的每个顶点generateRectanglePoints这个函数,增添了rotationAngle旋转角度这个参数,使得绘制的矩形能够进行旋转(当正方形顺时针旋转45度时即可得到菱形)。
// 计算矩形每个顶点的函数
void generateRectanglePoints(glm::vec2 vertices[], glm::vec3 colors[], int startVertexIndex,
glm::vec2 center, glm::vec2 scale, double rotationAngle, glm::vec3 color)
{
int vertexIndex = startVertexIndex;
for (int i = 0; i < 4; ++i) {
double currentAngle = getSquareAngle(i);
// 计算旋转后的坐标
glm::vec2 point = glm::vec2(sin(currentAngle), cos(currentAngle)) * scale;
glm::vec2 rotatedPoint = glm::vec2(
point.x * cos(rotationAngle) - point.y * sin(rotationAngle),
point.x * sin(rotationAngle) + point.y * cos(rotationAngle) );
// 应用平移
vertices[vertexIndex] = rotatedPoint + center;
colors[vertexIndex] = color;
vertexIndex++;
}
}
- 在init()函数中初始化鱼各个部分的数据,包括点以及颜色。使用 glGenVertexArrays 和 glGenBuffers 生成 VAO 和 VBO,再使用 glBindVertexArray 绑定 VAO,以及使用 glBindBuffer 绑定 VBO,并上传数据。
- 在display()函数中绘制鱼。(在这里需要注意的是,由于鱼眼睛是在鱼身体上面的,一定要先画鱼身体,再画眼睛,这样才不会被覆盖。)
//绘制鱼尾巴
glBindVertexArray(vao[2]);
glDrawArrays(GL_TRIANGLES, 0, TRIANGLE_NUM_POINTS);
//绘制鱼身体
glBindVertexArray(vao[3]);
glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
//绘制鱼眼睛
glBindVertexArray(vao[1]);
glDrawArrays(GL_TRIANGLE_FAN, 0, CIRCLE_NUM_POINTS);
11.其他图案同理绘制,最终效果如下: