第二课,OpenGL第一个三角形的流程

本篇文章不按照代码的顺序编写,而按照数据的走向流程编辑。

1 顶点输入

GLfloat vertices[] = {
    -0.5f, -0.5f, 0.0f,
     0.5f, -0.5f, 0.0f,
     0.0f,  0.5f, 0.0f
};

使用GLfloat数组记录顶点数据(数据在-1~1之间)

2 将数据发送到GPU内存

(1)需要顶点缓冲对象(Vertex Buffer Objects, VBO)管理这个内存,顶点数组对象(Vertex Array Object, VAO)储存顶点属性调用

一个顶点数组对象(VAO)会储存以下这些内容:

glEnableVertexAttribArray和glDisableVertexAttribArray的调用。(是否启用顶点调用)
通过glVertexAttribPointer设置的顶点属性配置。
通过glVertexAttribPointer调用与顶点属性关联的顶点缓冲对象。

初始化顶点数组对象VAO, 顶点缓冲对象VBO。

GLuint VBO, VAO;
    glGenBuffers(1, &VBO);
    glGenVertexArrays(1, &VAO);

使用glBindVertexArray绑定VAO,从绑定之后绑定和配置对应的VBO和属性指针,解绑VAO。这样便可再以后之后使用VAO来配置相同的顶点数据和属性配置。

glBindVertexArray(VAO);//绑定VAO

/*************************/
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);//启用顶点属性
glBindBuffer(GL_ARRAY_BUFFER, 0);//解绑VBO
/**************************/

glBindVertexArray(0);//解绑VAO

在绑定和解绑VAO之间的数据将被储存在VAO里
每个顶点属性从一个VBO管理的内存中获得它的数据,而具体是从哪个VBO(程序中可以有多个VBO)获取则是通过在调用glVertexAttribPointer时绑定到GL_ARRAY_BUFFER的VBO决定的。
glVertexAttribPointer(0,~)以及glEnableVertexAttribArray(0)中的0都是顶点属性索引(由location = 指定),这将在顶点着色器中看到。

glBindVertexArray(VAO);
glDrawArrays(GL_TRIANGLES, 0, 3);

上方代码为循环中绘制图元的代码

上方函数定义

函数定义:
void glGenBuffers(GLsizei n,GLuint * buffers);
  	n:指定要生成的缓冲区对象名称的数量。
  	buffers:指定存储生成的缓冲区对象名称的数组。

函数定义:
void glBindBuffer(GLenum target,GLuint buffer);
	target:指定缓冲区对象绑定的目标。
		符号常量必须为	GL_ARRAY_BUFFER   ????
				或		GL_ELEMENT_ARRAY_BUFFER  ????
	buffer:指定缓冲区对象的名称(ID)。

函数定义:**
void glBufferData(GLenum target,GLsizeiptr size,const GLvoid * data,GLenum usage);
参数:	1指定目标缓冲区对象。
		2指定缓冲区对象的新数据存储的大小(以字节为单位)。
		3指定将复制到数据存储区以进行初始化的数据的指针,如果不复制数据,则指定NULL4指定数据存储的预期使用模式。 符号常量必须为
			GL_STATIC_DRAW :数据不会或几乎不会改变。
			GL_DYNAMIC_DRAW:数据会被改变很多。
			GL_STREAM_DRAW :数据每次绘制时都会改变。

函数定义:**
void glVertexAttribPointer(GLuint index,  指定要修改的通用顶点属性的索引。
                            GLint size,    指定每个通用顶点属性的组件数。 必须为1,2,34.初始值为4。
                            GLenum type,   指定数组中每个组件的数据类型。 
                            			   接受符号常量GL_BYTE,GL_UNSIGNED_BYTE,GL_SHORT,GL_UNSIGNED_SHORT,GL_FIXED或GL_FLOAT。 
                            			   初始值为GL_FLOAT。
                            GLboolean normalized,   指定在访问定点数据值时是应将其标准化(GL_TRUE)还是直接转换为定点值(GL_FALSE)。
                            GLsizei stride,      指定连续通用顶点属性之间的字节偏移量。 
                            					 如果stride为0,则通用顶点属性被理解为紧密打包在数组中的。
                            					 初始值为0const GLvoid * pointer);  指定指向数组中第一个通用顶点属性的第一个组件的指针。 
                      							 初始值为0

顶点数组vertices通过glBufferData将数据传入GPU内存,这块内存由VBO管理,由VAO管理VBO及相关的顶点属性配置(连接的管线)

(2)需要顶点缓冲对象(VBO)管理点集,索引缓冲对象(管理索引),顶点数组对象(VAO)储存所有属性。

/*使用索引数据存储点的顺序,用IBO索引缓冲对象管理*/
    GLfloat vertices1[] = {
    0.5f, 0.5f, 0.0f,   // 右上角
    0.5f, -0.5f, 0.0f,  // 右下角
    -0.5f, -0.5f, 0.0f, // 左下角
    -0.5f, 0.5f, 0.0f   // 左上角
    };
    unsigned int indices1[] = { // 注意索引从0开始! 
        0, 1, 3, // 第一个三角形
        1, 2, 3  // 第二个三角形
    };
    
    GLuint VBO1,EBO1,VAO1;
    /*生成三类对象,不分先后顺序*/
    glGenVertexArrays(1, &VAO1);
    glGenBuffers(1, &VBO1);
    glGenBuffers(1, &EBO1);
    
    /*绑定三类对象*/
    glBindVertexArray(VAO1);//VAO必须为第一
    glBindBuffer(GL_ARRAY_BUFFER, VBO1);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO1);

	/*绑定缓冲数据*/
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices1), vertices1, GL_STATIC_DRAW);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices1), indices1, GL_STATIC_DRAW);
    
    /*设置数据传入管线的形式*/
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
    	//管线位置为0,每次传入三个float数据,是否标准化,连续的顶点属性组之间的间隔,偏移量
    /*启动顶点属性*/
    glEnableVertexAttribArray(0);

    glBindBuffer(GL_ARRAY_BUFFER, 0);
    //不可解绑EBO
    glBindVertexArray(0);

while循环里

glBindVertexArray(VAO1);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);

VAO,VBO,EBO关系图

补充(2021.8.11):VAO存储的所有顶点属性需在一次全部录入,如若需要改变VAO中顶点属性,则需对全部顶点属性重新全部录入,不可只录入改变的顶点属性。VAO的录入不是补充修改,而是对之前录入内容的覆盖。

3 数据通过图形渲染管线

第一个处理阶段::顶点着色器

#version 330 core  //设置版本,与OpenGL版本一致

layout (location = 0) in vec3 position;   //输入变量为三维向量,命名为position  
//layout (location = 0)  ?????????

void main()
{
    gl_Position = vec4(position.x, position.y, position.z, 1.0);
    //main函数的最后,gl_Position设置的值会成为该顶点着色器的输出.
}

在c++、python编程中需要将GLSL语言以字符串的方式输入,例如:

const GLchar* vertexShaderSource = 
	"#version 330 core\\n"
    "layout (location = 0) in vec3 position;\\n"
    "void main()\\n"
    "{\\n"
    "gl_Position = vec4(position.x, position.y, position.z, 1.0);\\n"
    "}\\0";

编译着色器

GLuint vertexShader;//创建对象
vertexShader = glCreateShader(GL_VERTEX_SHADER);
//指定要创建的着色器的类型只能是GL_VERTEX_SHADER顶点着色器
//							或GL_FRAGMENT_SHADER片段着色器。

glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);//替换着色器对象中的源代码
参数:	1.要被替换源代码的着色器对象的句柄(ID)。
		2.指定字符串和长度数组中的元素数。
		3.指定指向包含要加载到着色器的源代码的字符串的指针数组。
		4.指定字符串长度的数组。
glCompileShader(vertexShader);//编译

检验是否编译成功

引自 LearnOpenGL-CN
.
你可能会希望检测在调用glCompileShader后编译是否成功了,如果没成功的话,你还会希望知道错误是什么,这样你才能修复它们。检测编译时错误可以通过以下代码来实现:

GLint success;
GLchar infoLog[512];
glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success);

首先我们定义一个整型变量来表示是否成功编译,还定义了一个储存错误消息(如果有的话)的容器。然后我们用glGetShaderiv检查是否编译成功。如果编译失败,我们会用glGetShaderInfoLog获取错误消息,然后打印它。

if(!success)
{
   glGetShaderInfoLog(vertexShader, 512, NULL, infoLog);
   std::cout << "ERROR::SHADER::VERTEX::COMPILATION_FAILED\\n" << >infoLog << std::endl;
}

第二个处理阶段::形状装配(不可编程)

第三个处理阶段::几何着色器(可选)

第四个处理阶段::光栅化(不可编程)

第五个处理阶段::片段着色器

同顶点着色器

const GLchar* fragmentShaderSource =
    "#version 330 core\\n"
    "out vec4 FragColor;\\n"   //输出一个四分量向量,表示最终颜色。
    "void main(){\\n"
        "FragColor = vec4(1.0f,0.5f,0.2f,1.0f);\\n"
    "}\\n";

FragColor为片段着色器输出的最终颜色。

GLuint fragmentShader;
    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;
    }

第六个处理阶段::测试与混合(不可编程)

最终阶段::链接为着色器程序对象,即合成管线。

GLuint shaderProgram;
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::PROGRAM::SHADER::LINK_FAILED\\n" << infoLog << std::endl;
}
    //把着色器对象链接到程序对象以后,我们就不再需要它们了
glDeleteShader(vertexShader);
glDeleteShader(fragmentShader);

最终代码

#include <iostream>
#include <glad/glad.h>
#include <GLFW/glfw3.h>
//glad需要在glfw之前,因为GLAD的头文件包含了正确的OpenGL头文件,glfw依赖于OpenGL

void key_callback(GLFWwindow* window, int key, int scancode, int action, int mode);
void framebuffer_size_callback(GLFWwindow* window, int width, int height);

/*着色器定义*/
const GLchar* vertexShaderSource = 
    "#version 330 core\\n"
    "layout (location = 0) in vec3 position;\\n"//这儿有分号
    "void main()\\n"
    "{\\n"
    "gl_Position = vec4(position.x, position.y, position.z, 1.0);\\n"
    "}\\0";

const GLchar* fragmentShaderSource =
    "#version 330 core\\n"
    "out vec4 FragColor;\\n"   //输出一个四分量向量,表示最终颜色。
    "void main(){\\n"
        "FragColor = vec4(1.0f,0.5f,0.2f,1.0f);\\n"
    "}\\n";
    

int main()
{
    glfwInit();
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);

    GLFWwindow* window = glfwCreateWindow(800, 600, "LearnOpenGL", nullptr, nullptr);
    if (window == nullptr)
    {
        std::cout << "Failed to create GLFW window" << std::endl;
        glfwTerminate();
        return -1;
    }
    glfwMakeContextCurrent(window);
    //通知GLFW将我们窗口的上下文设置为当前线程的主上下文了。
    glfwSetKeyCallback(window, key_callback);
    glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);
    //通过GLFW注册我们的函数至合适的回调

    //glad初始化(GLAD是用来管理OpenGL的函数指针的)
    if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
    {
        std::cout << "Failed to initialize GLAD" << std::endl;
        return -1;
    }

    int width, height;
    
    glfwGetFramebufferSize(window, &width, &height);
    //获取帧缓冲大小,即设置窗口的大小(这里为800*600)。
    glViewport(0, 0, width, height);
    //设置视口(viewport)


    /*使用顶点着色器*/
    GLuint vertexShader;
    vertexShader = glCreateShader(GL_VERTEX_SHADER);
    glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);
    glCompileShader(vertexShader);
    //判断编译是否成功
    GLint success;
    GLchar 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;
    }


    /*使用片段着色器*/
    GLuint fragmentShader;
    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;
    }


    /*链接为着色器程序对象*/
    GLuint shaderProgram;
    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::PROGRAM::SHADER::LINK_FAILED\\n" << infoLog << std::endl;
    }
    //把着色器对象链接到程序对象以后,我们就不再需要它们了
    glDeleteShader(vertexShader);
    glDeleteShader(fragmentShader);


    /*使用顶点缓冲将顶点储存在显卡的内存中,用VBO顶点缓冲对象管理*/
    GLfloat vertices[] = {
        -1.0f,  0.0f, 0.0f,
         0.0f, -1.0f, 0.0f,
        -1.0f, -1.0f, 0.0f
    };
    GLuint VBO, VAO;
    glGenBuffers(1, &VBO);
    glGenVertexArrays(1, &VAO);
    glBindVertexArray(VAO);
    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);//启用顶点属性

    /*使用索引数据存储点的顺序,用IBO索引缓冲对象管理*/
    GLfloat vertices1[] = {
    0.5f, 0.5f, 0.0f,   // 右上角
    0.5f, -0.5f, 0.0f,  // 右下角
    -0.5f, -0.5f, 0.0f, // 左下角
    -0.5f, 0.5f, 0.0f   // 左上角
    };
    unsigned int indices1[] = { // 注意索引从0开始! 
        0, 1, 3, // 第一个三角形
        1, 2, 3  // 第二个三角形
    };
    GLuint VBO1,EBO1,VAO1;
    //生成三类对象,不分先后顺序
    glGenBuffers(1, &EBO1);
    glGenBuffers(1, &VBO1);
    glGenVertexArrays(1, &VAO1);
    //绑定三类对象
    glBindVertexArray(VAO1);//VAO必须为第一
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO1);
    glBindBuffer(GL_ARRAY_BUFFER, VBO1);
    //绑定缓冲数据
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices1), vertices1, GL_STATIC_DRAW);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices1), indices1, GL_STATIC_DRAW);
    //设置数据传入管线的形式
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
    glEnableVertexAttribArray(0);

    glBindBuffer(GL_ARRAY_BUFFER, 0);
    glBindVertexArray(0);

    //glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);//设置为线框模式
    //glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);//设置为填充模式(默认)
    //设置为一类,在后续将一直为该类,直到再次修改

    while (!glfwWindowShouldClose(window))//检查glfw是否被要求退出
    {
        glfwPollEvents();
        //检查有没有触发什么事件,然后调用对应的回调函数.

        glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT);
  
        glUseProgram(shaderProgram);//要用在循环里

        glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);//设置为填充模式(默认)
        glBindVertexArray(VAO);
        glDrawArrays(GL_TRIANGLES, 0, 3);

        glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);//设置为线框模式
        glBindVertexArray(VAO1);
        glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);

        glfwSwapBuffers(window);
        //交换颜色缓冲
    }
    //Game Loop

    glDeleteVertexArrays(1, &VAO);
    glDeleteBuffers(1, &VBO);
    glDeleteVertexArrays(1, &VAO1);
    glDeleteBuffers(1, &VBO1);
    glDeleteBuffers(1, &EBO1);
    glDeleteProgram(shaderProgram);

    glfwTerminate();
    //释放GLFW分配的内存。

    return 0;
}

//函数实现
void key_callback(GLFWwindow* window, int key, int scancode, int action, int mode)
{
    // 当用户按下ESC键,我们设置window窗口的WindowShouldClose属性为true
    // 关闭应用程序
    if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS)
        glfwSetWindowShouldClose(window, GL_TRUE);
}
void framebuffer_size_callback(GLFWwindow* window, int width, int height)
{
    glViewport(0, 0, width, height);
}

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情代码图片