OpenGL ES Shader相关API 总结【5】——VBO与VAO的作用与关系

举报
ShaderJoy 发表于 2021/12/29 23:37:48 2021/12/29
【摘要】 早期的OpenGL为了将模型的顶点数据传送到显卡,需要逐个顶点进行(冗余处理的问题),如果还需要额外的信息(纹理坐标和法线)的话,当模型比较复杂时,将导致大量函数的调用,传输开销是相当大的!为了解决这个问题引入了VBO(Vertex Buffer Object),VBO可以将顶点数据保存在显存中,绘制时直接从显存中取数据,减少了数据传输...

早期的OpenGL为了将模型的顶点数据传送到显卡,需要逐个顶点进行(冗余处理的问题),如果还需要额外的信息(纹理坐标和法线)的话,当模型比较复杂时,将导致大量函数的调用,传输开销是相当大的!为了解决这个问题引入了VBO(Vertex Buffer Object),VBO可以将顶点数据保存在显存中,绘制时直接从显存中取数据,减少了数据传输的开销。

顶点属性(Vertex Attribute),是关于顶点坐标和顶点纹理、顶点法线以及其他信息的统称。

顶点的格式:

 


  
  1. typedef struct
  2. {
  3. float x;
  4. float y;
  5. float z;
  6. }Vec3;
  7. typedef struct
  8. {
  9. float x;
  10. float y;
  11. }
  12. typedef struct
  13. {
  14. Vec v;
  15. Vec2 vt;
  16. Vec3 vn;
  17. }Vertex;


从以上可知,通过VBO我们可以将顶点属性数据保存在显存中,当绘制时问题又来了,需要调用好几个函数,过程挺复杂的。为了解决这个问题,OpenGL又引入了VAO(Vertex Array Object)来关联VBO中的数据 (VAO详解0VAO详解1),有了VAO,任何数组形式的GL函数调用都会添加到VAO的绘制列表当中(直到解除VAO绑定),当需要绘制的时候,我们仅需要重新绑定VAO,那么之前创建的绘制列表将会重新激活,使得绘制代码更加简洁。

 

举个例子:假设顶点着色器中定义了以下三个变量,用于保存顶点属性

 


  
  1. attribute vec3 vertex_position;
  2. attribute vec2 vertex_texture_coord;
  3. attribute vec3 vertex_normal;

 

 

在C++代码中定义一个init函数,用于加载shader、模型、纹理,以及最重要的创建VBO和VAO和关联它们

 


  
  1. // 初始化
  2. void init()
  3. {
  4. pid = load_shader_program("vertex.glsl", "fragment.glsl");
  5. // 获取vertex_position、vertex_texture_coord和vertex_nromal的位置
  6. GLuint vertex_position_loc = glGetAttribLocation(pid, "vertex_position");
  7. GLuint vertex_texture_coord_loc = glGetAttribLocation(pid, "vertex_texture_coord");
  8. GLuint vertex_normal_loc = glGetAttribLocation(pid, "vertex_normal");
  9. // 加载纹理
  10. texture_id = create_texture_2d("texture.bmp");
  11. // 加载模型
  12. loadModel("cube.obj", vertices);
  13. // 设置相机初始位置
  14. camera.set_position(0.0f, 0.0f, 2.0f);
  15. // 创建VBO
  16. GLuint vbo_id;
  17. glGenBuffers(1, &vbo_id);
  18. glBindBuffer(GL_ARRAY_BUFFER, vbo_id);
  19. // 传输数据
  20. glBufferData(GL_ARRAY_BUFFER, sizeof(Vertex) * vertices.size(), &vertices[0], GL_STATIC_DRAW);
  21. // 创建VAO
  22. GLuint vao_id;
  23. glGenVertexArrays(1, &vao_id);
  24. glBindVertexArray(vao_id);
  25. // 启用顶点属性
  26. glEnableVertexAttribArray(vertex_position_loc);
  27. glEnableVertexAttribArray(vertex_texture_coord_loc);
  28. glEnableVertexAttribArray(vertex_normal_loc);
  29. glBindBuffer(GL_ARRAY_BUFFER, vbo_id);
  30. // vertex_position_loc与顶点数据映射
  31. glVertexAttribPointer(vertex_position_loc, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), 0);
  32. // vertex_texture_coord_loc与纹理数据映射
  33. glVertexAttribPointer(vertex_texture_coord_loc, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (0+sizeof(Vec3)));
  34. // vertex_normal_loc与法线数据映射
  35. glVertexAttribPointer(vertex_normal_loc, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (GLvoid*)(0+sizeof(Vec3) + sizeof(Vec2)));
  36. }


glBufferData用于向VBO传递数据:第二个参数表示数据的大小,第三个参数表示指向数据的指针;

 

glVertexAttribPointer用于将顶点着色器中的attribute变量与VBO中的数据进行绑定:第一个参数表示属性的位置,第二个参数表示分量的个数,第三个参数表示数据类型,第四个参数表示是否归一化,第五个参数表示连续顶点属性之间的偏移量,第六个参数表示组件的第一个分量在对应的数组顶点属性中的偏移量

例子中VBO存储的数据格式如下:

 


  
  1. VBO = {v1.x, v1.y, v1.z, vt1.x, vt1.y, vt1.z, vn1.x, vn1.y, vn1.z,
  2. v2.x, v2.y, v2.z, vt2.x, vt2.y, vt2.z, vn2.x, vn2.y, vn2.z,
  3. ... ...
  4. vn.x, vn.y, vn.z, vtn.x, vtn.y, vtn.z, vnn.x, vnn.y, vnn.z,}


以取到法线数据为例,法线数据是vn1.xyz到vnn.xyz,从VBO的格式可知,vn1.x在VBO的 0+sizeof(Vec3)+sizeof(Vec2)位置,vn1.x到vn2.x之间相差了sizeof(Vertex)大小,这就是glVertexAttribPointer第五和第六个参数的由来。

 

 

最后绘制的时候,我们只需要调用以下代码即可

 


  
  1. glBindVertexArray(vao_id);
  2. glDrawArrays(GL_QUADS, 0, (GLsizei)vertices.size());

 

文章来源: panda1234lee.blog.csdn.net,作者:panda1234lee,版权归原作者所有,如需转载,请联系作者。

原文链接:panda1234lee.blog.csdn.net/article/details/51424156

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。