基于C++的OpenGL 01 之Hello Triangle

1. 引言

本文基于C++语言,描述OpenGL的绘制流程,这里描述的是OpenGL的核心模式(Core-profile)

本文基于Ubuntu 20.04.3 LTS系统,使用CMake构建程序,OpenGL环境搭建可参考:

笔者这里不过多描述每个名词、函数和细节,更详细的文档可以参考:

2. 流程综述

OpenGL的绘制流程(图形渲染管线,Graphics Pipeline)如下:

img

  • 顶点着色(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

运行结果如下:

image-20220715233727161

10. 参考资料

[1]你好,三角形 - LearnOpenGL CN (learnopengl-cn.github.io)

[2]【Learn OpenGL笔记】三角形(Triangle) - 知乎 (zhihu.com)

[3]基于Ubuntu搭建OpenGL开发环境 - 当时明月在曾照彩云归 - 博客园 (cnblogs.com)

posted @ 2022-07-13 23:48  当时明月在曾照彩云归  阅读(156)  评论(0编辑  收藏  举报