OPenGL入门学习第一课

最近准备入门计算机图形处理,今天来聊一聊OpenGL入门第一课~
有最近也在学的可以一起畅所欲言,聊聊想法,我的微信号:gxin_0508,希望遇到志同道合的你~

opengl开源中文学习网站:https://learnopengl-cn.github.io/
非常好的资源,网站上的内容,我们就不在缀述了~

我的文章主要针对于难理解的词,概念以及一些思路进行我自己的想法的描述~

首先是:你好,窗口!
由于是新手入门,可能有一些不太懂,我加了详细的注解与大家分享


#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <iostream>
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()
{
    
    
 // glfw: initialize and configure
 // ------------------------------
 glfwInit();
 glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);//主版本号(Major)
 glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);//次版本号(Minor)
 glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);//核心模式(Core-profile)
 //明确告诉GLFW我们需要使用核心模式,我们只能使用OpenGL功能的一个子集(没有我们已不再需要的向后兼容特性)

#ifdef __APPLE__
 glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); //   IOS 需要
#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);监听窗口大小变化
 //glViewport(0, 0, 800, 600);
 //虽然glfwSetFramebufferSizeCallback中做的事情就是定义窗口大小,但如果将该函数替换为glViewport,会出现闪屏效果
 //究其原因,glfwSetFramebufferSizeCallback要监听窗口大小变化,可能会变化,实时效果
// glad:GLAD是管理OpenGL指针的,在调用任何OpenGL的函数之前需要初始化GLAD
 // ---------------------------------------
 if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
 {
    
    
  std::cout << "Failed to initialize GLAD" << std::endl;
  return -1;
 }
// 循环渲染
 while (!glfwWindowShouldClose(window))
 {
    
    
  // 处理输入,例如键盘输入,鼠标移动,窗口改变
  processInput(window);




// 染色,注意:(0,0,0)黑,1,1,1白
  glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
  glClear(GL_COLOR_BUFFER_BIT);

将存储在缓冲区中的像素颜色进行绘制,这里涉及到双缓冲的问题
  glfwSwapBuffers(window);
  glfwPollEvents();检查有没有触发什么事件(键盘输入、鼠标移动等)、窗口改变
 }
 // glfw: terminate, clearing all previously allocated GLFW resources.
 glfwTerminate();
 return 0;
}

void processInput(GLFWwindow* window)
{
    
    
 if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)//现在是ESC键
  glfwSetWindowShouldClose(window, true);
}

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

下面是第二节,你好,三角形
核心代码,如下:


 unsigned int VBO, VAO;
    //创建
    glGenVertexArrays(1, &VAO);
    glGenBuffers(1, &VBO);
    //绑定VAO
    glBindVertexArray(VAO);
    //把顶点数组复制到缓冲中供OpenGL使用
    glBindBuffer(GL_ARRAY_BUFFER, VBO);//用glBindBuffer函数把新创建的缓冲绑定到GL_ARRAY_BUFFER目标上:
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
                  //从这一刻起,我们使用的任何(在GL_ARRAY_BUFFER目标上的)缓冲调用都会用来配置当前绑定的缓冲(VBO)。
    //然后我们可以调用glBufferData函数,它会把之前定义的顶点数据复制到缓冲的内存中:
                        //glBufferData是一个专门用来把用户定义的数据复制到当前绑定缓冲的函数。
   /*                    第一个参数是目标缓冲的类型:顶点缓冲对象当前绑定到GL_ARRAY_BUFFER目标上。
                    第二个参数指定传输数据的大小(以字节为单位);用一个简单的sizeof计算出顶点数据大小就行。
                    第三个参数是我们希望发送的实际数据。
                           第四个参数指定了我们希望显卡如何管理给定的数据。它有三种形式:
                                GL_STATIC_DRAW :数据不会或几乎不会改变。
                                GL_DYNAMIC_DRAW:数据会被改变很多。
                                GL_STREAM_DRAW :数据每次绘制时都会改变。*/
       // 3. 设置顶点属性指针
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
      glEnableVertexAttribArray(0);//使vbo的内存变为可用状态。
    //以使用glVertexAttribPointer函数告诉OpenGL该如何解析顶点数据(应用到逐个顶点属性上)了
    //第一个参数指定我们要配置的顶点属性。还记得我们在顶点着色器中使用layout(location = 0)定义了position顶点属性的位置值(Location)吗?它可以把顶点属性的位置值设置为0。因为我们希望把数据传递到这一个顶点属性中,所以这里我们传入0。
     //  第二个参数指定顶点属性的大小。顶点属性是一个vec3,它由3个值组成,所以大小是3。
     //      第三个参数指定数据的类型,这里是GL_FLOAT(GLSL中vec * 都是由浮点数值组成的)。
      //     下个参数定义我们是否希望数据被标准化(Normalize)。如果我们设置为GL_TRUE,所有数据都会被映射到0(对于有符号型signed数据是 - 1)到1之间。我们把它设置为GL_FALSE。
        //   第五个参数叫做步长(Stride),它告诉我们在连续的顶点属性组之间的间隔。由于下个组位置数据在3个float之后,我们把步长设置为3 * sizeof(float)。要注意的是由于我们知道这个数组是紧密排列的(在两个顶点属性之间没有空隙)我们也可以设置为0来让OpenGL决定具体步长是多少(只有当数值是紧密排列时才可用)。一旦我们有更多的顶点属性,我们就必须更小心地定义每个顶点属性之间的间隔,我们在后面会看到更多的例子(译注: 这个参数的意思简单说就是从这个属性第二次出现的地方到整个数组0位置之间有多少字节)。
      //     最后一个参数的类型是void * ,所以需要我们进行这个奇怪的强制类型转换。它表示位置数据在缓冲中起始位置的偏移量(Offset)。由于位置数据在数组的开头,所以这里是0。我们会在后面详细解释这个参数。
 

绘制时的代码:

  // glDrawArrays函数第一个参数是我们打算绘制的OpenGL图元的类型。
        //希望绘制的是一个三角形,这里传递GL_TRIANGLES给它。
        //第二个参数指定了顶点数组的起始索引,我们这里填0。
        //最后一个参数指定我们打算绘制多少个顶点,这里是3)。
        glUseProgram(shaderProgram);
        glBindVertexArray(VAO); //VAO好用的地方,如果没有VAO,那么每个点传一次,麻烦
        glDrawArrays(GL_TRIANGLES, 0, 3);
       

小警告,定义完VAO,VBO,注意删除
glDeleteVertexArrays(1, &VAO);
glDeleteBuffers(1, &VBO);
glDeleteProgram(shaderProgram);

本节比较难的是VAO,VBO的理解:

VBO
顶点缓冲对象(Vertex Buffer Objects,VBO)
顶点缓冲对象VBO是在显卡存储空间中开辟出的一块内存缓存区,用于存储顶点的各类属性信息,如顶点坐标,顶点法向量,顶点颜色数据等。在渲染时,可以直接从VBO中取出顶点的各类属性数据,由于VBO在显存而不是在内存中,不需要从CPU传输数据,处理效率更高。
所以可以理解为VBO就是显存中的一个存储区域,可以保持大量的顶点属性信息。并且可以开辟很多个VBO,每个VBO在OpenGL中有它的唯一标识ID,这个ID对应着具体的VBO的显存地址,通过这个ID可以对特定的VBO内的数据进行存取操作。

VAO
VAO一个顶点数组对象,存储着图像顶点,VBO保存了一个模型的顶点属性信息,每次绘制模型之前需要绑定顶点的所有信息,当数据量很大时,重复这样的动作变得非常麻烦。VAO可以把这些所有的配置都存储在一个对象中,每次绘制模型时,只需要绑定这个VAO对象就可以了。
VAO是一个保存了所有顶点数据属性的状态结合,它存储了顶点数据的格式以及顶点数据所需的VBO对象的引用。
VAO本身并没有存储顶点的相关属性数据,这些信息是存储在VBO中的,VAO相当于是对很多个VBO的引用,把一些VBO组合在一起作为一个对象统一管理。

执行VAO绑定之后其后的所有VBO配置都是这个VAO对象的一部分,可以说VBO是对顶点属性信息的绑定,VAO是对很多个VBO的绑定。

执行时,我所理解的思路(伪代码)

头文件
输入和窗口回调函数
main()
{
    
    
  初始化glfw
  窗口建立
   glfwMakeContextCurrent(window);
   glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);
  glad链接
  verdex shader初始化
  fragment shader初始化
  link shaders初始化
   glDeleteShader(vertexShader);//容易丢
   glDeleteShader(fragmentShader);
   点数组
   VAO、VBO建立
   绑定VAO
  顶点数组复制到缓冲,通过VBO,数学全部绑到GL_ARRAY_BUFFER实现
   设置顶点属性
   
   循环渲染{
    
    
     输入检查
     染色
     
     调用shaderProgress
     绑定VAO
     绘制

     交换缓冲区
     回滚
   }
   注意VAO、VBO、shaderprogress的释放
}

下面解释EBO(IBO):
索引缓冲对象,对于重复顶点,只存储一次,我们只需从EBO中取到对应的索引,存入VAO,VAO从VBO中拿到对应的属性值

关系图如下:
在这里插入图片描述
使用时,在循环渲染中注意把EBO的信息也传输一下

猜你喜欢

转载自blog.csdn.net/qq_38205273/article/details/109348847