LearnOpenGL入门章节学习记录:绘制三角形and一文搞懂VAO、VBO、EBO的设计模式

引言

1、本专栏为LearnOpenGL的学习笔记,用来记录基础知识、渲染流程和遇到的问题。如果文中理解有误,还请指正。
2、GLFW API手册
3、OpenGL Specification

一、为什么需要VAO、VBO、EBO?它们各自的用处是什么?

  • 先说结论:个人理解,这三个数据结构在渲染pipeline中最基本的是VBO,其在显存上开辟一块内存,用来存放批量顶点和纹理等数据。在其基础上EBO是为了顶点复用、节约内存,VAO是为了顶点属性解析复用,避免每组顶点传过去之后都要重新设置顶点属性。综上,VBO是为了传数据,EBO和VAO都是通过复用来提升效率的一种“(索引)绑定结构”。------->但是在渲染pipeline中,VAO和VBO是必须有的,EBO可以在顶点数据需要复用时再用。为什么不能只用VBO?其实我也不知道可不可以,但是没看到VBO有相关API告诉GPU如何解析数据,解析数据的函数glVertexAttribPointer()是设置VAO每个属性点(属性点对应每个不同类型数据的VBO)的,所以我感觉基本的结构是VAO+VBO,顶点多时,加入EBO。
  • VBO: 为了实现内存数据到显存的批量传输,从而加快渲染速度。因为CPU从内存中将数据发送到显卡显存速度的较慢,而顶点着色器几乎能立即访问到显存中的数据,所以需要一次发送尽可能多的顶点和纹理数据,以平衡IO速度制约渲染速度的问题。
  • VAO:在渲染Pipeline中,我们通过VBO将一大堆不同类型的数据(一般来说一种数据类型存一个VBO,算一个顶点属性,也可以把坐标、纹理等不同类型的数据存入一个VBO,然后解析顶点属性时通过偏移量来区分,但是大数据量时显然一个VBO存一个类型的数据更清晰)存入显存,但此时GPU并不知道如何解析显存中的数据,因此每当数据通过glBufferData()传输到显存后还需要调用glVertexAttribPointer()告诉GPU如何解析。而VAO的作用就是绑定一组VBO并分别设置好归属其的每个VBO的解析规则,使其在循环渲染时不需要重复解析。打个比方,VAO是一个鸭妈妈,VBO是它身后跟着的一群各不相同鸭宝宝,首先将每个鸭宝宝绑定到鸭妈妈不同的属性位置0、1、2…,并设置其解析规则。然后在循环渲染的过程中就可以通过绑定鸭妈妈就可以直接知道它身后所有鸭宝宝的数据解析规则,而不需要再通过循环去绑定每个VBO并设置解析规则,可以大幅减少篇幅。----->【Question】个人感觉代码量并未减少,而是像在渲染前将一组数据打包,然后在渲染过程中就只需要通过VAO来调用,使渲染过程更简洁
  • EBO: 一个复杂的渲染画面是由成千上万个顶点和纹理数据组成的,而显存是有限的资源,因此在多个三角形共用同一个顶点时,可以只存储该顶点一次而非多次,通过EBO使多个三角形来复用这个顶点,从而达到节约显存的作用。

二、绘制三角形的渲染Pipeline

  1. 初始化glfw并创建窗口、初始化GLAD
    • glfwWindowHint(): 初始化glfw的版本、核心模式
    • glfwInit(): 如果初始化失败返回0,所以作为异常处理的条件时要取反
    • glfwTerminate()
    • glfwMakeContextCurrent(): 创建完窗口一定要将其设置为当前进程的上下文
    • glfwSetFramebufferSizeCallback()
    • gladLoadGLLoader(): 初始化glad
  2. 创建着色器程序对象
    • glCreateShader(): 创建指定类型的着色器
    • glShaderSource(): 为着色器对象绑定着色器程序源码
    • glCompileShader(): 编译源码
    • glGetShaderiv(): 查看编译是否成功
    • glGetShaderInfoLog(): 返回Shader对象的日志信息
    • glCreatreProgram(): 创建着色器程序对象(就是用来拼接这个着色器对象的程序,用来形成一段完整的着色器程序,像把一段段的水管接起来一样)
    • glAttachShader(): 附加一个着色器对象到着色器程序对象中
    • glLinkProgram(): 将附加到着色器程序对象中的着色器对象拼接起来
    • glGetProgramiv()
    • glGetProgramInfoLog()
    • glDeleteShader():删除指定的shader对象,释放内存
  3. 导入顶点数据到显存并配置顶点属性
    • glGenVertexArrays():创建顶点数组对象—>注意!!对象ID是指向该内存的引用
    • glGenBuffers(): 创建缓冲对象(顶点缓冲/元素缓冲)
    • glBindVertexArray(): 绑定(激活)VAO,在该调用后紧接着绑定的VBO都属于该VAO。---->注意!!即第一节所说一个VAO可以有多个VBO,并且他们的顶点解析方法是相同的(顶点解析方法由VAO相关的函数决定)
    • glBindBuffer(): 将顶点数据(在内存中)拷贝到显存中(VBO指向该块显存空间)
    • glVertexAttribPointer(): 指定该VAO的所有VBO数据的解析方法
    • glEnableVertexAttribArray(): 使能数据 ----> 注意!!即上一个函数是告诉GPU如何解析显存中的数据,这个函数是让GPU能看到显存中的数据
  4. 循环渲染
    • processInput(): 处理输入
    • glClearColor: 设置清屏RGB
    • glClear(): 执行清屏
    • glUseProgram(): 激活着色器程序对象
    • glDrawElements() / glDrawArrays():都是从VBO中提取数据来渲染基本图元的作用。但是前者需要索引参数,适用于顶点复用多的情况。
    • glfwSwapBuffers(): 因为渲染画面是逐帧渲染的,所以如果只有一个缓冲,那渲染的时间就等于绘制+显示,所以就会造成闪屏(因为只有一个Buffer,拿到数据先绘制,绘制完再显示,绘制时屏就是黑的),所以利用双缓冲技术,用两个缓冲区来交替渲染,前缓冲区用于显示屏幕上的内容,后缓冲区用于绘制,每一帧开始时将两个缓冲区交换,即可达到前缓冲区一直显示,后缓冲区一直绘制的作用。
    • glfwPollEvent():通过窗体的关闭标志来控制循环会导致主线程(渲染进程)占用资源,从而导致窗体无法处理消息,所以需要在主线程中调用窗体处理消息函数来处理消息队列中的消息。

三、本节实验及其注意事项

3.1 作业要求及源码

  1. 实验要求及源码实现:通过3个顶点数据绘制一个三角形
    	#include <glad/glad.h>
    	#include <GLFW/glfw3.h>
    	
    	
    	#include <iostream>
    	
    	// window parameter
    	const unsigned int SCR_WIDTH = 800;
    	const unsigned int SCR_HEIGHT = 600;
    	const char* SCR_NAME = "Kkaiw";
    	
    	//framebuffer_size_callback funtion
    	void framebuffer_size_callback(GLFWwindow* window,int width,int height);
    	
    	//input catch funciton
    	void processInput(GLFWwindow* window);
    	
    	//shader source
    	const char* vertexShaderSource = "#version 330 core\n"
    	    "layout (location = 0) in vec3 aPos;\n"
    	    "out vec3 ourColor;\n"
    	    "void main()\n"
    	    "{\n"
    	    "   gl_Position = vec4(aPos,1.0f);\n"
    	    "   ourColor = aPos;\n"
    	    "}\0";
    	
    	const char* fragmentShaderSource = "#version 330 core\n"
    	    "in vec3 ourColor;\n"
    	    "out vec4 FragColor;\n"
    	    "void main()\n"
    	    "{\n"
    	    "   FragColor = vec4(ourColor,1.0f);\n"
    	    "}\n\0";
    	
    	
    	int main(){
    	    //1. Init
    	    //1.1 Init GLFW
    	    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR,3);
    	    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR,3);
    	    glfwWindowHint(GLFW_OPENGL_PROFILE,GLFW_OPENGL_CORE_PROFILE);
    	    if(!glfwInit()){
    	        std::cout << "Fail to init glfw\n" << std::endl;
    	        return -1;
    	    }
    	#ifdef __APPLE__
    	    glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT,GL_TRUE);
    	#endif
    	
    	    //1.2 Create Window
    	    GLFWwindow* window = glfwCreateWindow(SCR_WIDTH,SCR_HEIGHT,SCR_NAME,NULL,NULL);
    	    if(window == NULL){
    	        std::cout << "Fail to create window\n" << std::endl;
    	        glfwTerminate();
    	        return -1;
    	    }
    	    glfwMakeContextCurrent(window);
    	    glfwSetFramebufferSizeCallback(window,framebuffer_size_callback);
    	
    	    //1,3 Init GLAD
    	    if(!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)){
    	        std::cout << "Fail to init glad" << std::endl;
    	        return -1;
    	    }
    	
    	    //2. Shader
    	    int success;
    	    char InfoLog[512];
    	    //2.1 vertex shader
    	    unsigned int vertexShader = glCreateShader(GL_VERTEX_SHADER);
    	    glShaderSource(vertexShader,1,&vertexShaderSource,NULL);
    	    glCompileShader(vertexShader);
    	    glGetShaderiv(vertexShader,GL_COMPILE_STATUS,&success);
    	    if(!success){
    	        glGetShaderInfoLog(vertexShader,512,NULL,InfoLog);
    	        std::cout << "Fail to compile vertexShader\n " << InfoLog << std::endl; 
    	    }
    	
    	    //2.2 fragment shader
    	    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 << "Fail to compile fragmentShader\n" << InfoLog << std::endl;
    	    }
    	
    	    //2.3 shader program
    	    unsigned int shaderProgram = glCreateProgram();
    	    glAttachShader(shaderProgram,vertexShader);
    	    glAttachShader(shaderProgram,fragmentShader);
    	    glLinkProgram(shaderProgram);
    	    glGetProgramiv(shaderProgram,GL_COMPILE_STATUS,&success);
    	    if(!success){
    	        glGetProgramInfoLog(shaderProgram,512,NULL,InfoLog);
    	        std::cout << "Fail to compile shaderProgram" << std::endl;
    	    }
    	
    	    //2.4 delete shader to free memory
    	    glDeleteShader(vertexShader);
    	    glDeleteShader(fragmentShader);
    	
    	    //3. vertex data and config
    	    float vertices[]={
    	        -0.5f, -0.5f, 0.0f, // left  
    	         0.5f, -0.5f, 0.0f, // right 
    	         0.0f,  0.5f, 0.0f  // top           
    	    };
    	    
    	    unsigned int VAO,VBO;
    	    glGenVertexArrays(1,&VAO);
    	    glGenBuffers(1,&VBO);
    	
    	    glBindVertexArray(VAO);
    	
    	    glBindBuffer(GL_ARRAY_BUFFER,VBO);//【NOTE】为什么需要为缓冲区绑定名称?----->因为可以实现对不同渲染管线的快速切换!【Question】假设有两组顶点数据存在两个VBO中,可以通过将其bind到同一个缓冲区对象绑定类型上,完成切换?。
    	    glBufferData(GL_ARRAY_BUFFER,sizeof(vertices),vertices,GL_STATIC_DRAW);
    	    
    	    glVertexAttribPointer(0,3,GL_FLOAT,GL_FALSE,3*sizeof(float),(void*)0);
    	    glEnableVertexAttribArray(0);
    	
    	    glBindVertexArray(0);
    	
    	
    	    //4. loop render
    	    while(!glfwWindowShouldClose(window)){
    	        processInput(window);
    	
    	        glClearColor(0.52f,0.93f,0.78f,1.0f);
    	        glClear(GL_COLOR_BUFFER_BIT);
    	
    	        glUseProgram(shaderProgram);
    	        glBindVertexArray(VAO);
    	        glDrawArrays(GL_TRIANGLES,0,3);
    	
    	        glfwSwapBuffers(window);
    	        glfwPollEvents();
    	
    	    }
    	    glDeleteVertexArrays(1,&VAO);
    	    glDeleteBuffers(1,&VBO);
    	    glDeleteProgram(shaderProgram);
    	
    	    glfwTerminate();
    	    return 0;
    	}
    	
    	//function
    	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) glfwSetWindowShouldClose(window,true);
    	}
    	
    	```
    
  • 渲染结果
    在这里插入图片描述

3.2 关于本实验的思考和疑问及相应测试源码

3.2.1 一个VAO分别对应1个或多个VBO的实验方式----->VAO和VBO应该如何搭配使用?以及各种情况下是如何使用的?

  1. 将两个三角形的顶点坐标和颜色放在一个数组中,通过一个VAO和一个VBO绘制

    • 修改代码的第3部分和顶点着色器源码
      #include <glad/glad.h>
      #include <GLFW/glfw3.h>
      
      
      #include <iostream>
      
      // window parameter
      const unsigned int SCR_WIDTH = 800;
      const unsigned int SCR_HEIGHT = 600;
      const char* SCR_NAME = "Kkaiw";
      
      //framebuffer_size_callback funtion
      void framebuffer_size_callback(GLFWwindow* window,int width,int height);
      
      //input catch funciton
      void processInput(GLFWwindow* window);
      
      //shader source
      const char* vertexShaderSource = "#version 330 core\n"
          "layout (location = 0) in vec3 aPos;\n"
          "layout (location = 1) in vec3 aColor;\n"
          "out vec3 ourColor;\n"
          "void main()\n"
          "{\n"
          "   gl_Position = vec4(aPos,1.0f);\n"
          "   ourColor = aColor;\n"
          "}\0";
      
      const char* fragmentShaderSource = "#version 330 core\n"
          "in vec3 ourColor;\n"
          "out vec4 FragColor;\n"
          "void main()\n"
          "{\n"
          "   FragColor = vec4(ourColor,1.0f);\n"
          "}\n\0";
      
      
      int main(){
          //1. Init
          //1.1 Init GLFW
          glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR,3);
          glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR,3);
          glfwWindowHint(GLFW_OPENGL_PROFILE,GLFW_OPENGL_CORE_PROFILE);
          if(!glfwInit()){
              std::cout << "Fail to init glfw\n" << std::endl;
              return -1;
          }
      #ifdef __APPLE__
          glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT,GL_TRUE);
      #endif
      
          //1.2 Create Window
          GLFWwindow* window = glfwCreateWindow(SCR_WIDTH,SCR_HEIGHT,SCR_NAME,NULL,NULL);
          if(window == NULL){
              std::cout << "Fail to create window\n" << std::endl;
              glfwTerminate();
              return -1;
          }
          glfwMakeContextCurrent(window);
          glfwSetFramebufferSizeCallback(window,framebuffer_size_callback);
      
          //1,3 Init GLAD
          if(!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)){
              std::cout << "Fail to init glad" << std::endl;
              return -1;
          }
      
          //2. Shader
          int success;
          char InfoLog[512];
          //2.1 vertex shader
          unsigned int vertexShader = glCreateShader(GL_VERTEX_SHADER);
          glShaderSource(vertexShader,1,&vertexShaderSource,NULL);
          glCompileShader(vertexShader);
          glGetShaderiv(vertexShader,GL_COMPILE_STATUS,&success);
          if(!success){
              glGetShaderInfoLog(vertexShader,512,NULL,InfoLog);
              std::cout << "Fail to compile vertexShader\n " << InfoLog << std::endl; 
          }
      
          //2.2 fragment shader
          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 << "Fail to compile fragmentShader\n" << InfoLog << std::endl;
          }
      
          //2.3 shader program
          unsigned int shaderProgram = glCreateProgram();
          glAttachShader(shaderProgram,vertexShader);
          glAttachShader(shaderProgram,fragmentShader);
          glLinkProgram(shaderProgram);
          glGetProgramiv(shaderProgram,GL_COMPILE_STATUS,&success);
          if(!success){
              glGetProgramInfoLog(shaderProgram,512,NULL,InfoLog);
              std::cout << "Fail to compile shaderProgram" << std::endl;
          }
      
          //2.4 delete shader to free memory
          glDeleteShader(vertexShader);
          glDeleteShader(fragmentShader);
      
          //3. vertex data and config
          float vertices[] = {
              //first triangle      color
              -0.9f, -0.5f, 0.0f, 1.0f, 0.0f, 0.0f,
              -0.0f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f,
              -0.45f, 0.5f, 0.0f, 0.0f, 0.0f, 1.0f,
              //second triangle
              0.0f, -0.5f, 0.0f,  0.5f, 0.5f, 0.0f,
              0.9f, -0.5f, 0.0f,  0.5f, 0.0f, 0.5f,
              0.45f, 0.5f, 0.0f,  0.0f, 0.5f, 0.5f
          };
          
          unsigned int VAO,VBO;
          glGenVertexArrays(1,&VAO);
          glGenBuffers(1,&VBO);
      
          glBindVertexArray(VAO);
      
          glBindBuffer(GL_ARRAY_BUFFER,VBO);
          glBufferData(GL_ARRAY_BUFFER,sizeof(vertices),vertices,GL_STATIC_DRAW);
          
          glVertexAttribPointer(0,3,GL_FLOAT,GL_FALSE,6*sizeof(float),(void*)0);  //parse position
          glEnableVertexAttribArray(0);
      
          glVertexAttribPointer(1,3,GL_FLOAT,GL_FALSE,6*sizeof(float),(void*)(3*sizeof(float)));
          glEnableVertexAttribArray(1);
      
          glBindVertexArray(0);
      
      
          //4. loop render
          while(!glfwWindowShouldClose(window)){
              processInput(window);
      
              glClearColor(0.52f,0.93f,0.78f,1.0f);
              glClear(GL_COLOR_BUFFER_BIT);
      
              glUseProgram(shaderProgram);
              glBindVertexArray(VAO);
              glDrawArrays(GL_TRIANGLES,0,6); //[NOTE]如果数组中一个定点的熟悉长度是3 就是3  如果是6 就是6
      
              glfwSwapBuffers(window);
              glfwPollEvents();
      
          }
          glDeleteVertexArrays(1,&VAO);
          glDeleteBuffers(1,&VBO);
          glDeleteProgram(shaderProgram);
      
          glfwTerminate();
          return 0;
      }
      
      //function
      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) glfwSetWindowShouldClose(window,true);
      }
      
      
    • 渲染结果
      在这里插入图片描述
  2. 将两个三角形的顶点坐标和颜色放在两个数组中,分别通过一个VAO和一个VBO绘制

    • 可以发现与上一种方式有相同的渲染结果,但如果两个图形是不同的渲染方式,那本方式可以更好地实现渲染pipeline的切换----->所以本方式是在渲染pipeline上对上个实验上流程的优化,即可以完成不同渲染管线的快速切换,这在两个渲染目标不同时应该更具效率和清晰。但本方式的缺点在于,一个图形对象不同类型的顶点属性放在同一个数组里,这并没有很好地执行面对对象的思想。所以在下个实验中,我们取一个三角形为例,对其不同顶点属性进行优化设计,即一个图形由一个VAO和多个VBO组成
      #include <glad/glad.h>
      #include <GLFW/glfw3.h>
      
      
      #include <iostream>
      
      // window parameter
      const unsigned int SCR_WIDTH = 800;
      const unsigned int SCR_HEIGHT = 600;
      const char* SCR_NAME = "Kkaiw";
      
      //framebuffer_size_callback funtion
      void framebuffer_size_callback(GLFWwindow* window,int width,int height);
      
      //input catch funciton
      void processInput(GLFWwindow* window);
      
      //shader source
      const char* vertexShaderSource = "#version 330 core\n"
          "layout (location = 0) in vec3 aPos;\n"
          "layout (location = 1) in vec3 aColor;\n"
          "out vec3 ourColor;\n"
          "void main()\n"
          "{\n"
          "   gl_Position = vec4(aPos,1.0f);\n"
          "   ourColor = aColor;\n"
          "}\0";
      
      const char* fragmentShaderSource = "#version 330 core\n"
          "in vec3 ourColor;\n"
          "out vec4 FragColor;\n"
          "void main()\n"
          "{\n"
          "   FragColor = vec4(ourColor,1.0f);\n"
          "}\n\0";
      
      
      int main(){
          //1. Init
          //1.1 Init GLFW
          glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR,3);
          glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR,3);
          glfwWindowHint(GLFW_OPENGL_PROFILE,GLFW_OPENGL_CORE_PROFILE);
          if(!glfwInit()){
              std::cout << "Fail to init glfw\n" << std::endl;
              return -1;
          }
      #ifdef __APPLE__
          glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT,GL_TRUE);
      #endif
      
          //1.2 Create Window
          GLFWwindow* window = glfwCreateWindow(SCR_WIDTH,SCR_HEIGHT,SCR_NAME,NULL,NULL);
          if(window == NULL){
              std::cout << "Fail to create window\n" << std::endl;
              glfwTerminate();
              return -1;
          }
          glfwMakeContextCurrent(window);
          glfwSetFramebufferSizeCallback(window,framebuffer_size_callback);
      
          //1,3 Init GLAD
          if(!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)){
              std::cout << "Fail to init glad" << std::endl;
              return -1;
          }
      
          //2. Shader
          int success;
          char InfoLog[512];
          //2.1 vertex shader
          unsigned int vertexShader = glCreateShader(GL_VERTEX_SHADER);
          glShaderSource(vertexShader,1,&vertexShaderSource,NULL);
          glCompileShader(vertexShader);
          glGetShaderiv(vertexShader,GL_COMPILE_STATUS,&success);
          if(!success){
              glGetShaderInfoLog(vertexShader,512,NULL,InfoLog);
              std::cout << "Fail to compile vertexShader\n " << InfoLog << std::endl; 
          }
      
          //2.2 fragment shader
          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 << "Fail to compile fragmentShader\n" << InfoLog << std::endl;
          }
      
          //2.3 shader program
          unsigned int shaderProgram = glCreateProgram();
          glAttachShader(shaderProgram,vertexShader);
          glAttachShader(shaderProgram,fragmentShader);
          glLinkProgram(shaderProgram);
          glGetProgramiv(shaderProgram,GL_COMPILE_STATUS,&success);
          if(!success){
              glGetProgramInfoLog(shaderProgram,512,NULL,InfoLog);
              std::cout << "Fail to compile shaderProgram" << std::endl;
          }
      
          //2.4 delete shader to free memory
          glDeleteShader(vertexShader);
          glDeleteShader(fragmentShader);
      
          //3. vertex data and config
          float first_vertices[] = {
              //first triangle      color
              -0.9f, -0.5f, 0.0f, 1.0f, 0.0f, 0.0f,
              -0.0f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f,
              -0.45f, 0.5f, 0.0f, 0.0f, 0.0f, 1.0f,
          };
          float second_vertices[] = {
              //second triangle
              0.0f, -0.5f, 0.0f,  0.5f, 0.5f, 0.0f,
              0.9f, -0.5f, 0.0f,  0.5f, 0.0f, 0.5f,
              0.45f, 0.5f, 0.0f,  0.0f, 0.5f, 0.5f
          };
          
          unsigned int VAO[2],VBO[2];
          glGenVertexArrays(2,VAO);
          glGenBuffers(2,VBO);
      
          glBindVertexArray(VAO[0]);
      
          glBindBuffer(GL_ARRAY_BUFFER,VBO[0]);
          glBufferData(GL_ARRAY_BUFFER,sizeof(first_vertices),first_vertices,GL_STATIC_DRAW);
          
          glVertexAttribPointer(0,3,GL_FLOAT,GL_FALSE,6*sizeof(float),(void*)0);  //parse position
          glEnableVertexAttribArray(0);
      
          glVertexAttribPointer(1,3,GL_FLOAT,GL_FALSE,6*sizeof(float),(void*)(3*sizeof(float)));
          glEnableVertexAttribArray(1);
      
          glBindVertexArray(0);
      
          glBindVertexArray(VAO[1]);
          glBindBuffer(GL_ARRAY_BUFFER,VBO[1]);
          glBufferData(GL_ARRAY_BUFFER,sizeof(second_vertices),second_vertices,GL_STATIC_DRAW);
          glVertexAttribPointer(0,3,GL_FLOAT,GL_FALSE,6*sizeof(float),(void*)0);
          glEnableVertexAttribArray(0);
          glVertexAttribPointer(1,3,GL_FLOAT,GL_FALSE,6*sizeof(float),(void*)(3*sizeof(float)));
          glEnableVertexAttribArray(1);
          glBindVertexArray(0);
      
      
          //4. loop render
          while(!glfwWindowShouldClose(window)){
              processInput(window);
      
              glClearColor(0.52f,0.93f,0.78f,1.0f);
              glClear(GL_COLOR_BUFFER_BIT);
      
              glUseProgram(shaderProgram);
              //the first render pipeline
              glBindVertexArray(VAO[0]);
              glDrawArrays(GL_TRIANGLES,0,6); //[NOTE]如果数组中一个定点的熟悉长度是3 就是3  如果是6 就是6
              
              //fast swap to the second render pipeline
              glBindVertexArray(VAO[1]);
              glDrawArrays(GL_TRIANGLES,0,6);
      
              glfwSwapBuffers(window);
              glfwPollEvents();
      
          }
          glDeleteVertexArrays(2,VAO);
          glDeleteBuffers(2,VBO);
          glDeleteProgram(shaderProgram);
      
          glfwTerminate();
          return 0;
      }
      
      //function
      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) glfwSetWindowShouldClose(window,true);
      }
      
      
    • 渲染结果
      在这里插入图片描述
  3. 将一个三角形的顶点坐标和颜色放在两个数组中,通过一个VAO和两个VBO绘制

    • 就是把不同的VBO绑到VAO不同的属性点上
      #include <glad/glad.h>
      #include <GLFW/glfw3.h>
      
      
      #include <iostream>
      
      // window parameter
      const unsigned int SCR_WIDTH = 800;
      const unsigned int SCR_HEIGHT = 600;
      const char* SCR_NAME = "Kkaiw";
      
      //framebuffer_size_callback funtion
      void framebuffer_size_callback(GLFWwindow* window,int width,int height);
      
      //input catch funciton
      void processInput(GLFWwindow* window);
      
      //shader source
      const char* vertexShaderSource = "#version 330 core\n"
          "layout (location = 0) in vec3 aPos;\n"
          "layout (location = 1) in vec3 aColor;\n"
          "out vec3 ourColor;\n"
          "void main()\n"
          "{\n"
          "   gl_Position = vec4(aPos,1.0f);\n"
          "   ourColor = aColor;\n"
          "}\0";
      
      const char* fragmentShaderSource = "#version 330 core\n"
          "in vec3 ourColor;\n"
          "out vec4 FragColor;\n"
          "void main()\n"
          "{\n"
          "   FragColor = vec4(ourColor,1.0f);\n"
          "}\n\0";
      
      
      int main(){
          //1. Init
          //1.1 Init GLFW
          glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR,3);
          glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR,3);
          glfwWindowHint(GLFW_OPENGL_PROFILE,GLFW_OPENGL_CORE_PROFILE);
          if(!glfwInit()){
              std::cout << "Fail to init glfw\n" << std::endl;
              return -1;
          }
      #ifdef __APPLE__
          glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT,GL_TRUE);
      #endif
      
          //1.2 Create Window
          GLFWwindow* window = glfwCreateWindow(SCR_WIDTH,SCR_HEIGHT,SCR_NAME,NULL,NULL);
          if(window == NULL){
              std::cout << "Fail to create window\n" << std::endl;
              glfwTerminate();
              return -1;
          }
          glfwMakeContextCurrent(window);
          glfwSetFramebufferSizeCallback(window,framebuffer_size_callback);
      
          //1,3 Init GLAD
          if(!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)){
              std::cout << "Fail to init glad" << std::endl;
              return -1;
          }
      
          //2. Shader
          int success;
          char InfoLog[512];
          //2.1 vertex shader
          unsigned int vertexShader = glCreateShader(GL_VERTEX_SHADER);
          glShaderSource(vertexShader,1,&vertexShaderSource,NULL);
          glCompileShader(vertexShader);
          glGetShaderiv(vertexShader,GL_COMPILE_STATUS,&success);
          if(!success){
              glGetShaderInfoLog(vertexShader,512,NULL,InfoLog);
              std::cout << "Fail to compile vertexShader\n " << InfoLog << std::endl; 
          }
      
          //2.2 fragment shader
          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 << "Fail to compile fragmentShader\n" << InfoLog << std::endl;
          }
      
          //2.3 shader program
          unsigned int shaderProgram = glCreateProgram();
          glAttachShader(shaderProgram,vertexShader);
          glAttachShader(shaderProgram,fragmentShader);
          glLinkProgram(shaderProgram);
          glGetProgramiv(shaderProgram,GL_COMPILE_STATUS,&success);
          if(!success){
              glGetProgramInfoLog(shaderProgram,512,NULL,InfoLog);
              std::cout << "Fail to compile shaderProgram" << std::endl;
          }
      
          //2.4 delete shader to free memory
          glDeleteShader(vertexShader);
          glDeleteShader(fragmentShader);
      
          //3. vertex data and config
          float vertices[] = {
              //first triangle      
              -0.9f, -0.5f, 0.0f, 
              -0.0f, -0.5f, 0.0f, 
              -0.45f, 0.5f, 0.0f, 
          };
          float colors[] = {
              //color
              1.0f, 0.0f, 0.0f,
              0.0f, 1.0f, 0.0f,
              0.0f, 0.0f, 1.0f
          };
          
          unsigned int VAO,VBO[2];
          glGenVertexArrays(1,&VAO);
          glGenBuffers(2,VBO);
      
          glBindVertexArray(VAO);
      
          glBindBuffer(GL_ARRAY_BUFFER,VBO[0]);
          glBufferData(GL_ARRAY_BUFFER,sizeof(vertices),vertices,GL_STATIC_DRAW);
          glVertexAttribPointer(0,3,GL_FLOAT,GL_FALSE,3*sizeof(float),(void*)0);  //parse position
          glEnableVertexAttribArray(0);
      
          glBindBuffer(GL_ARRAY_BUFFER,VBO[1]);
          glBufferData(GL_ARRAY_BUFFER,sizeof(colors),colors,GL_STATIC_DRAW);
          glVertexAttribPointer(1,3,GL_FLOAT,GL_FALSE,3*sizeof(float),(void*)0);
          glEnableVertexAttribArray(1);
      
          glBindVertexArray(0);
      
      
          //4. loop render
          while(!glfwWindowShouldClose(window)){
              processInput(window);
      
              glClearColor(0.52f,0.93f,0.78f,1.0f);
              glClear(GL_COLOR_BUFFER_BIT);
      
              glUseProgram(shaderProgram);
              //render
              glBindVertexArray(VAO);
              glDrawArrays(GL_TRIANGLES,0,6); //[NOTE]如果数组中一个定点的熟悉长度是3 就是3  如果是6 就是6
              
      
              glfwSwapBuffers(window);
              glfwPollEvents();
      
          }
          glDeleteVertexArrays(1,&VAO);
          glDeleteBuffers(2,VBO);
          glDeleteProgram(shaderProgram);
      
          glfwTerminate();
          return 0;
      }
      
      //function
      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) glfwSetWindowShouldClose(window,true);
      }
      
      
    • 渲染结果在这里插入图片描述

3.2.2 用一个数组存两个三角形的顶点坐标,另一个数组存两个三角形的颜色数据,----->通过引入EBO,实现共同顶点数据的复用

  • Question】写到这里,让我产生了疑惑。究竟时1VAO+1VBO+1EBO的模式好,还是1VAO+xVBO+1EBO的模式好?前者代码更清晰,写起来方便。后者不同的顶点属性可以分为不同的数组,让数据更干净。望大佬告知。

    #include <glad/glad.h>
    #include <GLFW/glfw3.h>
    
    
    #include <iostream>
    
    // window parameter
    const unsigned int SCR_WIDTH = 800;
    const unsigned int SCR_HEIGHT = 600;
    const char* SCR_NAME = "Kkaiw";
    
    //framebuffer_size_callback funtion
    void framebuffer_size_callback(GLFWwindow* window,int width,int height);
    
    //input catch funciton
    void processInput(GLFWwindow* window);
    
    //shader source
    const char* vertexShaderSource = "#version 330 core\n"
        "layout (location = 0) in vec3 aPos;\n"
        "layout (location = 1) in vec3 aColor;\n"
        "out vec3 ourColor;\n"
        "void main()\n"
        "{\n"
        "   gl_Position = vec4(aPos,1.0f);\n"
        "   ourColor = aColor;\n"
        "}\0";
    
    const char* fragmentShaderSource = "#version 330 core\n"
        "in vec3 ourColor;\n"
        "out vec4 FragColor;\n"
        "void main()\n"
        "{\n"
        "   FragColor = vec4(ourColor,1.0f);\n"
        "}\n\0";
    
    
    int main(){
        //1. Init
        //1.1 Init GLFW
        glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR,3);
        glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR,3);
        glfwWindowHint(GLFW_OPENGL_PROFILE,GLFW_OPENGL_CORE_PROFILE);
        if(!glfwInit()){
            std::cout << "Fail to init glfw\n" << std::endl;
            return -1;
        }
    #ifdef __APPLE__
        glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT,GL_TRUE);
    #endif
    
        //1.2 Create Window
        GLFWwindow* window = glfwCreateWindow(SCR_WIDTH,SCR_HEIGHT,SCR_NAME,NULL,NULL);
        if(window == NULL){
            std::cout << "Fail to create window\n" << std::endl;
            glfwTerminate();
            return -1;
        }
        glfwMakeContextCurrent(window);
        glfwSetFramebufferSizeCallback(window,framebuffer_size_callback);
    
        //1,3 Init GLAD
        if(!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)){
            std::cout << "Fail to init glad" << std::endl;
            return -1;
        }
    
        //2. Shader
        int success;
        char InfoLog[512];
        //2.1 vertex shader
        unsigned int vertexShader = glCreateShader(GL_VERTEX_SHADER);
        glShaderSource(vertexShader,1,&vertexShaderSource,NULL);
        glCompileShader(vertexShader);
        glGetShaderiv(vertexShader,GL_COMPILE_STATUS,&success);
        if(!success){
            glGetShaderInfoLog(vertexShader,512,NULL,InfoLog);
            std::cout << "Fail to compile vertexShader\n " << InfoLog << std::endl; 
        }
    
        //2.2 fragment shader
        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 << "Fail to compile fragmentShader\n" << InfoLog << std::endl;
        }
    
        //2.3 shader program
        unsigned int shaderProgram = glCreateProgram();
        glAttachShader(shaderProgram,vertexShader);
        glAttachShader(shaderProgram,fragmentShader);
        glLinkProgram(shaderProgram);
        glGetProgramiv(shaderProgram,GL_COMPILE_STATUS,&success);
        if(!success){
            glGetProgramInfoLog(shaderProgram,512,NULL,InfoLog);
            std::cout << "Fail to compile shaderProgram" << std::endl;
        }
    
        //2.4 delete shader to free memory
        glDeleteShader(vertexShader);
        glDeleteShader(fragmentShader);
    
        //3. vertex data and config
        float vertices[] = {
            //first triangle      
            -0.9f, -0.5f, 0.0f, 
            -0.0f, -0.5f, 0.0f, 
            -0.45f, 0.5f, 0.0f, 
            0.9f, -0.5f, 0.0f,
            0.45f, 0.5f, 0.0f
        };
        float colors[] = {
            //color
            1.0f, 0.0f, 0.0f,
            0.0f, 1.0f, 0.0f,
            0.0f, 0.0f, 1.0f,
            0.5f, 0.0f, 0.5f,
            0.0f, 0.5f, 0.5f
        };
        unsigned int indices[] = {
            0, 1, 2,
            2, 3, 4
        };
    
        unsigned int VAO,VBO[2],EBO;
        glGenVertexArrays(1,&VAO);
        glGenBuffers(2,VBO);
        glGenBuffers(1,&EBO);
    
        glBindVertexArray(VAO);
    
        //NOTE!! EBO的数据要在设置解析规则前送入显存! 所以要写在前面。  因为我们是 1*VAO + X*VBO的格式,所以没法把EBO写在VBO的bufferdata和设置解析格式中间!
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,EBO);
        glBufferData(GL_ELEMENT_ARRAY_BUFFER,sizeof(indices),indices,GL_STATIC_DRAW);
    
    
        glBindBuffer(GL_ARRAY_BUFFER,VBO[0]);
        glBufferData(GL_ARRAY_BUFFER,sizeof(vertices),vertices,GL_STATIC_DRAW);
        glVertexAttribPointer(0,3,GL_FLOAT,GL_FALSE,3*sizeof(float),(void*)0);  //parse position
        glEnableVertexAttribArray(0); 
    
        glBindBuffer(GL_ARRAY_BUFFER,VBO[1]);
        glBufferData(GL_ARRAY_BUFFER,sizeof(colors),colors,GL_STATIC_DRAW);
        glVertexAttribPointer(1,3,GL_FLOAT,GL_FALSE,3*sizeof(float),(void*)0);
        glEnableVertexAttribArray(1);
    
        glBindVertexArray(0);
    
    
        //4. loop render
        while(!glfwWindowShouldClose(window)){
            processInput(window);
    
            glClearColor(0.52f,0.93f,0.78f,1.0f);
            glClear(GL_COLOR_BUFFER_BIT);
    
            glUseProgram(shaderProgram);
            //render
            glBindVertexArray(VAO);
            // glDrawArrays(GL_TRIANGLES,0,6); //[NOTE]如果数组中一个定点的熟悉长度是3 就是3  如果是6 就是6
            glDrawElements(GL_TRIANGLES,6,GL_UNSIGNED_INT,0);
            
    
            glfwSwapBuffers(window);
            glfwPollEvents();
    
        }
        glDeleteVertexArrays(1,&VAO);
        glDeleteBuffers(2,VBO);
        glDeleteBuffers(1,&EBO);
        glDeleteProgram(shaderProgram);
    
        glfwTerminate();
        return 0;
    }
    
    //function
    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) glfwSetWindowShouldClose(window,true);
    }
    
    
  • 渲染效果在这里插入图片描述

3.2.3 【Question】如何通过绑定(解绑)缓冲对象实现渲染管线的快速切换?---->可能因为搜索方式不对,未找到清晰的解答???

  • Maybe,3.2的第二个实验就是答案。
  • 在绑定时可以发现,通过关键字GL_ARRAY_BUFFER绑定到不同的关键字,使顶点数据可以发送到不同的VBO---------->这个关键字就像高铁站,VBO就像多条铁路,通过绑定高铁站,数据可以传输到不同的VBO中
  • 在渲染时可以发现,通过绑定不同的VAO,快速切换了两个图形的渲染,应该就是实现渲染管线的快速切换?

3.3 Tips

  1. 着色器程序的layout和in out
    • layout (postion = 0,1…15) 用于告诉着色器源码,每个输入变量对应VAO的哪个顶点属性数据------>对应于glVertexAttribPointer中的0,1…15
    • in和out用于定义着色器程序中的输入和输出变量,顶点着色器必有输入变量(因为要从VBO中按顶点属性拿数据),片段着色器必有输出变量,因为要把颜色传出去。
  2. 顶点坐标的范围,颜色的范围
    • [-1,1],[-1,1],[-1,1]的坐标系,(x,y,z)=(0,0,0)在视域的中心点
    • 颜色范围将[0,255]归一化到[0,1],即黑到白。------>所以可以知道为什么第一个渲染图中左下角是黑色的,因为坐标是负值,剪裁成0了。
  3. 在3.2的实验1中,我们将两个三角形的顶点坐标存入同一个VBO,OPENGL通过GL_TRIANGLES如何分别谁是谁的顶点?
    • 以glDrawArrays()的第三个参数决定每个顶点的数据长度,然后依次1-3,4-6的遍历取,如果此时只有任意5行数据而不是代码中的6行,则会自动将那行补0.

四、本节课后作业及其注意事项

  1. 作业1:EBO复用两个相同的顶点就行,即顶点数组中只需要4组坐标
  2. 作业2:画多个三角形的VAO和VBO使用方法
    - 可以 xVAO + xVBO,即一个VAO对应一个VBO,即LearnOpenGL中画的那个VAO,VBO,EBO的关系图(也即本文3.2节的测试2)
    - 也可以 1VAO + xVBO, 即本文3.2节的测试3
    - 也可以 1VAO + 1VBO, 即本文3.2节的测试1
  3. 作业3:即为两个三角形分别生成一个着色器程序

五、BUG记录

  1. glCreateShader()函数显示未定义,但库都是有效的。-----> 把glad.h的#include语句放到glfw3.h的#include语句前,错误消失。WHY?
  2. 着色器程序显示以下报错—>着色器程序中变量名写错了或者缺少#等字符型拼接错误
  3. 屏幕显示一瞬间就消失,但未报错------》窗口关闭状态的返回值问题
  4. 本文3.3节,1VAO + XVBO + 1*EBO的设计模式,如何摆放EBO BufferData的位置及Draw函数?
    - 首先,需要在解析顶点属性前完成EBO的传输,因为有EBO时解析需要用EBO的数据。其次,因为该测试代码是1个VAO拖多个VBO,所以不能像一个VAO拖一个VBO一样卸载VBO的bufferdata和解析顶点属性中间,因为多个VBO切换绑定GL_ARRAY_BUFFER这个关键字时,前面的又关闭了(看前面说的火车站的例子)。
    - 用了EBO,绘制时需要用glDrawElements,否则会忽略索引,自动补原点。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
### 回答1: OpenGL中的VAOVBOEBO是三种重要的缓存对象,用于存储和管理顶点数据和索引数据。 VAO(Vertex Array Object)是顶点数组对象,用于存储顶点数据的属性信息,如顶点坐标、颜色、法向量等。VAO可以将多个VBO绑定到同一个VAO中,方便管理和使用。 VBO(Vertex Buffer Object)是顶点缓存对象,用于存储顶点数据的实际数值,如顶点坐标、颜色、法向量等。使用VBO可以提高渲染效率,减少数据传输次数。 EBO(Element Buffer Object)是索引缓存对象,用于存储顶点索引数据,可以减少重复顶点的存储,提高渲染效率。 总之,VAOVBOEBOOpenGL中非常重要的缓存对象,可以提高渲染效率,减少数据传输次数,方便管理和使用。 ### 回答2: OpenGL是一款广泛应用于图形渲染的开源3D图形库。VAOVBOEBO这三个单词是OpenGL中的三种不同类型的缓冲区对象。 VAO,即顶点属性对象,是一个OpenGL缓冲区对象,用于存储和管理顶点属性,即描述一个图元的顶点的各种属性,如顶点坐标、颜色、法线等。VAO可以容纳多个VBO,而且可以把VBO的数据分组合起来,VBO的顶点属性索引可以绑定到VAO中,这样一来,当我们要渲染一个物体时,只需绑定一个VAO对象,就可以一次性调用所有需要的VBOVBO,即顶点缓冲区对象,是一个OpenGL缓冲区对象,用于高效存储顶点数据,如顶点坐标、顶点颜色等。当需要渲染一个物体时,我们可以从VBO中读取需要的顶点信息,然后传递给渲染管线进行处理和渲染。使用VBO有一些显著的优势,比如,VBO能够提升渲染性能,减少CPU和GPU之间的数据传输,同时还可以减少内存占用。 EBO,即索引缓冲区对象,是一种存储一系列索引的OpenGL缓冲区对象。EBO中的索引是用于指定顶点顺序的,即用于描述三角形顶点之间的关系。使用EBO可以大大减少需要传输给GPU的数据量,并且可以避免重复的顶点数据,提升渲染性能。 一般情况下,我们通过VAOVBOEBO来实现复杂图形的绘制。在渲染一个物体时,首先需要创建一个VAO对象,并绑定所有需要使用的VBOEBO。然后将这些缓冲区对象中的数据存储到GPU中,并在渲染时调用这些数据。最后,将VAO对象绑定到渲染管道中,执行图形渲染操作。 ### 回答3: OpenGL是一种图形库,它可以用来渲染三维场景或者二维绘图。VAOVBOEBO都是OpenGL中的重要概念,用于优化渲染性能和提高代码可读性。 VAO(Vertex Array Object)是一个顶点数组对象,它保存了一组顶点数据的状态。每个顶点包含诸如位置、法向量、纹理坐标等属性。VAO可以将这些属性打包成一个容器,使得OpenGL可以更快地访问和处理数据。VAO通常包括各种属性缓冲对象(VBO),它们保存了不同类型的属性数据。 VBO(Vertex Buffer Object)是OpenGL中的缓冲对象,用于存储顶点数据。VBO可以存储各种顶点属性,如位置、法向量、颜色等。VBO可以在GPU内存中创建,并且可以被透明地映射到CPU内存。这意味着我们可以直接在CPU内存中修改VBO的数据,而不必将其复制到GPU中。 EBO(Element Buffer Object)是一个索引缓冲对象,它充当索引数组的角色。EBO中存储的是顶点的索引,这些索引对应于VBO中存储的实际顶点数据。使用EBO可以减少顶点数据的冗余性。例如,在多个三角形之间共享相同的顶点数据时,可以使用EBO来避免重复数据的存储。 总之,VAOVBOEBOOpenGL中的重要工具,它们可以大大提高渲染性能和代码可读性。VAO提供了一种整理顶点属性的方法,VBO可以高效地管理顶点数据,而EBO可以减少数据冗余。对于OpenGL编程来说,深入了解VAOVBOEBO是非常重要的。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值