【(一)OpenGL入门】2.画一个三角形

目录

1. 基础知识

1-1 图形渲染管线

1-2 着色器

1-3 图形渲染管线各阶段的流程

1-4 VAO与VBO

2. 使用默认着色器绘制三角形

2-1 创建新工程

2-2 完整代码

3. 编写着色器绘制三角形

3-1 认识顶点着色器和片段着色器

3-2 完整代码

4.元素缓冲对象EBO

4-1简单的画一个矩形

4-2使用EBO画一个矩形

4-3 完整代码

5.练习

5-1 添加更多顶点到数据中,使用glDrawArrays,尝试绘制两个彼此相连的三角形

5-2 创建相同的两个三角形,但对它们的数据使用不同的VAO和VBO

5-3 创建两个着色器程序,第二个程序使用一个不同的片段着色器,输出黄色;再次绘制这两个三角形,让其中一个输出为黄色

1. 基础知识

        正式画三角形之前,请先了解以下知识点。

1-1 图形渲染管线

        图形渲染管线(Graphics Pipeline,大多译为管线,实际上指的是一堆原始图形数据途经一个输送管道,期间经过各种变化处理,最终出现在屏幕的过程)

渲染管线做了哪些事情?

1.将3D坐标转换为2D坐标。(opnegl是3D空间,而屏幕是2D的)

2.将2D坐标转换为实际的色彩像素。

1-2 着色器

        在GPU上为每一个图形渲染管线阶段快速处理数据的小程序。GPU上有数千个核,每个核上都可以运行这种小程序。Opengl Version3.3核心模式也叫可编程渲染管线模式,可编程的部分就是各个着色器。

        着色器是用着色器语言GLSL编写的,类似于C语言。通常我们对着色器进行编写并编译,就可以在程序中使用它了。

1-3 图形渲染管线各阶段的流程

        了解以上两个知识点后我们再来看下面这张图,可以直观感受一个三角形在渲染管线各个阶段的绘制过程。

        画一个三角形需要先向管线中输入三个顶点,每个顶点由3个float类型定义,代表顶点在opengl坐标系中x,y,z方向的位置。而opengl坐标系为标准设备坐标,它是一种在(-1, 1)区间范围内的立方体空间。输入顶点后经过渲染管线中的顶点着色器处理后将三个顶点转化为标准设备坐标,后面可以通过glviewport函数转换为屏幕坐标,作为光栅化和片段着色器阶段的输入。从顶点着色器输出的数据输入到图元装配阶段,根据指定的形状装配图元,得到一个三角形。几何着色器将图元装配输出的一系列顶点集合作为输入,可以通过产生新的顶点构造出新的图元,来生成其它形状,这里它生成了另一个三角形。几何着色器的输出会被传入光栅化阶段,这里它会把图元映射为最终屏幕上相应的像素,生成供片段着色器使用的片段。在片段着色器运行之前会执行裁切。裁切会丢弃超出你的视图以外的所有像素,用来提升执行效率。片段着色器的主要目的是计算一个像素的最终颜色,这也是所有OpenGL高级效果产生的地方。通常,片段着色器包含3D场景的数据(比如光照、阴影、光的颜色等等),这些数据可以被用来计算最终像素的颜色。在所有对应颜色值确定以后,最终的对象将会被传到最后一个阶段,我们叫做测试和混合阶段。这个阶段检测片段的对应的深度值(后面会讲),用它们来判断这个像素是其它物体的前面还是后面,决定是否应该丢弃。这个阶段也会检查alpha值(alpha值定义了一个物体的透明度)并对物体进行混合。所以,即使在片段着色器中计算出来了一个像素输出的颜色,在渲染多个三角形的时候最后的像素颜色也可能完全不同。

        可以看到,图形渲染管线非常复杂,它包含很多可配置的部分。然而,对于大多数场合,我们只需要配置顶点和片段着色器就行了。几何着色器是可选的,通常使用它默认的着色器就行了。

1-4 VAO与VBO

        画三角型就得输入顶点,那么如何输入顶点?

        使用VBO(顶点缓冲对象)在GPU上创建内存,存储顶点数据,顶点数据的缓冲类型是GL_ARRAY_BUFFER。但是还得创建VAO(顶点数组对象)来管理,因为opengl会直接通过VAO来解释这些数据。VAO并不实际储存顶点数据,只是储存顶点结构定义,数组中每一项都对应一个属性的解析。

        

        如图,因为我们定义的顶点坐标vertices只有顶点属性,所以采用VAO1的形式,只有一个属性,相应的VBO1中每一个元素就是一个顶点vertex,保存其x,y,z值。这里VBO相当于一个二维数组。而VBO中每个元素之间的偏移被称为步长(strire),一个元素是3个float类型数据,所以步长是12。VBO1由于存放在VAO1的首地址位置,所以基地址的偏移量offset为0。

        如果VBO中除了顶点属性还储存了其它属性,那么VAO就有两项属性,它们的存储位置如上图VBO2所示,个相同属性之间的步长会增加,因为中间夹杂着其它属性。而第二个属性的基地址就在第一个属性第一个元素之后。

2. 使用默认着色器绘制三角形

        我们可以先不着急编写着色器,使用系统自带的着色器画一个三角形出来,旨在使用上面学习的知识,先避开着色器部分,更快的用opengl画出一个三角形。

2-1 创建新工程

        基于上一讲编译好的库,我们重新创建一个工程。

        打开vs2022--->选择创建空的C++项目--->右击解决方案的属性--->选择VC++目录,添加库文件和头文件路径。

再在输入项里面添加依赖项。

将glad.c文件添加到项目中,至此该工程建立完毕,剩下的就是写代码了。

2-2 完整代码

我们基于上节课的代码进行添加,新添加的代码标注为1~9。


#include <glad.h>   //glad.h需要在glfw.h之前
#include <glfw3.h>
#include <iostream>

//1.定义三角形顶点数据
float vertices[] = {
    -0.5f, -0.5f, 0.0f,
    0.5f, -0.5f, 0.0f,
    0.0f, 0.5f, 0.0f
};

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

int main()
{
    //初始化glfw:选择opengl 3.3~4.2核心模式,取决于你的显卡驱动
    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(800, 600, "LearnOpenGL", NULL, NULL); //创建窗体对象:参数1:opengl窗口大小;参数2:窗口标题
    if (window == NULL)
    {
        std::cout << "Failed to create GLFW window" << std::endl;
        glfwTerminate();
        return -1;
    }
    glfwMakeContextCurrent(window);                                     //设置当前窗口上下文设置为当前线程的上下文,之后才能在窗口进行绘制

    if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))            //glad:加载所有OpenGL函数指针,固定写法就这一行
    {
        std::cout << "Failed to initialize GLAD" << std::endl;
        return -1;
    }
    glViewport(0, 0, 600, 400);                                         //设置opengl实际渲染的区域大小;0,0为左下角坐标

    glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);  //注册窗口大小改变回调函数

    //2.创建VBO和VAO对象并赋予ID值
    unsigned int VBO, VAO;
    glGenVertexArrays(1, &VAO);
    glGenBuffers(1, &VBO);

    //3.绑定VBO,VAO对象
    glBindVertexArray(VAO);
    glBindBuffer(GL_ARRAY_BUFFER, VBO);
    
    //4.开辟并填充数据
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
    //arg1:目标buffer对象,数组对象
    //arg2:数据大小
    //arg3:数据地址,如果为NULL则该函数只是开辟内存
    //arg4:作用,静态数据绘制

    //5.告知Shader如何解析缓冲里的属性值
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
    //arg1:第1个VAO从0开始,只有一个VAO表示坐标属性。
    //arg2:该属性里有3个坐标
    //arg3:浮点类型
    //arg4:是否标准化
    //arg5:步长
    //arg6:偏移量,顶点数组offset从0开始就是0
    
    //6.开启VAO管理的第一个属性值,第一个VAO编号为0
    glEnableVertexAttribArray(0);

    //7.解绑VBO,VAO
    glBindBuffer(GL_ARRAY_BUFFER, 0);
    glBindVertexArray(0);

    while (!glfwWindowShouldClose(window))                              //检查一次GLFW是否被要求退出,循环一次就是刷新一帧
    {
        processInput(window);

        glClearColor(0.2f, 0.3f, 0.3f, 1.0f);                           //设置颜色:深绿色
        glClear(GL_COLOR_BUFFER_BIT);                                   //刷新屏幕,使用设置的颜色
        
        //9.上面把VAO解绑了,这里必须重新绑定否则绘制不出来三角形
        glBindVertexArray(VAO);
        //8.渲染线程里加上绘制三角形的命令
        glDrawArrays(GL_TRIANGLES, 0, 3);

        glfwSwapBuffers(window);                                        //交换颜色缓冲(双缓冲):储存着GLFW窗口每一个像素颜色值作为输出显示在屏幕上
        glfwPollEvents();                                               //轮训检查输入设备触发事件
    }
    glfwTerminate();                                                    //释放分配的所有glfw资源
    return 0;
}

void framebuffer_size_callback(GLFWwindow* window, int width, int height)   //窗口大小被改变就重新设置渲染区域大小
{
    glViewport(0, 0, width, height);
}

void processInput(GLFWwindow* window)
{
    if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)              //按Esc键关闭界面
        glfwSetWindowShouldClose(window, true);
}

 运行结果:

3. 编写着色器绘制三角形

3-1 认识顶点着色器和片段着色器

        1-3 后面提到,我们至少需要编写顶点着色器和片段着色器,几何着色器可以使用默认的。下图是顶点着色器和片段着色器的简单声明和定义。

/*顶点着色器*/
#version 330 core
layout (location = 0) in vec3 aPos;
//layout:布局;
//location = 0:顶点属性的位置;
//in:输入属性,从显存拿到着色器;
//vec3 aPos:三分量变量

void main()//处理三个顶点,它就循环三次
{
    gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);//顶点着色器输出4分量的数据
}

/*片段着色器*/
#version 330 core
out vec4 FragColor;    //声明输出变量为4分量的数据

void main()//要渲染光栅化后每一个像素,有多少像素就循环执行多少遍
{
    FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);    //输出4分量的数据,表示最终输出颜色
} 

        我们需要做的就是把着色器源码在代码中动态编译生成着色器对象,然后再把顶点着色器对象和片段着色器对象链接起来形成一个着色器程序,即形成一个完整的流水线,最终使用着色器程序给图形着色。

3-2 完整代码

        继续在2-2代码上进行添加,添加标签为a~d。

#include <glad.h>   //glad.h需要在glfw.h之前
#include <glfw3.h>
#include <iostream>

//1.定义三角形顶点数据
float vertices[] = {
    -0.5f, -0.5f, 0.0f,
    0.5f, -0.5f, 0.0f,
    0.0f, 0.5f, 0.0f
};

//a.着色器源码
//顶点着色器源码
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";

//片段着色器源码
const char* fragmentShaderSource = "#version 330 core\n"
"out vec4 FragColor;\n"
"void main()\n"
"{\n"
" FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);\n"
"}\n\0";

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

int main()
{
    //初始化glfw:选择opengl 3.3~4.2核心模式,取决于你的显卡驱动
    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(800, 600, "LearnOpenGL", NULL, NULL); //创建窗体对象:参数1:opengl窗口大小;参数2:窗口标题
    if (window == NULL)
    {
        std::cout << "Failed to create GLFW window" << std::endl;
        glfwTerminate();
        return -1;
    }
    glfwMakeContextCurrent(window);                                     //设置当前窗口上下文设置为当前线程的上下文,之后才能在窗口进行绘制

    if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))            //glad:加载所有OpenGL函数指针,固定写法就这一行
    {
        std::cout << "Failed to initialize GLAD" << std::endl;
        return -1;
    }
    glViewport(0, 0, 600, 400);                                         //设置opengl实际渲染的区域大小;0,0为左下角坐标

    glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);  //注册窗口大小改变回调函数

    //b.创建并编译着色器
    //顶点着色器
    unsigned int vertexShader = glCreateShader(GL_VERTEX_SHADER);//创建顶点着色器,arg:顶点着色器
    glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);//获取顶点着色器源码
    //arg1:着色器类型
    //arg2:源码是一个字符串表示
    //arg3:字符串长度
    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::COMPILATION_FAILED\n" << infoLog << std::endl;
    }
    // 片段着色器
    unsigned int fragmentShader = glCreateShader(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::COMPILATION_FAILED\n" << infoLog << std::endl;
    }

    //c.把顶点着色器和片段着色器链接成一个着色器程序,即一个流水线
    //着色器程序
    unsigned int shaderProgram = glCreateProgram();
    glAttachShader(shaderProgram, vertexShader);
    glAttachShader(shaderProgram, fragmentShader);
    glLinkProgram(shaderProgram);
    //链接错误检查
    glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success);
    if (!success) {
        glGetProgramInfoLog(shaderProgram, 512, NULL, infoLog);
        std::cout << "ERROR::SHADER::PROGRAM::LINKING_FAILED\n" << infoLog << std::endl;
    }
    //链接成一个完整着色器程序后释放掉顶点着色器和片段着色器
    glDeleteShader(vertexShader);
    glDeleteShader(fragmentShader);


    //2.创建VBO和VAO对象并赋予ID值
    unsigned int VBO, VAO;
    glGenVertexArrays(1, &VAO);
    glGenBuffers(1, &VBO);

    //3.绑定VBO,VAO对象
    glBindVertexArray(VAO);
    glBindBuffer(GL_ARRAY_BUFFER, VBO);
    
    //4.开辟并填充数据
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
    //arg1:目标buffer对象,数组对象
    //arg2:数据大小
    //arg3:数据地址,如果为NULL则该函数只是开辟内存
    //arg4:作用,静态数据绘制

    //5.告知Shader如何解析缓冲里的属性值
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
    //arg1:第1个VAO从0开始,只有一个VAO表示坐标属性。
    //arg2:该属性里有3个坐标
    //arg3:浮点类型
    //arg4:是否标准化
    //arg5:步长
    //arg6:偏移量,顶点数组offset从0开始就是0
    
    //6.开启VAO管理的第一个属性值,第一个VAO编号为0
    glEnableVertexAttribArray(0);

    //7.解绑VBO,VAO
    glBindBuffer(GL_ARRAY_BUFFER, 0);
    glBindVertexArray(0);

    while (!glfwWindowShouldClose(window))                              //检查一次GLFW是否被要求退出,循环一次就是刷新一帧
    {
        processInput(window);

        glClearColor(0.2f, 0.3f, 0.3f, 1.0f);                           //设置颜色:深绿色
        glClear(GL_COLOR_BUFFER_BIT);                                   //刷新屏幕,使用设置的颜色
        
        //d.选择使用着色器给三角形上色,即上面创建链接好的流水线
        glUseProgram(shaderProgram);

        //9.上面把VAO解绑了,这里必须重新绑定否则绘制不出来三角形
        glBindVertexArray(VAO);
        //8.渲染线程里加上绘制三角形的命令
        glDrawArrays(GL_TRIANGLES, 0, 3);

        glfwSwapBuffers(window);                                        //交换颜色缓冲(双缓冲):储存着GLFW窗口每一个像素颜色值作为输出显示在屏幕上
        glfwPollEvents();                                               //轮训检查输入设备触发事件
    }

    //e.渲染结束后把VAO,VBO和着色器都释放掉
    glDeleteVertexArrays(1, &VAO);
    glDeleteBuffers(1, &VBO);
    glDeleteProgram(shaderProgram);

    glfwTerminate();                                                    //释放分配的所有glfw资源
    return 0;
}

void framebuffer_size_callback(GLFWwindow* window, int width, int height)   //窗口大小被改变就重新设置渲染区域大小
{
    glViewport(0, 0, width, height);
}

void processInput(GLFWwindow* window)
{
    if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)              //按Esc键关闭界面
        glfwSetWindowShouldClose(window, true);
}

运行结果:

4.元素缓冲对象EBO

4-1简单的画一个矩形

        接下来我们要绘制一个矩形,一个矩形可以是把两个三角形加起来。修改之前1.和8.的代码,编译运行后就画出了一个矩形。

float vertices[] = {
    // 第一个三角形 
    0.5f, 0.5f, 0.0f,  // 右上 
    0.5f, -0.5f, 0.0f, // 右下
    -0.5f, 0.5f, 0.0f, // 左上
    // 第二个三角形 
    0.5f, -0.5f, 0.0f, // 右下 
    -0.5f, -0.5f, 0.0f,// 左下
    -0.5f, 0.5f, 0.0f  // 左上
};

        //8.渲染线程里加上绘制三角形的命令
        glDrawArrays(GL_TRIANGLES, 0, 6);

4-2使用EBO画一个矩形

        但是,一个矩形明明4个顶点就够了,多出来两个顶点有点浪费内存,在大项目中可能存在成千上万个三角形,这就多了很多开销,可以删掉2个重叠的顶点,并且把它们用索引排序。这种索引绘制正是我们的解决方案。EBO是一个缓冲区,就像一个顶点缓冲区对象一样,它存储OpenGL 用来决定要绘制哪些顶点的索引。

        再次修改上面4-1的代码也可以得到一个矩形,但是还是没有使用到EBO,因为我们没有对其进行绑定操作,只是用DrawElement替换掉了DrawArray,并把索引通过indices数组传进来而已,一般不推荐这样使用。

float vertices[] = {
0.5f, 0.5f, 0.0f, // 右上   0
0.5f, -0.5f, 0.0f, // 右下  1
-0.5f, -0.5f, 0.0f, // 左下 2
-0.5f, 0.5f, 0.0f // 左上   3
};

unsigned int indices[] = { // 注意,我们从零开始算!
0, 1, 3, // 第一个三角形 
1, 2, 3 // 第二个三角形 
};

        //8.渲染线程里加上绘制三角形的命令
        //glDrawArrays(GL_TRIANGLES, 0, 6);

        //10.绘制元素
        glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, indices);

          正确使用EBO的方法是像VAO一样创一个对象,绑定它,这样VAO就会记录EBO的相关信息,glDrawElements的第4个参数就可以写成0了。绑定的顺序是先绑定VAO再绑定EBO,因为先绑定EBO的话,此时VAO都不在肯定无法记录接下来的事情。相反在解绑的时候,我们也需要先解绑EBO最后解绑VAO,因为我们先解绑VAO的话它还是记录着之前EBO绑定的状态,解绑将会失败。如果我们先解绑EBO,VAO就会记录EBO已经解绑。它们之间的关系如下:

                      

按照上面的理论改进代码如下:

    //2.创建VBO和VAO、EBO对象并赋予ID值
    unsigned int VBO, VAO, EBO;
    glGenVertexArrays(1, &VAO);
    glGenBuffers(1, &VBO);
    glGenBuffers(1, &EBO);

    //3.绑定VBO,VAO、EBO对象
    glBindVertexArray(VAO);
    glBindBuffer(GL_ARRAY_BUFFER, VBO);    
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
 
    //4.开辟并填充数据
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
    
    ......

    //7.解绑VBO,VAO,EBO 
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);//先解绑EBO让VAO记录EBO已经被解绑
    glBindVertexArray(0);
    glBindBuffer(GL_ARRAY_BUFFER, 0);//最后解绑VAO,EBO和VAO解绑顺序交换后即使在whie中不重新绑定也会画出矩形,证明解绑EBO失败

    while (!glfwWindowShouldClose(window))                              
    {
        ......

        //9.上面把VAO解绑了,这里必须重新绑定否则绘制不出来三角形
        glBindVertexArray(VAO);
        
        //11.绑定EBO,注意在绑定VAO之后才绑定EBO
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);

        ......     
    }

    //e.渲染结束后把VAO,VBO、EBO和着色器都释放掉
    glDeleteVertexArrays(1, &VAO);
    glDeleteBuffers(1, &VBO);
    glDeleteBuffers(1, &EBO);//销毁的顺序没有规定
    glDeleteProgram(shaderProgram);

  此时矩形已经可以绘制出来了。

 

如果我们想让上面的图形呈现出是由两个三角形组成的话,可以添加一行代码。

    //以线框模式渲染
    glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);

4-3 完整代码

#include <glad.h>   //glad.h需要在glfw.h之前
#include <glfw3.h>
#include <iostream>

float vertices[] = {
0.5f, 0.5f, 0.0f, // 右上   0
0.5f, -0.5f, 0.0f, // 右下  1
-0.5f, -0.5f, 0.0f, // 左下 2
-0.5f, 0.5f, 0.0f // 左上   3
};

unsigned int indices[] = { // 注意,我们从零开始算!
0, 1, 3, // 第一个三角形 
1, 2, 3 // 第二个三角形 
};

//a.着色器源码
//顶点着色器源码
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";

//片段着色器源码
const char* fragmentShaderSource = "#version 330 core\n"
"out vec4 FragColor;\n"
"void main()\n"
"{\n"
" FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);\n"
"}\n\0";

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

int main()
{
    //初始化glfw:选择opengl 3.3~4.2核心模式,取决于你的显卡驱动
    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(800, 600, "LearnOpenGL", NULL, NULL); //创建窗体对象:参数1:opengl窗口大小;参数2:窗口标题
    if (window == NULL)
    {
        std::cout << "Failed to create GLFW window" << std::endl;
        glfwTerminate();
        return -1;
    }
    glfwMakeContextCurrent(window);                                     //设置当前窗口上下文设置为当前线程的上下文,之后才能在窗口进行绘制

    if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))            //glad:加载所有OpenGL函数指针,固定写法就这一行
    {
        std::cout << "Failed to initialize GLAD" << std::endl;
        return -1;
    }
    glViewport(0, 0, 600, 400);                                         //设置opengl实际渲染的区域大小;0,0为左下角坐标

    glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);  //注册窗口大小改变回调函数

    //以线框模式渲染
    glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);

    //b.创建并编译着色器
    //顶点着色器
    unsigned int vertexShader = glCreateShader(GL_VERTEX_SHADER);//创建顶点着色器,arg:顶点着色器
    glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);//获取顶点着色器源码
    //arg1:着色器类型
    //arg2:源码是一个字符串表示
    //arg3:字符串长度
    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::COMPILATION_FAILED\n" << infoLog << std::endl;
    }
    // 片段着色器
    unsigned int fragmentShader = glCreateShader(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::COMPILATION_FAILED\n" << infoLog << std::endl;
    }

    //c.把顶点着色器和片段着色器链接成一个着色器程序,即一个流水线
    //着色器程序
    unsigned int shaderProgram = glCreateProgram();
    glAttachShader(shaderProgram, vertexShader);
    glAttachShader(shaderProgram, fragmentShader);
    glLinkProgram(shaderProgram);
    //链接错误检查
    glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success);
    if (!success) {
        glGetProgramInfoLog(shaderProgram, 512, NULL, infoLog);
        std::cout << "ERROR::SHADER::PROGRAM::LINKING_FAILED\n" << infoLog << std::endl;
    }
    //链接成一个完整着色器程序后释放掉顶点着色器和片段着色器
    glDeleteShader(vertexShader);
    glDeleteShader(fragmentShader);

    //2.创建VBO和VAO、EBO对象并赋予ID值
    unsigned int VBO, VAO, EBO;
    glGenVertexArrays(1, &VAO);
    glGenBuffers(1, &VBO);
    glGenBuffers(1, &EBO);

    //3.绑定VBO,VAO、EBO对象
    glBindVertexArray(VAO);
    glBindBuffer(GL_ARRAY_BUFFER, VBO);    
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
 
    //4.开辟并填充数据
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);

    //arg1:目标buffer对象,数组对象
    //arg2:数据大小
    //arg3:数据地址,如果为NULL则该函数只是开辟内存
    //arg4:作用,静态数据绘制

    //5.告知Shader如何解析缓冲里的属性值
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
    //arg1:第1个VAO从0开始,只有一个VAO表示坐标属性。
    //arg2:该属性里有3个坐标
    //arg3:浮点类型
    //arg4:是否标准化
    //arg5:步长
    //arg6:偏移量,顶点数组offset从0开始就是0
    
    //6.开启VAO管理的第一个属性值,第一个VAO编号为0
    glEnableVertexAttribArray(0);

    //7.解绑VBO,VAO,EBO 
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
    glBindVertexArray(0);
    glBindBuffer(GL_ARRAY_BUFFER, 0);

    while (!glfwWindowShouldClose(window))                              //检查一次GLFW是否被要求退出,循环一次就是刷新一帧
    {
        processInput(window);

        glClearColor(0.2f, 0.3f, 0.3f, 1.0f);                           //设置颜色:深绿色
        glClear(GL_COLOR_BUFFER_BIT);                                   //刷新屏幕,使用设置的颜色
        
        //d.选择使用着色器给三角形上色,即上面创建链接好的流水线
        glUseProgram(shaderProgram);

        //9.上面把VAO解绑了,这里必须重新绑定否则绘制不出来三角形
        glBindVertexArray(VAO);
        
        //11.绑定EBO,注意在绑定VAO之后才绑定EBO
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);

        //8.渲染线程里加上绘制三角形的命令
        //glDrawArrays(GL_TRIANGLES, 0, 6);

        //10.绘制元素
        glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0/*&indices*/);

        glfwSwapBuffers(window);                                        //交换颜色缓冲(双缓冲):储存着GLFW窗口每一个像素颜色值作为输出显示在屏幕上
        glfwPollEvents();                                               //轮训检查输入设备触发事件
    }
    //e.渲染结束后把VAO,VBO和着色器都释放掉
    glDeleteVertexArrays(1, &VAO);
    glDeleteBuffers(1, &VBO);
    glDeleteBuffers(1, &EBO);//销毁的顺序没有规定
    glDeleteProgram(shaderProgram);

    glfwTerminate();                                                    //释放分配的所有glfw资源
    return 0;
}

void framebuffer_size_callback(GLFWwindow* window, int width, int height)   //窗口大小被改变就重新设置渲染区域大小
{
    glViewport(0, 0, width, height);
}

void processInput(GLFWwindow* window)
{
    if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)              //按Esc键关闭界面
        glfwSetWindowShouldClose(window, true);
}

运行结果:

        由几次运行结果可知,最后呈现在窗口的画面是否是正方形和输入的标准设备坐标无关,而和glviewport后的屏幕坐标有关,片段着色器处理后的像素点的形状也是这么来的。

5.练习

5-1 添加更多顶点到数据中,使用glDrawArrays,尝试绘制两个彼此相连的三角形

        简单的方法就是定义6个顶点,将它们拍好位置然后画出来6个顶点就好了。

#include <glad.h>   //glad.h需要在glfw.h之前
#include <glfw3.h>
#include <iostream>

float vertices[] = {
-0.9f, -0.5f, 0.0f, // left 
-0.0f, -0.5f, 0.0f, // right 
-0.45f, 0.5f, 0.0f, // top 

0.0f, -0.5f, 0.0f, // left 
0.9f, -0.5f, 0.0f, // right 
0.45f, 0.5f, 0.0f // top 
};

//a.着色器源码
//顶点着色器源码
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";

//片段着色器源码
const char* fragmentShaderSource = "#version 330 core\n"
"out vec4 FragColor;\n"
"void main()\n"
"{\n"
" FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);\n"
"}\n\0";

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

int main()
{
    //初始化glfw:选择opengl 3.3~4.2核心模式,取决于你的显卡驱动
    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(800, 600, "LearnOpenGL", NULL, NULL); //创建窗体对象:参数1:opengl窗口大小;参数2:窗口标题
    if (window == NULL)
    {
        std::cout << "Failed to create GLFW window" << std::endl;
        glfwTerminate();
        return -1;
    }
    glfwMakeContextCurrent(window);                                     //设置当前窗口上下文设置为当前线程的上下文,之后才能在窗口进行绘制

    if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))            //glad:加载所有OpenGL函数指针,固定写法就这一行
    {
        std::cout << "Failed to initialize GLAD" << std::endl;
        return -1;
    }
    glViewport(0, 0, 600, 400);                                         //设置opengl实际渲染的区域大小;0,0为左下角坐标

    glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);  //注册窗口大小改变回调函数


    //b.创建并编译着色器
    //顶点着色器
    unsigned int vertexShader = glCreateShader(GL_VERTEX_SHADER);//创建顶点着色器,arg:顶点着色器
    glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);//获取顶点着色器源码
    //arg1:着色器类型
    //arg2:源码是一个字符串表示
    //arg3:字符串长度
    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::COMPILATION_FAILED\n" << infoLog << std::endl;
    }
    // 片段着色器
    unsigned int fragmentShader = glCreateShader(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::COMPILATION_FAILED\n" << infoLog << std::endl;
    }

    //c.把顶点着色器和片段着色器链接成一个着色器程序,即一个流水线
    //着色器程序
    unsigned int shaderProgram = glCreateProgram();
    glAttachShader(shaderProgram, vertexShader);
    glAttachShader(shaderProgram, fragmentShader);
    glLinkProgram(shaderProgram);
    //链接错误检查
    glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success);
    if (!success) {
        glGetProgramInfoLog(shaderProgram, 512, NULL, infoLog);
        std::cout << "ERROR::SHADER::PROGRAM::LINKING_FAILED\n" << infoLog << std::endl;
    }
    //链接成一个完整着色器程序后释放掉顶点着色器和片段着色器
    glDeleteShader(vertexShader);
    glDeleteShader(fragmentShader);

    //2.创建VBO和VAO对象并赋予ID值
    unsigned int VBO, VAO;
    glGenVertexArrays(1, &VAO);
    glGenBuffers(1, &VBO);

    //3.绑定VBO,VAO对象
    glBindVertexArray(VAO);
    glBindBuffer(GL_ARRAY_BUFFER, VBO);    
 
    //4.开辟并填充数据
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

    //arg1:目标buffer对象,数组对象
    //arg2:数据大小
    //arg3:数据地址,如果为NULL则该函数只是开辟内存
    //arg4:作用,静态数据绘制

    //5.告知Shader如何解析缓冲里的属性值
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
    //arg1:第1个VAO从0开始,只有一个VAO表示坐标属性。
    //arg2:该属性里有3个坐标
    //arg3:浮点类型
    //arg4:是否标准化
    //arg5:步长
    //arg6:偏移量,顶点数组offset从0开始就是0
    
    //6.开启VAO管理的第一个属性值,第一个VAO编号为0
    glEnableVertexAttribArray(0);

    //7.解绑VBO,VAO
    glBindVertexArray(0);
    glBindBuffer(GL_ARRAY_BUFFER, 0);

    while (!glfwWindowShouldClose(window))                              //检查一次GLFW是否被要求退出,循环一次就是刷新一帧
    {
        processInput(window);

        glClearColor(0.2f, 0.3f, 0.3f, 1.0f);                           //设置颜色:深绿色
        glClear(GL_COLOR_BUFFER_BIT);                                   //刷新屏幕,使用设置的颜色
        
        //d.选择使用着色器给三角形上色,即上面创建链接好的流水线
        glUseProgram(shaderProgram);

        //9.上面把VAO解绑了,这里必须重新绑定否则绘制不出来三角形
        glBindVertexArray(VAO);
       
        //8.渲染线程里加上绘制三角形的命令
        glDrawArrays(GL_TRIANGLES, 0, 6);

        glfwSwapBuffers(window);                                        //交换颜色缓冲(双缓冲):储存着GLFW窗口每一个像素颜色值作为输出显示在屏幕上
        glfwPollEvents();                                               //轮训检查输入设备触发事件
    }
    //e.渲染结束后把VAO,VBO和着色器都释放掉
    glDeleteVertexArrays(1, &VAO);
    glDeleteBuffers(1, &VBO);
    glDeleteProgram(shaderProgram);

    glfwTerminate();                                                    //释放分配的所有glfw资源
    return 0;
}

void framebuffer_size_callback(GLFWwindow* window, int width, int height)   //窗口大小被改变就重新设置渲染区域大小
{
    glViewport(0, 0, width, height);
}

void processInput(GLFWwindow* window)
{
    if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)              //按Esc键关闭界面
        glfwSetWindowShouldClose(window, true);
}

5-2 创建相同的两个三角形,但对它们的数据使用不同的VAO和VBO

        可以定义两个三角形,使用两个VAO和VBO来画。

#include <glad.h>   //glad.h需要在glfw.h之前
#include <glfw3.h>
#include <iostream>

float firstTriangle[] = {
    -0.9f, -0.5f, 0.0f,  // left 
    -0.0f, -0.5f, 0.0f,  // right
    -0.45f, 0.5f, 0.0f,  // top 
};
float secondTriangle[] = {
    0.0f, -0.5f, 0.0f,  // left
    0.9f, -0.5f, 0.0f,  // right
    0.45f, 0.5f, 0.0f   // top 
};

//a.着色器源码
//顶点着色器源码
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";

//片段着色器源码
const char* fragmentShaderSource = "#version 330 core\n"
"out vec4 FragColor;\n"
"void main()\n"
"{\n"
" FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);\n"
"}\n\0";

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

int main()
{
    //初始化glfw:选择opengl 3.3~4.2核心模式,取决于你的显卡驱动
    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(800, 600, "LearnOpenGL", NULL, NULL); //创建窗体对象:参数1:opengl窗口大小;参数2:窗口标题
    if (window == NULL)
    {
        std::cout << "Failed to create GLFW window" << std::endl;
        glfwTerminate();
        return -1;
    }
    glfwMakeContextCurrent(window);                                     //设置当前窗口上下文设置为当前线程的上下文,之后才能在窗口进行绘制

    if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))            //glad:加载所有OpenGL函数指针,固定写法就这一行
    {
        std::cout << "Failed to initialize GLAD" << std::endl;
        return -1;
    }
    glViewport(0, 0, 600, 400);                                         //设置opengl实际渲染的区域大小;0,0为左下角坐标

    glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);  //注册窗口大小改变回调函数


    //b.创建并编译着色器
    //顶点着色器
    unsigned int vertexShader = glCreateShader(GL_VERTEX_SHADER);//创建顶点着色器,arg:顶点着色器
    glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);//获取顶点着色器源码
    //arg1:着色器类型
    //arg2:源码是一个字符串表示
    //arg3:字符串长度
    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::COMPILATION_FAILED\n" << infoLog << std::endl;
    }
    // 片段着色器
    unsigned int fragmentShader = glCreateShader(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::COMPILATION_FAILED\n" << infoLog << std::endl;
    }

    //c.把顶点着色器和片段着色器链接成一个着色器程序,即一个流水线
    //着色器程序
    unsigned int shaderProgram = glCreateProgram();
    glAttachShader(shaderProgram, vertexShader);
    glAttachShader(shaderProgram, fragmentShader);
    glLinkProgram(shaderProgram);
    //链接错误检查
    glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success);
    if (!success) {
        glGetProgramInfoLog(shaderProgram, 512, NULL, infoLog);
        std::cout << "ERROR::SHADER::PROGRAM::LINKING_FAILED\n" << infoLog << std::endl;
    }
    //链接成一个完整着色器程序后释放掉顶点着色器和片段着色器
    glDeleteShader(vertexShader);
    glDeleteShader(fragmentShader);

    //2.创建VBO和VAO对象并赋予ID值
    unsigned int VBOs[2], VAOs[2];
    glGenVertexArrays(2, VAOs); // we can also generate multiple VAOs or buffers at the same time
    glGenBuffers(2, VBOs);

    //3.绑定VBO,VAO对象
    glBindVertexArray(VAOs[0]);
    glBindBuffer(GL_ARRAY_BUFFER, VBOs[0]);
 
    //4.开辟并填充数据
    glBufferData(GL_ARRAY_BUFFER, sizeof(firstTriangle), firstTriangle, GL_STATIC_DRAW);
    //arg1:目标buffer对象,数组对象
    //arg2:数据大小
    //arg3:数据地址,如果为NULL则该函数只是开辟内存
    //arg4:作用,静态数据绘制

    //5.告知Shader如何解析缓冲里的属性值
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
    //arg1:第1个VAO从0开始,只有一个VAO表示坐标属性。
    //arg2:该属性里有3个坐标
    //arg3:浮点类型
    //arg4:是否标准化
    //arg5:步长
    //arg6:偏移量,顶点数组offset从0开始就是0
    
    //6.开启VAO管理的第一个属性值,第一个VAO编号为0
    glEnableVertexAttribArray(0);

    glBindVertexArray(VAOs[1]);	// note that we bind to a different VAO now
    glBindBuffer(GL_ARRAY_BUFFER, VBOs[1]);	// and a different VBO
    glBufferData(GL_ARRAY_BUFFER, sizeof(secondTriangle), secondTriangle, GL_STATIC_DRAW);
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
    glEnableVertexAttribArray(0);   

    //7.解绑VBO,VAO
    glBindVertexArray(0);
    glBindBuffer(GL_ARRAY_BUFFER, 0);

    while (!glfwWindowShouldClose(window))                              //检查一次GLFW是否被要求退出,循环一次就是刷新一帧
    {
        processInput(window);

        glClearColor(0.2f, 0.3f, 0.3f, 1.0f);                           //设置颜色:深绿色
        glClear(GL_COLOR_BUFFER_BIT);                                   //刷新屏幕,使用设置的颜色
        
        //d.选择使用着色器给三角形上色,即上面创建链接好的流水线
        glUseProgram(shaderProgram);

        //9.上面把VAO解绑了,这里必须重新绑定否则绘制不出来三角形
        glBindVertexArray(VAOs[0]);
       
        //8.渲染线程里加上绘制三角形的命令
        glDrawArrays(GL_TRIANGLES, 0, 3);

        glBindVertexArray(VAOs[1]);
        glDrawArrays(GL_TRIANGLES, 0, 3);

        glfwSwapBuffers(window);                                        //交换颜色缓冲(双缓冲):储存着GLFW窗口每一个像素颜色值作为输出显示在屏幕上
        glfwPollEvents();                                               //轮训检查输入设备触发事件
    }
    //e.渲染结束后把VAO,VBO和着色器都释放掉
    glDeleteVertexArrays(2, VAOs);
    glDeleteBuffers(2, VBOs);
    glDeleteProgram(shaderProgram);

    glfwTerminate();                                                    //释放分配的所有glfw资源
    return 0;
}

void framebuffer_size_callback(GLFWwindow* window, int width, int height)   //窗口大小被改变就重新设置渲染区域大小
{
    glViewport(0, 0, width, height);
}

void processInput(GLFWwindow* window)
{
    if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)              //按Esc键关闭界面
        glfwSetWindowShouldClose(window, true);
}

5-3 创建两个着色器程序,第二个程序使用一个不同的片段着色器,输出黄色;再次绘制这两个三角形,让其中一个输出为黄色

        多一个着色器就是多一套流水线。继续定义一个着色器,然后创建、编译、链接、运行它。

最后注意在while循环里和三角形匹配使用,否则会出现两个三角形相同颜色的情况。

#include <glad.h>   //glad.h需要在glfw.h之前
#include <glfw3.h>
#include <iostream>

float firstTriangle[] = {
    -0.9f, -0.5f, 0.0f,  // left 
    -0.0f, -0.5f, 0.0f,  // right
    -0.45f, 0.5f, 0.0f,  // top 
};
float secondTriangle[] = {
    0.0f, -0.5f, 0.0f,  // left
    0.9f, -0.5f, 0.0f,  // right
    0.45f, 0.5f, 0.0f   // top 
};

//a.着色器源码
//顶点着色器源码
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";

//片段着色器源码
const char* fragmentShaderSource = "#version 330 core\n"
"out vec4 FragColor;\n"
"void main()\n"
"{\n"
" FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);\n"
"}\n\0";

//第二个片段着色器源码
const char* fragmentShader2Source = "#version 330 core\n"
"out vec4 FragColor;\n"
"void main()\n"
"{\n"
"   FragColor = vec4(1.0f, 1.0f, 0.0f, 1.0f);\n"
"}\n\0";

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

int main()
{
    //初始化glfw:选择opengl 3.3~4.2核心模式,取决于你的显卡驱动
    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(800, 600, "LearnOpenGL", NULL, NULL); //创建窗体对象:参数1:opengl窗口大小;参数2:窗口标题
    if (window == NULL)
    {
        std::cout << "Failed to create GLFW window" << std::endl;
        glfwTerminate();
        return -1;
    }
    glfwMakeContextCurrent(window);                                     //设置当前窗口上下文设置为当前线程的上下文,之后才能在窗口进行绘制

    if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))            //glad:加载所有OpenGL函数指针,固定写法就这一行
    {
        std::cout << "Failed to initialize GLAD" << std::endl;
        return -1;
    }
    glViewport(0, 0, 600, 400);                                         //设置opengl实际渲染的区域大小;0,0为左下角坐标

    glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);  //注册窗口大小改变回调函数


    //b.创建并编译着色器
    //顶点着色器
    unsigned int vertexShader = glCreateShader(GL_VERTEX_SHADER);//创建顶点着色器,arg:顶点着色器
    glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);//获取顶点着色器源码
    //arg1:着色器类型
    //arg2:源码是一个字符串表示
    //arg3:字符串长度
    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::COMPILATION_FAILED\n" << infoLog << std::endl;
    }
    // 片段着色器
    unsigned int fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);//创建顶点着色器
    unsigned int fragment2Shader = glCreateShader(GL_FRAGMENT_SHADER);//创建顶点着色器
    unsigned int shaderProgram = glCreateProgram();
    unsigned int shader2Program = glCreateProgram();
    glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);//获取片段着色器源码
    glCompileShader(fragmentShader);//编译片段着色器

    glShaderSource(fragment2Shader, 1, &fragmentShader2Source, NULL); 
    glCompileShader(fragment2Shader);
    // 检查编译错误
    glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &success);
    if (!success) {
        glGetShaderInfoLog(fragmentShader, 512, NULL, infoLog);
        std::cout << "ERROR::SHADER::FRAGMENT::COMPILATION_FAILED\n" << infoLog << std::endl;
    }

    //c.把顶点着色器和片段着色器链接成一个着色器程序,即一个流水线

    glAttachShader(shaderProgram, vertexShader);
    glAttachShader(shaderProgram, fragmentShader);
    glLinkProgram(shaderProgram);

    glAttachShader(shader2Program, vertexShader);
    glAttachShader(shader2Program, fragment2Shader);
    glLinkProgram(shader2Program);
    //链接错误检查
    glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success);
    if (!success) {
        glGetProgramInfoLog(shaderProgram, 512, NULL, infoLog);
        std::cout << "ERROR::SHADER::PROGRAM::LINKING_FAILED\n" << infoLog << std::endl;
    }
    //链接成一个完整着色器程序后释放掉顶点着色器和片段着色器
    glDeleteShader(vertexShader);
    glDeleteShader(fragmentShader);
    glDeleteShader(fragment2Shader);

    //2.创建VBO和VAO对象并赋予ID值
    unsigned int VBOs[2], VAOs[2];
    glGenVertexArrays(2, VAOs); // we can also generate multiple VAOs or buffers at the same time
    glGenBuffers(2, VBOs);

    //3.绑定VBO,VAO对象
    glBindVertexArray(VAOs[0]);
    glBindBuffer(GL_ARRAY_BUFFER, VBOs[0]);
 
    //4.开辟并填充数据
    glBufferData(GL_ARRAY_BUFFER, sizeof(firstTriangle), firstTriangle, GL_STATIC_DRAW);
    //arg1:目标buffer对象,数组对象
    //arg2:数据大小
    //arg3:数据地址,如果为NULL则该函数只是开辟内存
    //arg4:作用,静态数据绘制

    //5.告知Shader如何解析缓冲里的属性值
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
    //arg1:第1个VAO从0开始,只有一个VAO表示坐标属性。
    //arg2:该属性里有3个坐标
    //arg3:浮点类型
    //arg4:是否标准化
    //arg5:步长
    //arg6:偏移量,顶点数组offset从0开始就是0
    
    //6.开启VAO管理的第一个属性值,第一个VAO编号为0
    glEnableVertexAttribArray(0);

    glBindVertexArray(VAOs[1]);	// note that we bind to a different VAO now
    glBindBuffer(GL_ARRAY_BUFFER, VBOs[1]);	// and a different VBO
    glBufferData(GL_ARRAY_BUFFER, sizeof(secondTriangle), secondTriangle, GL_STATIC_DRAW);
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
    glEnableVertexAttribArray(0);   

    //7.解绑VBO,VAO
    glBindVertexArray(0);
    glBindBuffer(GL_ARRAY_BUFFER, 0);

    while (!glfwWindowShouldClose(window))                              //检查一次GLFW是否被要求退出,循环一次就是刷新一帧
    {
        processInput(window);

        glClearColor(0.2f, 0.3f, 0.3f, 1.0f);                           //设置颜色:深绿色
        glClear(GL_COLOR_BUFFER_BIT);                                   //刷新屏幕,使用设置的颜色
        
        //d.选择使用着色器给三角形上色,即上面创建链接好的流水线
        glUseProgram(shaderProgram);//橘色

        //9.上面把VAO解绑了,这里必须重新绑定否则绘制不出来三角形
        glBindVertexArray(VAOs[0]);
       
        //8.渲染线程里加上绘制三角形的命令
        glDrawArrays(GL_TRIANGLES, 0, 3);

        glUseProgram(shader2Program);//黄色
        glBindVertexArray(VAOs[1]);
        glDrawArrays(GL_TRIANGLES, 0, 3);

        glfwSwapBuffers(window);                                        //交换颜色缓冲(双缓冲):储存着GLFW窗口每一个像素颜色值作为输出显示在屏幕上
        glfwPollEvents();                                               //轮训检查输入设备触发事件
    }
    //e.渲染结束后把VAO,VBO和着色器都释放掉
    glDeleteVertexArrays(2, VAOs);
    glDeleteBuffers(2, VBOs);
    glDeleteProgram(shaderProgram);
    glDeleteProgram(shader2Program);

    glfwTerminate();                                                    //释放分配的所有glfw资源
    return 0;
}

void framebuffer_size_callback(GLFWwindow* window, int width, int height)   //窗口大小被改变就重新设置渲染区域大小
{
    glViewport(0, 0, width, height);
}

void processInput(GLFWwindow* window)
{
    if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)              //按Esc键关闭界面
        glfwSetWindowShouldClose(window, true);
}

原作:你好,三角形 - LearnOpenGL CN

  • 23
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值