- 着色器的定义改了
- 顶点坐标、颜色,索引缓存数据定义
- VAO 使用
- 先生成 VAO
- 再绑定 VAO
- 再设置VAO 当前指向的VBO
- 再设置VAO 当前指向的VBO格式
- 再设置VAO 当前指向的EBO
- 绘制对应的 VAO 指向的数据的 VBO,EBO,和VBO格式之前,先绑定对应的VAO
- 绘制
- 第一个Quad
- 第二个Quad
- 绘制效果
- 完整源码
- 总结
- References
本人才疏学浅,如有什么错误,望不吝指出。
上一篇:LearnGL - 04 - VAO 探究,对 VAO 有了个基本的了解。
这一篇:使用 VAO 来绘制两个不同材质的 Quad(Quadrilateral) 四边形。
顺便值得一提:前一个星期,EA 公司开源了两个游戏项目:《红色警戒》、《命令与征服》。
然后进去瞄了一下源码,是 C++ 的,书写的相当工整,都是 1991~1995 年的代码。注释也是相当全面与工整,看起来很养眼。
因为要使用到 VAO 所以概述一下结构,用了Excel画了一些图解:
- VBO 可参考:
- LearnGL - 02 - DrawTriangle - VBO
- LearnGL - 02.1 - DrawTriangle_Extension - VBO
- EBO 可参考:
- LearnGL - 03 - DrawQuad - VBO/EBO
- LearnGL - 03 - DrawQuad - VBO/EBO
想要理解 VAO 的大致工作原理,最好想看看我之前写的一个模仿 OpenGL 的 API 来实现 VAO 的功能项目:LearnGL - 04 - VAO 探究。
这里再简述一下 VAO 一般的使用方式:
- 先生成 VAO
- 再绑定 VAO
- 再设置VAO 当前指向的 VBO
- 再设置VAO 当前指向的 VBO格式
- 再设置VAO 当前指向的 EBO
- 绘制对应的 VAO 指向的数据的 VBO,EBO,和VBO格式 之前,先绑定 对应的 VAO
具体可以查看我上面的车时项目:LearnGL - 04 - VAO 探究
着色器的定义改了这回我们的着色器程序的硬边码直接使用的是C++的 R"flag()flag" 的raw-string的方式来定义,书写更方便
// 这回我们的着色器程序的硬边码直接使用的是C++的 R"flag()flag" 的raw-string的方式来定义,书写更方便
static const char* vertex_shader_text = R"glsl(
#version 450 compatibility
uniform mat4 transformMat;
attribute vec3 vPos;
attribute vec3 vCol;
varying vec3 fCol;
void main() {
gl_Position = transformMat * vec4(vPos, 1.0);
fCol = vCol;
}
)glsl";
static const char* fragment_shader_text = R"glsl(
#version 450 compatibility
varying vec3 fCol;
void main() {
gl_FragColor = vec4(fCol, 1.0);
}
)glsl";
顶点坐标、颜色,索引缓存数据定义
GLfloat vertices[] = {
// x, y, z
// 直接放4个顶点
-0.25f, -0.25f, 0.0f, // 第0个顶点,左下角
0.25f, -0.25f, 0.0f, // 第1个顶点,右下角
0.25f, 0.25f, 0.0f, // 第2个顶点,右上角
-0.25f, 0.25f, 0.0f, // 第3个顶点,左上角
};
GLfloat colors_1[] = { // 顶点颜色缓存数据1
1.0f, 0.0f, 0.0f, // 第0个顶点颜色
0.0f, 1.0f, 0.0f, // 第1个顶点颜色
1.0f, 1.0f, 0.0f, // 第2个顶点颜色
0.0f, 0.0f, 1.0f, // 第3个顶点颜色
};
GLfloat colors_2[] = { // 顶点颜色缓存数据2
1.0f, 1.0f, 0.0f, // 第0个顶点颜色
0.0f, 1.0f, 1.0f, // 第1个顶点颜色
1.0f, 1.0f, 1.0f, // 第2个顶点颜色
1.0f, 0.0f, 1.0f, // 第3个顶点颜色
};
GLuint indices[] = { // 注意索引从0开始!通过索引缓存来指定 图元 组成 用的 顶点有哪些
0, 1, 3, // 放置顶点的索引,第一个三角形
1, 2, 3 // 放置顶点的索引,第二个三角形
};
因为这次绘制的两个 Quad 都是不同的颜色的,所以我们的 colors 有两个缓存数据。
而顶点的坐标、绘制索引顺序都是一样的,索引这些数据都没有变化。
VAO 使用要记得 VAO 的使用步骤哦(前面有列出来的):
- 先生成 VAO
- 再绑定 VAO
- 再设置VAO 当前指向的 VBO
- 再设置VAO 当前指向的 VBO格式
- 再设置VAO 当前指向的 EBO
- 绘制对应的 VAO 指向的数据的 VBO,EBO,和VBO格式 之前,先绑定 对应的 VAO
GLuint vertex_array_object[2];
...
glGenVertexArrays(2, vertex_array_object); // 生成两个 VAO
glGenBuffers(3, vertex_buffer); // 创建3个 VBO,这里我们因为有一个一样的顶点,两个不同的顶点顔色
glGenBuffers(1, &index_buffer); // 创建1个 EBO,因为两个 Quad 的顶点索引顺序都是一样的
glGenVertexArrays(2, vertex_array_object);
生成了2个 VAO 指针,分别用于指向两个 Quad 的绘制 VBO,EBO,VBO格式,等。glGenBuffers(3, vertex_buffer);
生成3个 VBO 指针,[0] 是坐标,都一样;[1] 是 Quad[0] 的顶点颜色 绑定于VAO[0];[2] 是 Quad[1] 的顶点颜色 绑定于VAO[1]。glGenBuffers(1, &index_buffer);
生成一个 EBO 指针,用于两个 VAO 都指向这个同样的 EBO。
主要是 glGenVertexArrays(2, vertex_array_object);
是 生成 VBO 的
glBindVertexArray(vertex_array_object[0]); // 绑定 VAO[0],那么之后的 vbo, ebo,的绑定指针都是指向该 VAO 中的,还有顶点格式(规范)都会保存在该 VAO
执行这句后,当前的 VAO 就是 vertex_array_object[0]
对应的 VAO。
glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer[0]); // 绑定 VBO[0]
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); // 设置 VBO 坐标数据
glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer[1]); // 绑定 VBO[0]
glBufferData(GL_ARRAY_BUFFER, sizeof(colors_1), colors_1, GL_STATIC_DRAW); // 设置 VBO 颜色数据
这时绑定的 VBO 数据都会在 VAO 对象中有指针保存着。(当前的 VAO 是 vertex_array_object[0]
)
glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer[0]); // 绑定 VBO[0],因为后面要设置该 VBO 的坐标格式
glVertexAttribPointer(vpos_location, 3, GL_FLOAT, GL_FALSE, // 设置 顶点属性 vPos 格式
sizeof(GLfloat) * 3, (GLvoid*)0);
glEnableVertexAttribArray(vpos_location); // 启用 顶点缓存 location 位置的属性
这时在 glVertexAttribPointer
后,当前的 VBO 的格式将保存在该 VAO 对象中。(当前的 VAO 是 vertex_array_object[0]
)
EBO 的绘制也不是必须的,主要看的绘制接口是否用到。(当然 VBO 也不是必须的,因为可以使用其他绘制接口,如:立即绘制模式的接口。但如果 VBO 都不用的话, VAO就没有什么意义了)
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, index_buffer); // 绑定 EBO
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW); // 设置 EBO 数据
绘制对应的 VAO 指向的数据的 VBO,EBO,和VBO格式之前,先绑定对应的VAO
...
glBindVertexArray(vertex_array_object[0]); // 先绘制 VAO[0] 的 VBO,EBO,VAF,ENABLED
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, (GLvoid*)0);
...
glDrawElements
接口之前说过是使用了 GL_ELEMENT_ARRAY_OBJECT
类型的缓存数据,就是索引缓存的数据。
另一个 vertex_array_object[1]
也是需要上面的过程来处理的。
唯独需要注意的是 colors_2
的顶点颜色设置是不同的。
glBindVertexArray(vertex_array_object[0]);
...
glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer[1]); // 绑定 VBO[0]
glBufferData(GL_ARRAY_BUFFER, sizeof(colors_2), colors_2, GL_STATIC_DRAW); // 设置 VBO 颜色数据
绘制
第一个Quad
glBindVertexArray(vertex_array_object[0]); // 先绘制 VAO[0] 的 VBO,EBO,VAF,ENABLED
mat4x4_identity(tMat); // 给矩阵单位化,消除之前的所有变换
mat4x4_translate(tMat, -0.5, 0.0f, 0.0f); // x轴位移-0.5,注意是NDC下的坐标
glUniformMatrix4fv(mat_location, 1, GL_FALSE, (const GLfloat*)tMat); // 设置, 着色器中 uniform mat4 rMat; 的矩阵数据
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, (GLvoid*)0); // 参数1:绘制三角图元;参数2:取6个索引来绘制三角图元(每个三角图元需要3个,所以可以画两个三角图元);参数3:将 GL_ELEMENT_ARRAY_BUFFER 每个元素视为 uint 类型;参数4:设置索引缓存的字节偏移量。也可以设置为另一个 缓存数据的指针,即:使用另一个数据。
瞧瞧,现在绘制简单多了,以后绘制多个对象的时候,都可以这么使用。
注意有:mat4x4_translate(tMat, -0.5, 0.0f, 0.0f);
,将第一个 Quad 以x轴位移到 NDC 坐标下偏移:-0.5f
的单位。
glBindVertexArray(vertex_array_object[1]); // 先绘制 VAO[1] 的 VBO,EBO,VAF,ENABLED
mat4x4_identity(rMat); // 给矩阵单位化,消除之前的所有变换
mat4x4_rotate_Z(rMat, rMat, (float)glfwGetTime()); // 先旋转,沿着 z 轴旋转,旋转量为当前 glfw 启用到现在的时间点(秒)
mat4x4_translate(tMat, +0.5, 0.0f, 0.0f); // 再位移
mat4x4_mul(tranformMat, tMat, rMat); // 将旋转与位移的变换合并
glUniformMatrix4fv(mat_location, 1, GL_FALSE, (const GLfloat*)tranformMat); // 设置, 着色器中 uniform mat4 rMat; 的矩阵数据
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, (GLvoid*)0); // 参数1:绘制三角图元;参数2:取6个索引来绘制三角图元(每个三角图元需要3个,所以可以画两个三角图元);参数3:将 GL_ELEMENT_ARRAY_BUFFER 每个元素视为 uint 类型;参数4:设置索引缓存的字节偏移量。也可以设置为另一个 缓存数据的指针,即:使用另一个数据。
第二个 Quad 可以看到,除了有 位移,而且还有 旋转,然后还使用另一个 linmath.h
的API:mat4x4_mul
,来将两个变换柔和在一起了,这里要注意 mat4x4_mul
API中 参数2,参数3 的位移,这里我是先旋转,再位移,将 先 变换的矩阵放到 右 边,后续 新增 变换的矩阵放 左边,有点类似 矩阵左乘(就是将新的要变换的矩阵放到左边去乘上右边原来的变换矩阵,如:有矩阵A,矩阵B。矩阵C=BA,B矩阵就是A的左乘矩阵)的。注意矩阵乘法是不满足交换律的,所以这些相乘的顺序要注意。
// jave.lin
#include"glad/glad.h"
#include"GLFW/glfw3.h"
//#include"linmath.h"
// 把linmath.h 放在 iostream 之前include会有错误,所以放到iostream 后include就好了
// 而这个错误正式 xkeycheck.h 文件内 #error 提示的,所以可以使用 #define _XKEYCHECK_H 这个头文件的引用标记宏
// 就可以避免对 xkeycheck.h 头文件的 include 了。
#include
#include"linmath.h"
#define PRINT_VERSION // 打印版本相关信息
#ifdef PRINT_VERSION
#define PROFILE_NAME_CORE "core"
#define PROFILE_NAME_COMPAT "compat"
static const char* get_api_name(int api) {
if (api == GLFW_OPENGL_API)
return "OpenGL";
else if (api == GLFW_OPENGL_ES_API)
return "OpenGL ES";
return "Unknown API";
}
static const char* get_profile_name_gl(GLint mask) {
if (mask & GL_CONTEXT_COMPATIBILITY_PROFILE_BIT)
return PROFILE_NAME_COMPAT;
if (mask & GL_CONTEXT_CORE_PROFILE_BIT)
return PROFILE_NAME_CORE;
return "unknown";
}
// 打印各种版本信息
static void print_infos(GLFWwindow* window) {
//
// ====== GLFW Library的版本 ======
//
int glfw_major, glfw_minor, glfw_revision;
glfwGetVersion(&glfw_major, &glfw_minor, &glfw_revision);
// 头文件声明版本
printf("GLFW header version: %u.%u.%u\n",
GLFW_VERSION_MAJOR,
GLFW_VERSION_MINOR,
GLFW_VERSION_REVISION);
// 库版本
printf("GLFW library version: %u.%u.%u\n", glfw_major, glfw_minor, glfw_revision);
// 库版本的字符串描述
printf("GLFW library version string: \"%s\"\n", glfwGetVersionString());
//
// ====== client, context, profile 的版本 ======
//
int ch, client, major, minor, revision, profile;
client = glfwGetWindowAttrib(window, GLFW_CLIENT_API);
major = glfwGetWindowAttrib(window, GLFW_CONTEXT_VERSION_MAJOR);
minor = glfwGetWindowAttrib(window, GLFW_CONTEXT_VERSION_MINOR);
revision = glfwGetWindowAttrib(window, GLFW_CONTEXT_REVISION);
profile = glfwGetWindowAttrib(window, GLFW_OPENGL_PROFILE);
printf("%s context version string: \"%s\"\n",
get_api_name(client),
glGetString(GL_VERSION));
printf("%s context version parsed by GLFW: %u.%u.%u\n",
get_api_name(client),
major, minor, revision);
GLint mask;
glGetIntegerv(GL_CONTEXT_PROFILE_MASK, &mask);
printf("%s profile mask (0x%08x): %s\n",
get_api_name(client),
mask,
get_profile_name_gl(mask));
//
// ====== render, vendor 的信息 ======
//
printf("%s context renderer string: \"%s\"\n",
get_api_name(client),
glGetString(GL_RENDERER));
printf("%s context vendor string: \"%s\"\n",
get_api_name(client),
glGetString(GL_VENDOR));
printf("%s context shading language version: \"%s\"\n",
get_api_name(client),
glGetString(GL_SHADING_LANGUAGE_VERSION));
}
#endif // PRINT_VERSION
// 这回我们的着色器程序的硬边码直接使用的是C++的 R"flag()flag" 的raw-string的方式来定义,书写更方便
static const char* vertex_shader_text = R"glsl(
#version 450 compatibility
uniform mat4 transformMat;
attribute vec3 vPos;
attribute vec3 vCol;
varying vec3 fCol;
void main() {
gl_Position = transformMat * vec4(vPos, 1.0);
fCol = vCol;
}
)glsl";
static const char* fragment_shader_text = R"glsl(
#version 450 compatibility
varying vec3 fCol;
void main() {
gl_FragColor = vec4(fCol, 1.0);
}
)glsl";
GLfloat vertices[] = {
// x, y, z
// 直接放4个顶点
-0.25f, -0.25f, 0.0f, // 第0个顶点,左下角
0.25f, -0.25f, 0.0f, // 第1个顶点,右下角
0.25f, 0.25f, 0.0f, // 第2个顶点,右上角
-0.25f, 0.25f, 0.0f, // 第3个顶点,左上角
};
GLfloat colors_1[] = { // 顶点颜色缓存数据1
1.0f, 0.0f, 0.0f, // 第0个顶点颜色
0.0f, 1.0f, 0.0f, // 第1个顶点颜色
1.0f, 1.0f, 0.0f, // 第2个顶点颜色
0.0f, 0.0f, 1.0f, // 第3个顶点颜色
};
GLfloat colors_2[] = { // 顶点颜色缓存数据2
1.0f, 1.0f, 0.0f, // 第0个顶点颜色
0.0f, 1.0f, 1.0f, // 第1个顶点颜色
1.0f, 1.0f, 1.0f, // 第2个顶点颜色
1.0f, 0.0f, 1.0f, // 第3个顶点颜色
};
GLuint indices[] = { // 注意索引从0开始!通过索引缓存来指定 图元 组成 用的 顶点有哪些
0, 1, 3, // 放置顶点的索引,第一个三角形
1, 2, 3 // 放置顶点的索引,第二个三角形
};
static void error_callback(int error, const char* description) {
fprintf(stderr, "ErrorCode : %d(0x%08x), Error: %s\n", error, error, description);
}
static void key_callback(GLFWwindow* window, int key, int scancode, int action, int mods) { // 当键盘按键ESCAPE按下时,设置该window为:需要关闭
if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS)
glfwSetWindowShouldClose(window, GLFW_TRUE);
}
int main() {
glfwSetErrorCallback(error_callback); // 安装glfw内部错误时的回调
if (!glfwInit()) { // 初始化glfw
std::cout
关注
打赏
最近更新
- 深拷贝和浅拷贝的区别(重点)
- 【Vue】走进Vue框架世界
- 【云服务器】项目部署—搭建网站—vue电商后台管理系统
- 【React介绍】 一文带你深入React
- 【React】React组件实例的三大属性之state,props,refs(你学废了吗)
- 【脚手架VueCLI】从零开始,创建一个VUE项目
- 【React】深入理解React组件生命周期----图文详解(含代码)
- 【React】DOM的Diffing算法是什么?以及DOM中key的作用----经典面试题
- 【React】1_使用React脚手架创建项目步骤--------详解(含项目结构说明)
- 【React】2_如何使用react脚手架写一个简单的页面?