基于C++的OpenGL 01 之Hello Triangle
1. 引言
本文基于C++语言,描述OpenGL的绘制流程,这里描述的是OpenGL的核心模式(Core-profile)
本文基于Ubuntu 20.04.3 LTS系统,使用CMake构建程序,OpenGL环境搭建可参考:
笔者这里不过多描述每个名词、函数和细节,更详细的文档可以参考:
2. 流程综述
OpenGL的绘制流程(图形渲染管线,Graphics Pipeline)如下:
- 顶点着色(vertex shader)阶段将CPU传入的数据进行一定的变换处理
- 图元装配(shape assembly)阶段的就是上阶段的顶点数据处理成图元(如,三角形)
- 几何着色(geometry shader)阶段是根据一定规则将输入的图元变更或输出更多的图元(可选)
- 光栅化(rasterization)阶段的是将上阶段的图元进行计算得到图元占据的屏幕像素列表
- 片元着色(fragment)阶段是将上阶段生成的片元进行着色处理后
- 测试与混合阶段计算片元的深度、颜色等从而进行舍弃或保留
绘制流程繁琐,然而,我们能配置的只有三个蓝色的着色器部分。几何着色器可选,一般配置顶点着色器和片段着色器即可,即,以下步骤就是配置顶点着色器和片段着色器
3. 生成顶点数据
生成顶点缓冲对象(Vertex Buffer Objects, VBO)并加载数据:
float vertices[] = { 0.0f, 0.5f, 0.0f, -0.5f, -0.5f, 0.0f, 0.5f, -0.5f, 0.0f }; unsigned int VBO; glGenBuffers(1, &VBO); glBindBuffer(GL_ARRAY_BUFFER, VBO); glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
4. 链接属性数据
顶点数组对象(Vertex Array Object, VAO)与VBO绑定,用于保存属性数据(先绑定VAO,再创建VBO就会绑定到VAO上):
unsigned int VAO; glGenVertexArrays(1, &VAO); glBindVertexArray(VAO); float vertices[] = { 0.0f, 0.5f, 0.0f, -0.5f, -0.5f, 0.0f, 0.5f, -0.5f, 0.0f }; unsigned int VBO; glGenBuffers(1, &VBO); glBindBuffer(GL_ARRAY_BUFFER, VBO); glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void *)0); glEnableVertexAttribArray(0);
5. 创建顶点着色器
创建顶点着色器(Vertex Shader)并编译:
const char *vertexShaderSource = "#version 330 core\n" "layout (location = 0) in vec3 aPos;\n" "void main()\n" "{\n" "gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);\n" "}\0"; unsigned int vertexShader = glCreateShader(GL_VERTEX_SHADER); glShaderSource(vertexShader, 1, &vertexShaderSource, nullptr); glCompileShader(vertexShader);
6. 创建片段着色器
创建片段着色器(Fragment Shader)并编译:
const char *fragmentShaderSource = "#version 330 core\n" "out vec4 FragColor;" "void main()\n" "{\n" "FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);\n" "}\0"; unsigned int fragmentShader = glCreateShader(GL_FRAGMENT_SHADER); glShaderSource(fragmentShader, 1, &fragmentShaderSource, nullptr); glCompileShader(fragmentShader);
7. 链接着色器
着色器程序对象(Shader Program Object)是多个着色器合并之后并最终链接完成的版本:
unsigned int shaderProgram = glCreateProgram(); glAttachShader(shaderProgram, vertexShader); glAttachShader(shaderProgram, fragmentShader); glLinkProgram(shaderProgram);
8. 绘制
开始(循环)绘制:
glClearColor(0.2, 0.3, 0.3, 1.0); glClear(GL_COLOR_BUFFER_BIT); glUseProgram(shaderProgram); glBindVertexArray(VAO); glDrawArrays(GL_TRIANGLES, 0, 3);
9. 完整代码
基于GLFW与GLAD创建OpenGL开发环境,完整代码如下:
#include <glad/glad.h> #include <GLFW/glfw3.h> #include <iostream> void framebuffer_size_callback(GLFWwindow *window, int width, int height); void process_input(GLFWwindow *window); unsigned int *renderInit(); void render(unsigned int shaderProgram, unsigned int VAO); bool checkCompile(unsigned int shader); bool checkProgram(unsigned int shaderProgram); int main() { glfwInit(); glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); GLFWwindow *window = glfwCreateWindow(800, 600, "hello triangle", nullptr, nullptr); if (window == nullptr) { std::cout << "Faild to create window" << std::endl; glfwTerminate(); } glfwMakeContextCurrent(window); if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) { std::cout << "Faild to initialize glad" << std::endl; return -1; } glad_glViewport(0, 0, 800, 600); glfwSetFramebufferSizeCallback(window, framebuffer_size_callback); unsigned int *arr = renderInit(); while (!glfwWindowShouldClose(window)) { process_input(window); // render std::cout << arr[0] << " " << arr[1] << " " << arr[2] << std::endl; render(arr[0], arr[1]); glfwSwapBuffers(window); glfwPollEvents(); } glDeleteProgram(arr[0]); glDeleteVertexArrays(1, &arr[1]); glDeleteBuffers(1, &arr[2]); glfwTerminate(); return 0; } void framebuffer_size_callback(GLFWwindow *window, int width, int height) { glViewport(0, 0, width, height); } void process_input(GLFWwindow *window) { if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS) { glfwSetWindowShouldClose(window, true); } } unsigned int *renderInit() { unsigned int VAO; glGenVertexArrays(1, &VAO); glBindVertexArray(VAO); float vertices[] = { 0.0f, 0.5f, 0.0f, -0.5f, -0.5f, 0.0f, 0.5f, -0.5f, 0.0f }; unsigned int VBO; glGenBuffers(1, &VBO); glBindBuffer(GL_ARRAY_BUFFER, VBO); glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void *)0); glEnableVertexAttribArray(0); const char *vertexShaderSource = "#version 330 core\n" "layout (location = 0) in vec3 aPos;\n" "void main()\n" "{\n" "gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);\n" "}\0"; unsigned int vertexShader = glCreateShader(GL_VERTEX_SHADER); glShaderSource(vertexShader, 1, &vertexShaderSource, nullptr); glCompileShader(vertexShader); checkCompile(vertexShader); const char *fragmentShaderSource = "#version 330 core\n" "out vec4 FragColor;" "void main()\n" "{\n" "FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);\n" "}\0"; unsigned int fragmentShader = glCreateShader(GL_FRAGMENT_SHADER); glShaderSource(fragmentShader, 1, &fragmentShaderSource, nullptr); glCompileShader(fragmentShader); checkCompile(fragmentShader); unsigned int shaderProgram = glCreateProgram(); glAttachShader(shaderProgram, vertexShader); glAttachShader(shaderProgram, fragmentShader); glLinkProgram(shaderProgram); checkProgram(shaderProgram); glDeleteShader(vertexShader); glDeleteShader(fragmentShader); return new unsigned int[3]{shaderProgram, VAO, VBO}; } void render(unsigned int shaderProgram, unsigned int VAO) { glClearColor(0.2, 0.3, 0.3, 1.0); glClear(GL_COLOR_BUFFER_BIT); glUseProgram(shaderProgram); glBindVertexArray(VAO); glDrawArrays(GL_TRIANGLES, 0, 3); } bool checkCompile(unsigned int shader) { int success; char infoLog[512]; glGetShaderiv(shader, GL_COMPILE_STATUS, &success); if (!success) { glGetShaderInfoLog(shader, 512, NULL, infoLog); std::cout << "ERROR::SHADER::COMPILATION_FAILED\n" << infoLog << std::endl; return false; } return true; } bool checkProgram(unsigned int shaderProgram) { int success; char infoLog[512]; glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success); if (!success) { glGetProgramInfoLog(shaderProgram, 512, NULL, infoLog); return false; } return true; }
代码基于CMake构建,CMakeLists.txt
如下:
cmake_minimum_required(VERSION 3.3) set(CMAKE_C_STANDARD 11) set(CMAKE_CXX_STANDARD 14) project(HelloTriangle) find_package(glfw3 REQUIRED) find_package( OpenGL REQUIRED ) include_directories( ${OPENGL_INCLUDE_DIRS} ) file(GLOB project_file glad.c main.cpp) add_executable(${PROJECT_NAME} ${project_file}) target_link_libraries(${PROJECT_NAME} ${OPENGL_LIBRARIES} glfw)
main.cpp
是上述代码的文件名
使用CMake构建并编译运行:
$ cmake build . $ make $ ./HelloTriangle
运行结果如下:
10. 参考资料
[1]你好,三角形 - LearnOpenGL CN (learnopengl-cn.github.io)
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律