#include "PlayerGLWidget.h" #include "spdlog/spdlog.h" #include PlayerGLWidget::PlayerGLWidget(QWidget *parent) : QOpenGLWidget(parent) { } PlayerGLWidget::~PlayerGLWidget() { } void PlayerGLWidget::initializeGL() { /* 初始化OpenGL函数,OpenGL的函数是在运行时才确定函数指针的 */ initializeOpenGLFunctions(); /* ------------------------------------------------------------------------------------- * 顶点数组对象 * ------------------------------------------------------------------------------------ */ /* 创建一个顶点数组对象 */ GLuint VAO1; /* 生成一个VAO,返回的ID存储在VAO1中 */ glGenVertexArrays(1, &VAO1); /* 绑定VAO1到 GL_VERTEX_ARRAY 上,表示VAO1是一个顶点数组对象 */ glBindVertexArray(VAO1); /* 创建一个三角形坐标数组,这里的z坐标都是0,表示平面2维 */ float vertices[] = { -0.5f, -0.5f, 0.0f, // 左下角 0.5f, -0.5f, 0.0f, // 右下角 0.0f, 0.5f, 0.0f // 顶部 }; /* ------------------------------------------------------------------------------------- * 创建顶点数组缓冲区 * (GLuint就是unsigned int) * ------------------------------------------------------------------------------------ */ GLuint VBO1; /* 生成一个VBO,返回的ID存储在VBO1中 */ glGenBuffers(1, &VBO1); /* OpenGL有很多缓冲对象类型,顶点缓冲对象的缓冲类型是GL_ARRAY_BUFFER * 绑定VBO1到GL_ARRAY_BUFFER上,表示VBO1是一个顶点缓冲对象 */ glBindBuffer(GL_ARRAY_BUFFER, VBO1); /* 将顶点数据复制到缓冲区的内存中 */ glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); /* ------------------------------------------------------------------------------------- * 链接顶点属性 * ------------------------------------------------------------------------------------ */ /* 设置顶点属性指针,告诉OpenGL如何解析顶点数据 * 1、glVertexAttribPointer函数的第一个参数是顶点属性的位置值 * 2、第二个参数是每个顶点属性的大小,这里是3,因为我们有3个坐标分量。 * 3、第三个参数是数据类型,这里是GL_FLOAT,表示数据类型是float。 * 4、第四个参数是是否归一化,这里是GL_FALSE,表示不归一化。 * 5、第五个参数是步长,这里是0,表示紧凑存储。 * 6、第六个参数是偏移量,这里是0,表示从缓冲区的开头开始读取数据。 * 7、最后一个参数是启用顶点属性指针,告诉OpenGL我们要使用这个顶点属性。 */ glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0); /* 启用顶点属性指针 */ glEnableVertexAttribArray(0); /* ------------------------------------------------------------------------------------- * 顶点着色器 * ------------------------------------------------------------------------------------ */ /* 顶点着色器的源代码 * GLSL看起来很像C语言。每个着色器都起始于一个版本声明。OpenGL 3.3以及和更高版本中,GLSL版本号和OpenGL的版本是匹配的(比如说GLSL 420版本对应于OpenGL 4.2)。 * 同样明确表示我们会使用核心模式。 * 1、#version 330 core 设置版本号和模式 * 2、使用in关键字,在顶点着色器中声明所有的输入顶点属性(Input Vertex Attribute)。现在我们只关心位置(Position)数据,所以我们只需要一个顶点属性。 * GLSL有一个向量数据类型,它包含1到4个float分量,包含的数量可以从它的后缀数字看出来。由于每个顶点都有一个3D坐标,我们就创建一个vec3输入变量aPos。 * 我们同样也通过layout (location = 0)设定了输入变量的位置值(Location) * 3、gl_Position是一个内建变量,它是一个vec4类型的变量,表示顶点在裁剪空间中的位置。我们将aPos赋值给gl_Position,OpenGL会自动将vec3转换为vec4。 */ const char* vertexShaderSource = R"( #version 330 core layout(location = 0) in vec3 aPos; void main() { gl_Position = vec4(aPos, 1.0); } )"; /* ------------------------------------------------------------------------------------- * 编译着色器 * ------------------------------------------------------------------------------------ */ /* 创建一个着色器对象,参数是着色器类型 * GL_VERTEX_SHADER代表是顶点着色器,GL_FRAGMENT_SHADER代表是片段着色器 * glCreateShader函数返回一个着色器对象的ID,OpenGL会为我们分配一个唯一的ID */ GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER); /* 将着色器源代码附加到着色器对象上 */ glShaderSource(vertexShader, 1, &vertexShaderSource,nullptr); /* 编译着色器 */ glCompileShader(vertexShader); /* 检查编译错误 */ GLint success; GLchar infoLog[512]; glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success); if (!success) { glGetShaderInfoLog(vertexShader, sizeof(infoLog), nullptr, infoLog); SPDLOG_ERROR("着色器编译错误: {}", infoLog); } /* ------------------------------------------------------------------------------------- * 片段着色器 * ------------------------------------------------------------------------------------ */ /* 片段着色器的源代码 */ const char* fragmentShaderSource = R"( #version 330 core out vec4 FragColor; void main() { FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f); } )"; /* 创建一个片段着色器对象 */ GLuint fragmentShader = glCreateShader(GL_FRAGMENT_SHADER); /* 将片段着色器源代码附加到片段着色器对象上 */ glShaderSource(fragmentShader, 1, &fragmentShaderSource, nullptr); /* 编译片段着色器 */ glCompileShader(fragmentShader); /* 检查编译错误 */ success = 0; glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &success); if (!success) { glGetShaderInfoLog(fragmentShader, sizeof(infoLog), nullptr, infoLog); SPDLOG_ERROR("片段着色器编译错误: {}", infoLog); } /* ------------------------------------------------------------------------------------- * 着色器程序对象 * ------------------------------------------------------------------------------------ */ /* 创建一个着色器程序对象 */ GLuint shaderProgram = glCreateProgram(); /* 将顶点着色器和片段着色器附加到着色器程序对象上 */ glAttachShader(shaderProgram, vertexShader); glAttachShader(shaderProgram, fragmentShader); /* 链接着色器程序对象 */ glLinkProgram(shaderProgram); /* 检查链接错误 */ glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success); if(!success) { glGetProgramInfoLog(shaderProgram, 512, nullptr, infoLog); SPDLOG_ERROR("着色器程序链接错误: {}", infoLog); } /* 删除着色器对象,因为它们已经链接到着色器程序对象上了 */ glDeleteShader(vertexShader); glDeleteShader(fragmentShader); /* 使用着色器程序对象 */ glUseProgram(shaderProgram); } void PlayerGLWidget::resizeGL(int w, int h) { } void PlayerGLWidget::paintGL() { /* glClearColor函数是一个状态设置函数,而glClear函数则是一个状态使用的函数, * 它使用了当前的状态来获取应该清除为的颜色。 */ glClearColor(0.1f, 0.3f, 0.3f, 1.0f); glClear(GL_COLOR_BUFFER_BIT); /* 绘制三角形 */ /* glDrawArrays函数的第一个参数是绘制模式,这里是GL_TRIANGLES,表示绘制三角形。 * 第二个参数是起始索引,这里是0,表示从第一个顶点开始绘制。 * 第三个参数是顶点数量,这里是3,表示绘制3个顶点。 */ glDrawArrays(GL_TRIANGLES, 0, 3); }