【OpenGL】绘制彩色立方体
参考文章:https://www.opengl-tutorial.org/cn/beginners-tutorials/tutorial-4-a-colored-cube/
一、绘制立方体
立方体有六个方形表面,而OpenGL只支持画三角形,因此需要画12个三角形,每个面两个。我们用定义三角形顶点的方式来定义这些顶点。
// Our vertices. Three consecutive floats give a 3D vertex; Three consecutive vertices give a triangle.
// A cube has 6 faces with 2 triangles each, so this makes 6*2=12 triangles, and 12*3 vertices
static const GLfloat g_vertex_buffer_data[] = {-1.0f,-1.0f,-1.0f, // triangle 1 : begin-1.0f,-1.0f, 1.0f,-1.0f, 1.0f, 1.0f, // triangle 1 : end1.0f, 1.0f,-1.0f, // triangle 2 : begin-1.0f,-1.0f,-1.0f,-1.0f, 1.0f,-1.0f, // triangle 2 : end1.0f,-1.0f, 1.0f,-1.0f,-1.0f,-1.0f,1.0f,-1.0f,-1.0f,1.0f, 1.0f,-1.0f,1.0f,-1.0f,-1.0f,-1.0f,-1.0f,-1.0f,-1.0f,-1.0f,-1.0f,-1.0f, 1.0f, 1.0f,-1.0f, 1.0f,-1.0f,1.0f,-1.0f, 1.0f,-1.0f,-1.0f, 1.0f,-1.0f,-1.0f,-1.0f,-1.0f, 1.0f, 1.0f,-1.0f,-1.0f, 1.0f,1.0f,-1.0f, 1.0f,1.0f, 1.0f, 1.0f,1.0f,-1.0f,-1.0f,1.0f, 1.0f,-1.0f,1.0f,-1.0f,-1.0f,1.0f, 1.0f, 1.0f,1.0f,-1.0f, 1.0f,1.0f, 1.0f, 1.0f,1.0f, 1.0f,-1.0f,-1.0f, 1.0f,-1.0f,1.0f, 1.0f, 1.0f,-1.0f, 1.0f,-1.0f,-1.0f, 1.0f, 1.0f,1.0f, 1.0f, 1.0f,-1.0f, 1.0f, 1.0f,1.0f,-1.0f, 1.0f
};
OpenGL的缓冲由一些标准的函数(glGenBuffers, glBindBuffer, glBufferData, glVertexAttribPointer)
来创建、绑定、填充和配置,具体操作可以看之前的章节
实际上绘制一个正方体和绘制三角形类似,只需要改变顶点参数,并且将绘制的顶点数量从3改为36,如下:
glDrawArrays(GL_TRIANGLES, 0, 12*3);
关于这段代码,有几点要说明一下:
- 截至目前我们使用的三维模型都是固定的:只能在源码中修改模型,重新编译,然后祈祷不要出什么差错。我们将在第七课中学习如何动态地加载模型。
- 实际上,每个顶点至少出现了三次(在以上代码中搜索”-1.0f,-1.0f,-1.0f”看看)。这严重浪费了内存空间。我们将在第九课中学习怎样对此进行优化。
现在您已具备绘制一个白色立方体的所有条件。试着让着色器运行起来吧:)
此时绘制出来的立方体应该是红色的,因为之前设置了片段着色器颜色为红色:
二、为立方体添加颜色
从概念上讲,颜色与位置是一回事:就是数据嘛。OpenGL术语中称之为”属性(attribute)”。其实我们之前已用glEnableVertexAttribArray()和glVertexAttribPointer()设置过属性了。现在加上颜色属性,代码很相似。
首先声明颜色:每个顶点一个RGB三元组。这里随机生成一些颜色,所以效果看起来可能不太好;您可以调整得更好些,例如把顶点的位置作为颜色值。
// One color for each vertex. They were generated randomly.
static const GLfloat g_color_buffer_data[] = {0.583f, 0.771f, 0.014f,0.609f, 0.115f, 0.436f,0.327f, 0.483f, 0.844f,0.822f, 0.569f, 0.201f,0.435f, 0.602f, 0.223f,0.310f, 0.747f, 0.185f,0.597f, 0.770f, 0.761f,0.559f, 0.436f, 0.730f,0.359f, 0.583f, 0.152f,0.483f, 0.596f, 0.789f,0.559f, 0.861f, 0.639f,0.195f, 0.548f, 0.859f,0.014f, 0.184f, 0.576f,0.771f, 0.328f, 0.970f,0.406f, 0.615f, 0.116f,0.676f, 0.977f, 0.133f,0.971f, 0.572f, 0.833f,0.140f, 0.616f, 0.489f,0.997f, 0.513f, 0.064f,0.945f, 0.719f, 0.592f,0.543f, 0.021f, 0.978f,0.279f, 0.317f, 0.505f,0.167f, 0.620f, 0.077f,0.347f, 0.857f, 0.137f,0.055f, 0.953f, 0.042f,0.714f, 0.505f, 0.345f,0.783f, 0.290f, 0.734f,0.722f, 0.645f, 0.174f,0.302f, 0.455f, 0.848f,0.225f, 0.587f, 0.040f,0.517f, 0.713f, 0.338f,0.053f, 0.959f, 0.120f,0.393f, 0.621f, 0.362f,0.673f, 0.211f, 0.457f,0.820f, 0.883f, 0.371f,0.982f, 0.099f, 0.879f
};
缓冲的创建、绑定和填充方法与之前一样:
GLuint colorbuffer;
glGenBuffers(1, &colorbuffer);
glBindBuffer(GL_ARRAY_BUFFER, colorbuffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(g_color_buffer_data), g_color_buffer_data, GL_STATIC_DRAW);
配置也一样:
// 2nd attribute buffer : colors
glEnableVertexAttribArray(1);
glBindBuffer(GL_ARRAY_BUFFER, colorbuffer);
glVertexAttribPointer(1, // attribute. No particular reason for 1, but must match the layout in the shader.3, // sizeGL_FLOAT, // typeGL_FALSE, // normalized?0, // stride(void*)0 // array buffer offset
);
现在在顶点着色器中已经能访问这个新增的缓冲了:
// Notice that the "1" here equals the "1" in glVertexAttribPointer
layout(location = 1) in vec3 vertexColor;
这一课的顶点着色器没有什么复杂的效果,仅仅是简单地把颜色传递到片段着色器:
// Output data ; will be interpolated for each fragment.
out vec3 fragmentColor;void main(){[...]// The color of each vertex will be interpolated// to produce the color of each fragmentfragmentColor = vertexColor;
}
在片段着色器中要再次声明片段颜色:
// Interpolated values from the vertex shaders
in vec3 fragmentColor;
然后将其拷贝到输出颜色:
// Ouput data
out vec3 color;void main(){// Output color = color specified in the vertex shader,// interpolated between all 3 surrounding verticescolor = fragmentColor;
}
于是得到:
呃,太难看了。为了搞清楚出现这种情况原因,我们先看看画一个”远”和”近”的三角形会发生什么:
似乎挺好。现在画”远”三角形:
它遮住了”近”三角形!它本应该在”近”三角形后面的!我们的立方体问题就在这里:一些理应被遮挡的面,因为绘制次序靠后,竟然变成可见的了。我们将用深度缓冲(Z-Buffer)算法解决它。
便签1
如果您没发现问题,把摄像机放到(4,3,-3)试试
便签2
如果”颜色和位置同为属性”,那为什么颜色要声明 out vec3 fragmentColor,而位置不需要?实际上,位置有点特殊:它是唯一必须赋初值的(否则OpenGL不知道在哪画三角形)。所以在顶点着色器里, gl_Position是内置变量。
三、深度缓冲(Z-Buffer)The Z-Buffer
该问题的解决方案是:在缓冲中存储每个片段的深度(即”Z”值);并且每次绘制片段之前要比较当前与先前片段的深度值,看谁离摄像机更近。
您可以自己实现深度缓冲,但让硬件自动完成更简单:
// Enable depth test
glEnable(GL_DEPTH_TEST);
// Accept fragment if it closer to the camera than the former one
glDepthFunc(GL_LESS);
你还需要清除除了颜色以外每一帧(frame)的深度。
// Clear the screen
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
问题解决了。
四、完整代码
my_glwidget.h
//
// Created by liuhang on 2025/9/16.
// #ifndef OPENGL_LEARNING_MY_GLWIDGET_H
#define OPENGL_LEARNING_MY_GLWIDGET_H #include<QOpenGLWidget>
#include<QOpenGLFunctions>
#include<QMatrix4x4>
#include<QTimer> class MyGLWidget : public QOpenGLWidget,protected QOpenGLFunctions
{ Q_OBJECT
public: explicit MyGLWidget(QWidget* parent = nullptr); ~MyGLWidget() override; protected: void initializeGL() override; void paintGL() override; void resizeGL(int w,int h) override; void doMVP();
private: void loadShader(std::string const& vertex_shader_path,std::string const& fragment_shader_path); private: GLuint vertex_array_id; GLuint vertex_buffer_id; GLuint color_buffer_id; GLuint vertex_shader_id; GLuint fragment_shader_id; GLuint shader_program_id; GLint uniform_model_matrix_location; GLint uniform_view_matrix_location; GLint uniform_projection_matrix_location;
}; #endif //OPENGL_LEARNING_MY_GLWIDGET_H
my_glwidget.cpp
//
// Created by liuhang on 2025/9/16.
//#include "my_glwidget.h"
#include<string>
#include<fstream>
#include<sstream>
#include<iostream>
#include<filesystem>
#include<QThread>
#include<math.h>// Our vertices. Three consecutive floats give a 3D vertex; Three consecutive vertices give a triangle.
// A cube has 6 faces with 2 triangles each, so this makes 6*2=12 triangles, and 12*3 vertices
static const GLfloat vertex_buffer_data[] = {-1.0f,-1.0f,-1.0f, // triangle 1 : begin-1.0f,-1.0f, 1.0f,-1.0f, 1.0f, 1.0f, // triangle 1 : end1.0f, 1.0f,-1.0f, // triangle 2 : begin-1.0f,-1.0f,-1.0f,-1.0f, 1.0f,-1.0f, // triangle 2 : end1.0f,-1.0f, 1.0f,-1.0f,-1.0f,-1.0f,1.0f,-1.0f,-1.0f,1.0f, 1.0f,-1.0f,1.0f,-1.0f,-1.0f,-1.0f,-1.0f,-1.0f,-1.0f,-1.0f,-1.0f,-1.0f, 1.0f, 1.0f,-1.0f, 1.0f,-1.0f,1.0f,-1.0f, 1.0f,-1.0f,-1.0f, 1.0f,-1.0f,-1.0f,-1.0f,-1.0f, 1.0f, 1.0f,-1.0f,-1.0f, 1.0f,1.0f,-1.0f, 1.0f,1.0f, 1.0f, 1.0f,1.0f,-1.0f,-1.0f,1.0f, 1.0f,-1.0f,1.0f,-1.0f,-1.0f,1.0f, 1.0f, 1.0f,1.0f,-1.0f, 1.0f,1.0f, 1.0f, 1.0f,1.0f, 1.0f,-1.0f,-1.0f, 1.0f,-1.0f,1.0f, 1.0f, 1.0f,-1.0f, 1.0f,-1.0f,-1.0f, 1.0f, 1.0f,1.0f, 1.0f, 1.0f,-1.0f, 1.0f, 1.0f,1.0f,-1.0f, 1.0f
};// One color for each vertex. They were generated randomly.
static const GLfloat color_buffer_data[] = {0.583f, 0.771f, 0.014f,0.609f, 0.115f, 0.436f,0.327f, 0.483f, 0.844f,0.822f, 0.569f, 0.201f,0.435f, 0.602f, 0.223f,0.310f, 0.747f, 0.185f,0.597f, 0.770f, 0.761f,0.559f, 0.436f, 0.730f,0.359f, 0.583f, 0.152f,0.483f, 0.596f, 0.789f,0.559f, 0.861f, 0.639f,0.195f, 0.548f, 0.859f,0.014f, 0.184f, 0.576f,0.771f, 0.328f, 0.970f,0.406f, 0.615f, 0.116f,0.676f, 0.977f, 0.133f,0.971f, 0.572f, 0.833f,0.140f, 0.616f, 0.489f,0.997f, 0.513f, 0.064f,0.945f, 0.719f, 0.592f,0.543f, 0.021f, 0.978f,0.279f, 0.317f, 0.505f,0.167f, 0.620f, 0.077f,0.347f, 0.857f, 0.137f,0.055f, 0.953f, 0.042f,0.714f, 0.505f, 0.345f,0.783f, 0.290f, 0.734f,0.722f, 0.645f, 0.174f,0.302f, 0.455f, 0.848f,0.225f, 0.587f, 0.040f,0.517f, 0.713f, 0.338f,0.053f, 0.959f, 0.120f,0.393f, 0.621f, 0.362f,0.673f, 0.211f, 0.457f,0.820f, 0.883f, 0.371f,0.982f, 0.099f, 0.879f
};MyGLWidget::MyGLWidget(QWidget *parent): QOpenGLWidget(parent)
{
#if 0timer.setInterval(1);connect(&timer,&QTimer::timeout,this,[this](){static float count = 0;count+= 0.01f;global_i = std::fabs(sin(count));this->update();timer.setInterval(1);timer.start();});#endif#if 0timer.setInterval(1);connect(&timer,&QTimer::timeout,this,[this](){global_i+= 1;if(fabs(global_i - 100) < 0.1f){global_i = 0;}this->update();timer.setInterval(1);timer.start();});timer.start();
#endif}MyGLWidget::~MyGLWidget() {makeCurrent();glDeleteVertexArrays(1,&vertex_array_id);glDeleteBuffers(1,&vertex_buffer_id);doneCurrent();
}void MyGLWidget::initializeGL() {initializeOpenGLFunctions();glClearColor(0.2f,0.3f,0.3f,1.0f);//启动深度测试glEnable(GL_DEPTH_TEST);// Accept fragment if it closer to the camera than the former oneglDepthFunc(GL_LESS);glGenVertexArrays(1,&vertex_array_id);glBindVertexArray(vertex_array_id);glGenBuffers(1,&vertex_buffer_id);glBindBuffer(GL_ARRAY_BUFFER,vertex_buffer_id);glBufferData(GL_ARRAY_BUFFER,sizeof(vertex_buffer_data),vertex_buffer_data,GL_STATIC_DRAW);//描述顶点数组信息//描述顶点数组信息glVertexAttribPointer(0,3,GL_FLOAT,GL_FALSE,0,nullptr);//启用layout = 0为当前绑定的VAOglEnableVertexAttribArray(0);//================颜色信息================//生成颜色的VBO idglGenBuffers(1,&color_buffer_id);//绑定颜色VBOglBindBuffer(GL_ARRAY_BUFFER,color_buffer_id);//加入颜色数组到VBOglBufferData(GL_ARRAY_BUFFER,sizeof(color_buffer_data),color_buffer_data,GL_STATIC_DRAW);//描述颜色数组信息glVertexAttribPointer(1,3,GL_FLOAT,GL_FALSE,0,nullptr);//启动layout = 1绑定当前的VAOglEnableVertexAttribArray(1);//加载着色器loadShader("/Users/liuhang/CLionProjects/opengl-learning/opengl1-4-colored-cube/shader/shader.vert","/Users/liuhang/CLionProjects/opengl-learning/opengl1-4-colored-cube/shader/shader.frag");//解绑VAOglBindVertexArray(0);//解绑VBOglBindBuffer(GL_ARRAY_BUFFER, 0);
}void MyGLWidget::paintGL() {//清除颜色缓存和深度缓存glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);glBindVertexArray(vertex_array_id);//使用着色器程序glUseProgram(shader_program_id);doMVP();glDrawArrays(GL_TRIANGLES,0,3 * 12);
}void MyGLWidget::resizeGL(int w, int h) {glViewport(0, 0, w, h);
}void MyGLWidget::loadShader(std::string const& vertex_shader_path,std::string const& fragment_shader_path) {//创建顶点着色器vertex_shader_id = glCreateShader(GL_VERTEX_SHADER);//读取shader.vertstd::string vertex_shader_code;std::ifstream vertex_shader_stream(vertex_shader_path,std::ios::in);if(vertex_shader_stream.is_open()){std::stringstream ss;ss << vertex_shader_stream.rdbuf();vertex_shader_code = ss.str();vertex_shader_stream.close();}else{std::cout << "current path = " << std::filesystem::current_path() << std::endl;if (!std::filesystem::exists(vertex_shader_path)) {std::cerr << "File not found: " << vertex_shader_path << std::endl;}std::cerr << "read vertex_shader fail!" << std::endl;return;}//加载vertex着色器程序代码const char* vertex_shader_code_pointer = vertex_shader_code.c_str();glShaderSource(vertex_shader_id,1,&vertex_shader_code_pointer,nullptr);//编译vertex shaderglCompileShader(vertex_shader_id);//查看vertex编译结果GLint Result = GL_FALSE;int infoLogLength;glGetShaderiv(vertex_shader_id,GL_COMPILE_STATUS,&Result);glGetShaderiv(vertex_shader_id,GL_INFO_LOG_LENGTH,&infoLogLength);if(infoLogLength > 0){std::vector<char> vertex_shader_error_message(infoLogLength+1);glGetShaderInfoLog(vertex_shader_id, infoLogLength, nullptr, &vertex_shader_error_message[0]);std::cout << "vertex shader:" << &vertex_shader_error_message[0] << std::endl;}//创建片段着色器fragment_shader_id = glCreateShader(GL_FRAGMENT_SHADER);//读取shader.fragstd::string fragment_shader_code;std::ifstream fragment_shader_stream(fragment_shader_path,std::ios::in);if(fragment_shader_stream.is_open()){std::stringstream ss;ss << fragment_shader_stream.rdbuf();fragment_shader_code = ss.str();fragment_shader_stream.close();}else{std::cout << "current path = " << std::filesystem::current_path() << std::endl;if (!std::filesystem::exists(vertex_shader_path)) {std::cerr << "File not found: " << vertex_shader_path << std::endl;}std::cerr << "read fragment_shader fail!" << std::endl;}//加载fragment着色器程序代码const char* fragment_shader_code_pointer = fragment_shader_code.c_str();glShaderSource(fragment_shader_id,1,&fragment_shader_code_pointer,nullptr);//编译fragment shaderglCompileShader(fragment_shader_id);//查看fragment编译结果glGetShaderiv(fragment_shader_id,GL_COMPILE_STATUS,&Result);glGetShaderiv(fragment_shader_id,GL_INFO_LOG_LENGTH,&infoLogLength);if(infoLogLength > 0){std::vector<char> fragment_shader_error_message(infoLogLength+1);glGetShaderInfoLog(fragment_shader_id, infoLogLength, nullptr, &fragment_shader_error_message[0]);std::cout << "fragment shader compile error:" << &fragment_shader_error_message[0] << std::endl;}//创建着色器程序shader_program_id = glCreateProgram();//附加着色器到程序glAttachShader(shader_program_id,vertex_shader_id);glAttachShader(shader_program_id,fragment_shader_id);//链接shaderglLinkProgram(shader_program_id);//检查程序链接结果glGetProgramiv(shader_program_id,GL_LINK_STATUS,&Result);glGetProgramiv(shader_program_id,GL_INFO_LOG_LENGTH,&infoLogLength);if(infoLogLength > 0){std::vector<char>program_error_message(infoLogLength + 1);glGetProgramInfoLog(shader_program_id,infoLogLength,nullptr,&program_error_message[0]);std::cout << "program link:" << &program_error_message[0] << std::endl;}//删除着色器编译结果glDeleteShader(vertex_shader_id);glDeleteShader(fragment_shader_id);
}void MyGLWidget::doMVP()
{//模型矩阵QMatrix4x4 model_matrix;model_matrix.setToIdentity();//观察矩阵QVector3D camera_pos = {4,4,-3};QVector3D look_dir = {1,0,0};QVector3D up_dir = {0,1,0};//lookAtQMatrix4x4 view_matrix;view_matrix.lookAt(camera_pos,look_dir,up_dir);//投影矩阵float angle = 45;float aspect = this->width() * 1.0f / this->height();//距离相机的位置,渲染范围:0.1-100float near_plane = 0.1f;float far_plane = 100.f;QMatrix4x4 projection_matrix;projection_matrix.perspective(angle,aspect,near_plane,far_plane);//获取uniform变量uniform_model_matrix_location = glGetUniformLocation(shader_program_id,"model_matrix");uniform_view_matrix_location = glGetUniformLocation(shader_program_id,"view_matrix");uniform_projection_matrix_location = glGetUniformLocation(shader_program_id,"projection_matrix");//矩阵传入shaderglUniformMatrix4fv(uniform_model_matrix_location,1,GL_FALSE,model_matrix.data());glUniformMatrix4fv(uniform_view_matrix_location,1,GL_FALSE,view_matrix.data());glUniformMatrix4fv(uniform_projection_matrix_location,1,GL_FALSE,projection_matrix.data());}
shader.vert
#version 330 core layout(location = 0) in vec3 vertex_posion_modelspace;
layout(location = 1) in vec3 vertex_color;
out vec3 fragment_color; uniform mat4 model_matrix;
uniform mat4 view_matrix;
uniform mat4 projection_matrix; void main(){ mat4 MVP_matrix = projection_matrix * view_matrix * model_matrix; fragment_color = vertex_color; gl_Position = MVP_matrix * vec4(vertex_posion_modelspace,1.0);
}
shader.frag
#version 330 core in vec3 fragment_color;
out vec3 color; void main(){ color = fragment_color;
}