LearnGL - 学习笔记目录
本人才疏学浅,如有什么错误,望不吝指出。
上些篇:
- LearnGL - 06 - Matrix - 矩阵01 - 齐次坐标、缩放、旋转矩阵,了解了矩阵就是定义一个坐标空间的轴向
- LearnGL - 06.1 - Matrix - 矩阵02 - 向量空间、向量空间的维度、为何矩阵乘法要有 [M x N] * [N * P] 的 N 要相等的限制,了解了一些矩阵的乘法的维度限制
- LearnGL - 06.2 - Matrix - 矩阵03 - 逆矩阵、行列式、伴随矩阵、余子式、代数余子式、练习,了解了逆矩阵相关内容
这一篇搬砖:我们来绘制一个Cube立方体。
构造顶点、索引、uv数据
我们之前绘制一个Quad就用了 4 个顶点,用了6个索引,两个三角图元。
那我们现在要绘制一个Cube立方体呢?
需要绘制 6 个 Quad 用到 24 个顶点即可。(其实也可以使用 8 个顶点的,但是UV坐标就不好控制,所以我们使用 24 顶点的方式)
但是索引的话,之前每个面,即:每个 Quad 需要用到 6个,现在需要绘制 6 个Quad,也就是 6*6=36个索引。
那么画一个立方体,标上每个顶点的索引,便于我们来构造好顶点、索引、uv数据:
GLfloat vertices[] = {
// x, y, z
// 直接放 24 个顶点
// back
-0.5f, -0.5f, -0.5f, // 第0 个顶点
0.5f, -0.5f, -0.5f, // 第1 个顶点
0.5f, 0.5f, -0.5f, // 第2 个顶点
-0.5f, 0.5f, -0.5f, // 第3 个顶点
// front
0.5f, -0.5f, 0.5f, // 第4 个顶点
-0.5f, -0.5f, 0.5f, // 第5 个顶点
-0.5f, 0.5f, 0.5f, // 第6 个顶点
0.5f, 0.5f, 0.5f, // 第7 个顶点
// left
-0.5f, -0.5f, 0.5f, // 第8 个顶点
-0.5f, -0.5f, -0.5f, // 第9 个顶点
-0.5f, 0.5f, -0.5f, // 第10个顶点
-0.5f, 0.5f, 0.5f, // 第11个顶点
// right
0.5f, -0.5f, -0.5f, // 第12个顶点
0.5f, -0.5f, 0.5f, // 第13个顶点
0.5f, 0.5f, 0.5f, // 第14个顶点
0.5f, 0.5f, -0.5f, // 第15个顶点
// top
-0.5f, 0.5f, -0.5f, // 第16个顶点
0.5f, 0.5f, -0.5f, // 第17个顶点
0.5f, 0.5f, 0.5f, // 第18个顶点
-0.5f, 0.5f, 0.5f, // 第19个顶点
// bottom
-0.5f, -0.5f, 0.5f, // 第20个顶点
0.5f, -0.5f, 0.5f, // 第21个顶点
0.5f, -0.5f, -0.5f, // 第22个顶点
-0.5f, -0.5f, -0.5f, // 第23个顶点
};
GLfloat uvs[] = { // 顶点的 uv 坐标
// back
0.0f, 0.0f, // 左下角
1.0f, 0.0f, // 右下角
1.0f, 1.0f, // 右上角
0.0f, 1.0f, // 左上角
// front
0.0f, 0.0f, // 左下角
1.0f, 0.0f, // 右下角
1.0f, 1.0f, // 右上角
0.0f, 1.0f, // 左上角
// left
0.0f, 0.0f, // 左下角
1.0f, 0.0f, // 右下角
1.0f, 1.0f, // 右上角
0.0f, 1.0f, // 左上角
// right
0.0f, 0.0f, // 左下角
1.0f, 0.0f, // 右下角
1.0f, 1.0f, // 右上角
0.0f, 1.0f, // 左上角
// top
0.0f, 0.0f, // 左下角
1.0f, 0.0f, // 右下角
1.0f, 1.0f, // 右上角
0.0f, 1.0f, // 左上角
// bottom
0.0f, 0.0f, // 左下角
1.0f, 0.0f, // 右下角
1.0f, 1.0f, // 右上角
0.0f, 1.0f, // 左上角
};
GLuint indices[] = { // 注意索引从0开始!通过索引缓存来指定 图元 组成 用的 顶点有哪些
// back
0, 1, 2, // 第 0 个三角面
2, 3, 0, // 第 1 个三角面
// front
4, 5, 6, // 第 2 个三角面
6, 7, 4, // 第 3 个三角面
// left
8, 9, 10, // 第 4 个三角面
10, 11, 8, // 第 5 个三角面
// right
12, 13, 14, // 第 6 个三角面
14, 15, 12, // 第 7 个三角面
// top
16, 17, 18, // 第 8 个三角面
18, 19, 16, // 第 9 个三角面
// bottom
20, 21, 22, // 第 10个三角面
22, 23, 20, // 第 11个三角面
};
绘制 36 个索引对应的顶点
glDrawElements(GL_TRIANGLES, 36, GL_UNSIGNED_INT, (GLvoid*)0); // 绘制 36 个索引对应的顶点
让 Cube 旋转起来
为了让 cube 旋转起来,我们需要再着色器添加一个 uniform mat4 rMat;
,具体怎么旋转我之前的文章有提到:LearnGL - 06 - Matrix - 矩阵01。
着色器没怎么改动,就按照之前绘制 texture 纹理的着色器中,对顶点着色器添加了 rMat
:
// jave.lin - draw_cube.vert - 绘制 cube
#version 450 compatibility
uniform mat4 rMat;
uniform float time;
attribute vec3 vPos;
attribute vec2 vUV;
varying vec2 fUV;
void main() {
gl_Position = rMat * vec4(vPos, 1.0);
fUV = vUV;
}
// jave.lin - draw_cube.frag - 绘制 cube
#version 450 compatibility
varying vec2 fUV; // uv 坐标
varying vec3 fPos;
uniform sampler2D main_tex; // 主纹理
uniform sampler2D mask_tex; // 遮罩纹理
uniform sampler2D flash_light_tex; // 闪光/流光纹理
uniform float time; // 时间(秒)用于动画
void main() {
vec3 mainCol = texture(main_tex, fUV).rgb;
float mask = texture(mask_tex, fUV).r;
vec4 flashCol = texture(flash_light_tex, fUV + vec2(-time, 0));
flashCol *= flashCol.a * mask;
mainCol = mainCol + flashCol.rgb;
gl_FragColor = vec4(mainCol, 1.0);
}
绘制效果
着色器添加了旋转矩阵 rMat
,要在绘制循环更新该矩阵:
void _stdcall OnUpdateCallback() {
glClearColor(0.1f, 0.2f, 0.1f, 0.f); // 设置清理颜色缓存时,填充的颜色值
glClear(GL_COLOR_BUFFER_BIT); // 清理颜色缓存
const float PI = 3.1415926f;
mat4x4 rMat;
mat4x4_identity(rMat);
mat4x4_rotate_Y(rMat, rMat, (float)glfwGetTime() * PI * 0.25f);
mat4x4_rotate_X(rMat, rMat, (float)glfwGetTime() * PI * 0.25f);
shaderProgram->use(); // 使用此着色器程序
shaderProgram->setInt("main_tex", 0); // 主 纹理设置采样器采样 0 索引纹理单元
shaderProgram->setInt("mask_tex", 1); // 遮罩 纹理设置采样器采样 1 索引纹理单元
shaderProgram->setInt("flash_light_tex", 2); // 闪光/流光 纹理设置采样器采样 2 索引纹理单元
shaderProgram->setMatrix4x4("rMat", (const GLfloat*)rMat); // 测试用就直接用字符串了,方便一些
shaderProgram->setFloat("time", (float)glfwGetTime()); // 测试用就直接用字符串了,方便一些
glBindVertexArray(vertex_array_object); // 先绘制 VAO[0] 的 VBO,EBO,VAF,ENABLED
glDrawElements(GL_TRIANGLES, 36, GL_UNSIGNED_INT, (GLvoid*)0); // 绘制 36 个索引对应的顶点
}
你会发现绘制效果相当奇怪
深度测试
导致这个原因是因为深度的问题,深度是每个片元位置上对应里镜头的距离值,范围是:0~1,越小的值越靠近镜头,越大的则远离镜头。
所以越小的值的片元(靠近镜头)的就应该挡住离得更远的片元。
OpenGL 会有一个深度缓存专门用于存放每个片元对应的深度值,就叫:深度缓存。
开启深度测试
而测试比较深度是需要开启的,默认是关闭的,那么先要开启深度测试:
glEnable(GL_DEPTH_TEST); // 开启深度测试
设置深度比较枚举
OpenGL 默认是以 GL_LESS
,作为与当前的深度值做比较的。less 的意思,就是小于的意思。
就是或如果我们当前绘制的新的片元比深度缓存中的对应片元位置上的深度值小就可以通过。
这个枚举值有好几种:GL_NEVER
, GL_LESS
, GL_EQUAL
, GL_LEQUAL
, GL_GREATER
, GL_NOTEQUAL
, GL_GEQUAL
, and GL_ALWAYS
。默认是:GL_LESS
。
枚举的意思,具体你可以查看 glDepthFunc API,我懒得写了:
glDepthFunc(GL_LESS); // 默认值,深度测试的比较使用:小于等于的值将通过测试
清理缓存
每次绘制几何体之前,我们通常都清理深度缓存的值,并且可以在清理时使用指定的深度来填充缓存:glClearDepth
glClearDepthf(1.0f); // 设置清理深度缓存时,填充的深度值
然后在 glClear 函数指定上清理深度的位域值,我们之前有清理颜色缓存的,现在添加上清理深度缓存的:
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // 清理颜色缓存 与 深度缓存
再次绘制效果
可以看到绘制效果正常很多了:
面向剔除
虽然上面我们使用了深度测试来完成了遮挡、可见问题,但其实好可以优化:剔除背面 三角形。
这样就可以让部分看到的三角形不绘制,从而提升性能。
之前的文章有提到过,可以使用 glFrontFace 来设置三角形组合的顶点最终在 NDC 坐标系 下的顺序是怎么样的来作为 正面。
下面我们使用的是,在 NDC 坐标系 下逆时针的顶点组合作为正面:
glFrontFace(GL_CCW); // 默认就是GL_CCW逆时针
然后开启 glEnable 面向剔除 的开关:
glEnable(GL_CULL_FACE); // 启用面向剔除
最后是 glCullFace 剔除背面 的设置:
glCullFace(GL_BACK); // 剔除背面
就像下面这样:
glFrontFace(GL_CCW); // 默认就是GL_CCW逆时针
//glFrontFace(GL_CW); // GL_CW顺时针
glEnable(GL_CULL_FACE); // 启用面向剔除
glCullFace(GL_BACK); // 剔除背面
完整代码
// jave.lin
#include<glad/glad.h>
//#include"linmath.h"
// 把linmath.h 放在 iostream 之前include会有错误,所以放到iostream 后include就好了
// 而这个错误正式 xkeycheck.h 文件内 #error 提示的,所以可以使用 #define _XKEYCHECK_H 这个头文件的引用标记宏
// 就可以避免对 xkeycheck.h 头文件的 include 了。
#include<iostream>
#include<linmath.h>
#include<shader.h>
// 使用 stb_image.h 的加载库
// github 源码:https://github.com/nothings/stb/blob/master/stb_image.h
#define STB_IMAGE_IMPLEMENTATION
#include<stb_image.h>
// 将之前的打印版本信息代码包含一下
#include<my_init.h>
#include<my_gl_check_error.h>
#include<my_simple_load_tex.h>
using namespace my_util;
GLfloat vertices[] = {
// x, y, z
// 直接放 24 个顶点
// back
-0.5f, -0.5f, -0.5f, // 第0 个顶点
0.5f, -0.5f, -0.5f, // 第1 个顶点
0.5f, 0.5f, -0.5f, // 第2 个顶点
-0.5f, 0.5f, -0.5f, // 第3 个顶点
// front
0.5f, -0.5f, 0.5f, // 第4 个顶点
-0.5f, -0.5f, 0.5f, // 第5 个顶点
-0.5f, 0.5f, 0.5f, // 第6 个顶点
0.5f, 0.5f, 0.5f, // 第7 个顶点
// left
-0.5f, -0.5f, 0.5f, // 第8 个顶点
-0.5f, -0.5f, -0.5f, // 第9 个顶点
-0.5f, 0.5f, -0.5f, // 第10个顶点
-0.5f, 0.5f, 0.5f, // 第11个顶点
// right
0.5f, -0.5f, -0.5f, // 第12个顶点
0.5f, -0.5f, 0.5f, // 第13个顶点
0.5f, 0.5f, 0.5f, // 第14个顶点
0.5f, 0.5f, -0.5f, // 第15个顶点
// top
-0.5f, 0.5f, -0.5f, // 第16个顶点
0.5f, 0.5f, -0.5f, // 第17个顶点
0.5f, 0.5f, 0.5f, // 第18个顶点
-0.5f, 0.5f, 0.5f, // 第19个顶点
// bottom
-0.5f, -0.5f, 0.5f, // 第20个顶点
0.5f, -0.5f, 0.5f, // 第21个顶点
0.5f, -0.5f, -0.5f, // 第22个顶点
-0.5f, -0.5f, -0.5f, // 第23个顶点
};
GLfloat uvs[] = { // 顶点的 uv 坐标
// back
0.0f, 0.0f, // 左下角
1.0f, 0.0f, // 右下角
1.0f, 1.0f, // 右上角
0.0f, 1.0f, // 左上角
// front
0.0f, 0.0f, // 左下角
1.0f, 0.0f, // 右下角
1.0f, 1.0f, // 右上角
0.0f, 1.0f, // 左上角
// left
0.0f, 0.0f, // 左下角
1.0f, 0.0f, // 右下角
1.0f, 1.0f, // 右上角
0.0f, 1.0f, // 左上角
// right
0.0f, 0.0f, // 左下角
1.0f, 0.0f, // 右下角
1.0f, 1.0f, // 右上角
0.0f, 1.0f, // 左上角
// top
0.0f, 0.0f, // 左下角
1.0f, 0.0f, // 右下角
1.0f, 1.0f, // 右上角
0.0f, 1.0f, // 左上角
// bottom
0.0f, 0.0f, // 左下角
1.0f, 0.0f, // 右下角
1.0f, 1.0f, // 右上角
0.0f, 1.0f, // 左上角
};
GLuint indices[] = { // 注意索引从0开始!通过索引缓存来指定 图元 组成 用的 顶点有哪些
// back
0, 1, 2, // 第 0 个三角面
2, 3, 0, // 第 1 个三角面
// front
4, 5, 6, // 第 2 个三角面
6, 7, 4, // 第 3 个三角面
// left
8, 9, 10, // 第 4 个三角面
10, 11, 8, // 第 5 个三角面
// right
12, 13, 14, // 第 6 个三角面
14, 15, 12, // 第 7 个三角面
// top
16, 17, 18, // 第 8 个三角面
18, 19, 16, // 第 9 个三角面
// bottom
20, 21, 22, // 第 10个三角面
22, 23, 20, // 第 11个三角面
};
GLint vpos_location, vuv_location;
GLuint vertex_buffer[2], index_buffer;
GLuint vertex_array_object;
GLuint texture[3];
GLuint pixelBufObject;
GLint success, infoLogLen;
ShaderProgram* shaderProgram;
GLuint win_width, win_height;
void _stdcall OnBeforeInitWinCallback(InitInfo* info);
void _stdcall OnInitCallback();
void _stdcall OnBackBuffResizeCallback(const int width, const int height);
void _stdcall OnUpdateCallback();
void _stdcall OnBeforeExitCallback();
int main() {
g_BeforeInitWinCallback = OnBeforeInitWinCallback;
g_InitCallback = OnInitCallback;
g_BackBuffResizeCallback = OnBackBuffResizeCallback;
g_UpdateCallback = OnUpdateCallback;
g_BeforeExitCallback = OnBeforeExitCallback;
return run();
} // int main() {
void _stdcall OnBeforeInitWinCallback(InitInfo* info) {
info->width = win_width = 300;
info->height = win_height = 300;
info->print_version_info = true;
const char* title = "07_DrawCube";
strcpy_s(info->win_title, strlen(title) + 1, title);
}
void _stdcall OnInitCallback() {
// 打印支持最大的顶点支持的数量
int nrAttributes;
glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &nrAttributes);
std::cout << "Maximum number of vertex attributes supported : " << nrAttributes << std::endl;
// 打印着色器支持最大的纹理图像单元的数量
int maxTexUnit;
glGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, &maxTexUnit);
std::cout << "Maximun number of texture image units : " << maxTexUnit << std::endl;
// 打印着色器支持最大的所有组合的纹理图像单元的数量
int maxCombinedTexUnit;
glGetIntegerv(GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS, &maxCombinedTexUnit);
std::cout << "Maximun number of Combined texture image units : " << maxCombinedTexUnit << std::endl;
// 用 lambda 设置,获取 pic 目录的回调,后面在封装
g_GetPicturePathCallback = [](char* receiveBuff, const char* file)->char* {
char buf[MAX_PATH];
sprintf_s(buf, "..\\..\\Dependencies\\Pic\\%s", file);
strcpy_s(receiveBuff, MAX_PATH, buf);
return receiveBuff;
};
// 用 lambda 设置,获取 shader 目录的回调,后面在封装
g_GetShaderPathCallback = [](char* receiveBuff, const char* file)->char* {
char buf[MAX_PATH];
sprintf_s(buf, "..\\..\\Dependencies\\Shaders\\%s", file);
strcpy_s(receiveBuff, MAX_PATH, buf);
return receiveBuff;
};
shaderProgram = new ShaderProgram;
// shader program init 5 - 根据shader源码的相对路径(变量),加载deps下的shader
char vs_path[MAX_PATH], fs_path[MAX_PATH];
g_GetShaderPathCallback(vs_path, "DrawCube\\draw_cube.vert");
g_GetShaderPathCallback(fs_path, "DrawCube\\draw_cube.frag");
if (!shaderProgram->initByPath(vs_path, fs_path)) {
std::cout << "ShaderProgram init Error: " << shaderProgram->errorLog() << std::endl; // 输出shader program错误
exit(EXIT_FAILURE);
}
glFrontFace(GL_CCW); // 默认就是GL_CCW逆时针
//glFrontFace(GL_CW); // GL_CW顺时针
glEnable(GL_CULL_FACE); // 启用面向剔除
glCullFace(GL_BACK); // 剔除背面
glEnable(GL_DEPTH_TEST); // 开启深度测试
glDepthFunc(GL_LESS); // 默认值,深度测试的比较使用:小于等于的值将通过测试
checkGLError();
glCreateTextures(GL_TEXTURE_2D, 3, texture); // 创建 3 个纹理对象
glActiveTexture(GL_TEXTURE0); // 激活第 0 索引纹理单元
glBindTextureUnit(0, texture[0]); // 纹理单元 0 绑定:主纹理
glActiveTexture(GL_TEXTURE1); // 激活第 1 索引纹理单元
glBindTextureUnit(1, texture[1]); // 纹理单元 0 绑定:遮罩纹理
glActiveTexture(GL_TEXTURE2); // 激活第 2 索引纹理单元
glBindTextureUnit(2, texture[2]); // 纹理单元 0 绑定:闪光/流光纹理
checkGLError();
loadTexture(texture[0], "my_tex.png"); // 加载纹理对象0:主纹理
loadTexture(texture[1], "my_tex_flash_mask.jpg"); // 加载纹理对象1:遮罩纹理
loadTexture(texture[2], "flash.png"); // 加载纹理对象2:闪光/流光纹理
checkGLError();
vpos_location = shaderProgram->getAttributeLoc("vPos"); // 获取 顶点着色器中的顶点 attribute 属性的 location
vuv_location = shaderProgram->getAttributeLoc("vUV"); // 获取 顶点着色器中的顶点 attribute 属性的 location
glGenVertexArrays(1, &vertex_array_object); // 生成1个 VAO
glGenBuffers(2, vertex_buffer); // 创建2个 VBO,这里我们因为有一个一样的顶点坐标,一个一样的顶点UV
glGenBuffers(1, &index_buffer); // 创建1个 EBO,因为两个 Quad 的顶点索引顺序都是一样的
//
// === VAO[0] ===
//
glBindVertexArray(vertex_array_object); // 绑定 VAO[0],那么之后的 vbo, ebo,的绑定指针都是指向该 VAO 中的,还有顶点格式(规范)都会保存在该 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[1]
glBufferData(GL_ARRAY_BUFFER, sizeof(uvs), uvs, GL_STATIC_DRAW); // 设置 VBO uv数据
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, index_buffer); // 绑定 EBO
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW); // 设置 EBO 数据
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 位置的属性
glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer[1]); // 绑定 VBO[1],因为后面要设置该 VBO 的uv格式
glVertexAttribPointer(vuv_location, 2, GL_FLOAT, GL_FALSE, // 设置 顶点属性 vUV 格式
sizeof(GLfloat) * 2, (GLvoid*)0);
glEnableVertexAttribArray(vuv_location); // 启用 顶点缓存 location uv的属性
glViewport(0, 0, win_width, win_height); // 设置视口坐标
}
void _stdcall OnBackBuffResizeCallback(const int width, const int height) {
glViewport(0, 0, width, height);
win_width = width;
win_height = height;
}
void _stdcall OnUpdateCallback() {
glClearColor(0.1f, 0.2f, 0.1f, 0.f); // 设置清理颜色缓存时,填充的颜色值
glClearDepthf(1.0f); // 设置清理深度缓存时,填充的深度值
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // 清理颜色缓存 与 深度缓存
const float PI = 3.1415926f;
mat4x4 rMat;
mat4x4_identity(rMat);
mat4x4_rotate_Y(rMat, rMat, (float)glfwGetTime() * PI * 0.25f);
mat4x4_rotate_X(rMat, rMat, (float)glfwGetTime() * PI * 0.25f);
shaderProgram->use(); // 使用此着色器程序
shaderProgram->setInt("main_tex", 0); // 主 纹理设置采样器采样 0 索引纹理单元
shaderProgram->setInt("mask_tex", 1); // 遮罩 纹理设置采样器采样 1 索引纹理单元
shaderProgram->setInt("flash_light_tex", 2); // 闪光/流光 纹理设置采样器采样 2 索引纹理单元
shaderProgram->setMatrix4x4("rMat", (const GLfloat*)rMat); // 测试用就直接用字符串了,方便一些
shaderProgram->setFloat("time", (float)glfwGetTime()); // 测试用就直接用字符串了,方便一些
glBindVertexArray(vertex_array_object); // 先绘制 VAO[0] 的 VBO,EBO,VAF,ENABLED
glDrawElements(GL_TRIANGLES, 36, GL_UNSIGNED_INT, (GLvoid*)0); // 绘制 36 个索引对应的顶点
}
void _stdcall OnBeforeExitCallback() {
glDeleteBuffers(1, &pixelBufObject); // 测试删除 BO
glDeleteBuffers(2, vertex_buffer); // 测试删除 VBO
glDeleteBuffers(1, &index_buffer); // 测试删除 EBO
glDeleteBuffers(1, &vertex_array_object); // 测试删除 VAO
glDeleteTextures(3, texture); // 删除纹理对象 TO
delete shaderProgram; // 销毁 shader program
checkGLError(); // 最后再看看GL还有什么错误
}