通过OpenGL画三角形

通过绘制三角形,介绍顶点着色器和片段着色器

  • 片段着色器FragmentShader.h

    1
    2
    3
    4
    5
    6
    7
    8
    9
    #ifndef _FRAGMENTSHADER_H_
    #define _FRAGMENTSHADER_H_
    const char * fragmentShaderSource =
    "#version 330 core\n"
    //out:声明输出变量
    "out vec4 FragColor;\n"
    "void main(){\n"
    "FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);\n" //输出为固定颜色
    "}\0";
  • 顶点着色器VertexShader.h

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    #ifndef _VERTEXSHADER_H_
    #define _VERTEXSHADER_H_

    const char* vertexShaderSource =
    "#version 330 core\n" //声明版本并使用核心模式
    //in:顶点着色器中声明所有的输入顶点属性,现阶段只关心位置数据,所以只需要设置一个顶点属性。
    //GLSL(着色器语言)有一个向量类型,包含1--4个float分量。
    //layout (location = 0)设定了输入变量的位置值,相当于一个索引,可以在代码中通过glVertexAttribPointer指定传入参数、通过glEnableVertexAttribArray启用数据,放入aPos中。
    "layout (location = 0) in vec3 aPos;\n"
    "void main(){\n"
    //为了设置顶点着色器输出,必须把位置数据赋值给预定义的gl_Position变量,而gl_Position本身是一个vec4类型,在main函数最后需要将三分量的值转为四分量的值
    "gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);\n"
    "}\0";

    #endif // !_VERTEXSHADER_H_
  • main函数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    #include <glad\glad.h>
    #include <GLFW\glfw3.h>
    #include "VertexShader.h"
    #include "FragmentShader.h"
    #include <iostream>

    /*
    OpenGL基于3维坐标系,OpenGL的大部分工作就是3D坐标系转为2D坐标系。该转化过程由OpenGL图形渲染管线完成。分为两步:一、3D坐标转2D坐标;二、2D坐标转实际有颜色的像素
    */

    void framebuffer_size_callback(GLFWwindow* window, int width, int height);
    void processInput(GLFWwindow* window);

    const unsigned int SCR_WIDTH = 800;
    const unsigned int SCR_HEIGHT = 600;

    int main() {

    glfwInit();
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);

    #ifdef __APPLE__
    glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
    #endif

    GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "LearnOpenGL", NULL, NULL);
    if (window == NULL) {
    std::cout << "Failed to create GLFW window." << std::endl;
    glfwTerminate();
    return -1;
    }
    glfwMakeContextCurrent(window);
    glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);

    if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) {
    std::cout << "Failed to initialize GLAD" << std::endl;
    return -1;
    }

    //顶点着色器:是可编程着色器之一。现代OpenGL需要至少设置一个顶点和一个片段着色器。
    unsigned int vertexShader; //vertexShader:用ID来引用着色器对象
    vertexShader = glCreateShader(GL_VERTEX_SHADER); //创建一个着色器对象,GL_VERTEX_SHADER:顶点着色器

    glShaderSource(vertexShader, 1, &vertexShaderSource, NULL); //参数1:将要编译的着色器;参数2:源码字符串数量;参数3:顶点着色器真正的源码;参数4:指定字符串长度的数组。
    glCompileShader(vertexShader);
    //检查着色器编译时是否报错
    int success;
    char infoLog[512];
    glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success);
    if (!success) {
    glGetShaderInfoLog(vertexShader, 512, NULL, infoLog);
    std::cout << "ERROR:SHADER::VERTEX::COMPOLEATION_FAILED\n" << infoLog << std::endl;
    }

    //片段着色器:计算像素最后的颜色输出
    unsigned int fragmentShader;
    fragmentShader = glCreateShader(GL_FRAGMENT_SHADER); // GL_FRAGMENT_SHADER:片段着色器
    glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);
    glCompileShader(fragmentShader);
    glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &success);
    if (!success) {
    glGetShaderInfoLog(fragmentShader, 512, NULL, infoLog);
    std::cout << "ERROR::SHADER::FRAGMENT::COMPLATION_FAILED\n" << infoLog << std::endl;
    }

    //将着色器链接到一个用来渲染的着色器程序
    //着色器程序对象:是多个着色器合并之后并最终链接完成的版本。如果想要使用着色器,必须链接为一个着色器程序对象,然后渲染对象时激活这个着色器程序。
    unsigned int shaderProgram;
    shaderProgram = glCreateProgram(); //glCreateProgram:创建一个着色器程序,并返回ID引用
    glAttachShader(shaderProgram, vertexShader); //附加着色器
    glAttachShader(shaderProgram, fragmentShader);
    glLinkProgram(shaderProgram); //链接着色器

    //检测链接着色器程序是否失败
    glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success);
    if (!success) {
    glGetProgramInfoLog(shaderProgram, 512, NULL, infoLog);
    std::cout << "ERROR::SHADERPROGRAM::COMPOLEATION_FAILED\n" << infoLog << std::endl;
    }

    //着色器对象链接到程序对象之后,删除着色器对象。
    glDeleteShader(vertexShader);
    glDeleteShader(fragmentShader);

    //定义一个三角形顶点数组,作为输入发送给图形渲染管线的第一个处理阶段:顶点着色器。其会在GPU上创建内存用于储存顶点数据。
    //还需配置OpenGL如何解释该内存,指定其如何发送给显卡。顶点着色器会处理在内存中指定数量的顶点。
    float vertices[] = {
    -0.5f, -0.5f, 0.0f,
    0.5f, -0.5f, 0.0f,
    0.0f, 0.5f, 0.0f
    };

    //通过顶点缓冲对象(Vertex Buffer Objects,VBO)管理这个内存,其会在GPU内存(显存)中储存大量顶点。
    //优势在于,可一次性发送大批数据至显卡。因为CPU把数据发送到显卡相对较慢,所以需要尽量一次发送尽可能多的数据。
    unsigned int VBO; //顶点缓冲对象:OpenGL对象都有一个独一无二的ID
    glGenBuffers(1, &VBO); //glGenBuffers: 生成一个VBO对象,VBO保存ID。

    //顶点数组对象(Vertex Array Object):可以像顶点缓冲对象被绑定,任何随后的顶点属性都会储存在VAO中。
    //绑定后,当配置顶点属性指针时,只需要将那些调用执行一次,之后在绘制物体时,只需要绑定相应的VAO即可。
    //顶点数据对象的储存内容: 1、glEnableVertexAttribArray和glDisableVertexAttribArray的调用。
    // 2、通过glVertexAttribPointer设置的顶点属性配置。
    // 3、通过glVertexAttribPointer调用与顶点属性关联的顶点缓冲对象。
    unsigned int VAO;
    glGenVertexArrays(1, &VAO);
    glBindVertexArray(VAO);

    //GL_ARRAY_BUFFER:顶点缓冲对象类型,是缓冲对象类型中的一个。OpenGL允许同时绑定多个缓冲,只要是不同的缓冲类型。
    //glBindBuffer:绑定函数,将新创建的缓冲绑定到GL_ARRAY_BUFFER目标上。
    //调用完成后,接下来使用的任何缓冲调用都会用来配置当前绑定的缓冲。
    glBindBuffer(GL_ARRAY_BUFFER, VBO);

    //glBufferData:把之前定义的顶点数据复制到缓冲的内存中
    //参数:1、目标缓存的类型,顶点缓冲对象当前绑定到GL_ARRAY_BUFFER目标上;
    // 2、指定传输数据的大小(字节为单位);
    // 3、实际发送的数据;
    // 4、指定如何管理数据(GL_STATIC_DRAW(数据不会或几乎不会改变)、GL_DUNAMIC_DRAW(数据会被改变很多)、GL_STREAM_DRAW(数据每次绘制都会改变)),该选项会决定显卡是否将数据放入高速写入的内存部分。
    //调用完成后,顶点数据存储在显卡的内存中,用VBO顶点缓冲对象管理。
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

    //链接顶点属性:顶点着色器可以用任何顶点属性为形式的输入。我们必须手动指定输入数据部分对应的顶点属性,在渲染前指定OpenGL如何解释顶点数据。
    //glVertexAttribPointer: 参数1:指定配置的顶点属性,(layout(location = 0):顶点着色器中定义的顶点属性的位置值)
    // 参数2:顶点属性的大小。顶点属性是vec3,所以为3
    // 参数3:指定数据类型,
    // 参数4:是否希望数据被标准化,GL_TRUE时,数据会被映射到0(有符号型signed数据为-1)至1之间。
    // 参数5:步长,连续顶点属性组之间的间隔。当数组是紧密排列时,可用0来让OpenGL自行决定步长。
    // 参数6:类型必须为void*,表示位置数据在缓冲中起始位置的偏移量。由于位置数据在开头,所以无偏差。
    //每个顶点属性从一个VBO管理的内存中获取数据,VBO是在调用glVetexAttribPointer之前绑定到GL_ARRAY_BUFFER的VBO指定的。
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
    //以顶点属性位置值作为参数,启用顶点属性,默认顶点属性是禁用的。
    glEnableVertexAttribArray(0);

    //解绑VAO和VBO
    glBindBuffer(GL_ARRAY_BUFFER, 0);
    glBindVertexArray(0);
    while (!glfwWindowShouldClose(window)) {
    //检测输入
    processInput(window);

    //渲染
    glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT);

    //调用useProgram函数,激活程序对象。之后,着色器调用和渲染调用都将使用这个程序对象。
    glUseProgram(shaderProgram);
    glBindVertexArray(VAO);
    glDrawArrays(GL_TRIANGLES, 0, 3);

    glfwSwapBuffers(window);//交换指定窗口双缓冲区的前后缓冲
    glfwPollEvents();//该函数会调用相关的窗口和输入回调
    }

    glDeleteVertexArrays(1, &VAO);
    glDeleteBuffers(1, &VBO);

    glfwTerminate();
    return 0;
    }

    void processInput(GLFWwindow* window) {
    if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS) {
    glfwSetWindowShouldClose(window, true);
    }
    }

    void framebuffer_size_callback(GLFWwindow* window, int width, int height) {
    glViewport(0, 0, width, height);
    }