- 构造顶点、索引、uv数据
- 绘制 36 个索引对应的顶点
- 让 Cube 旋转起来
- 绘制效果
- 深度测试
- 开启深度测试
- 设置深度比较枚举
- 清理缓存
- 再次绘制效果
- 面向剔除
- 完整代码
- References
本人才疏学浅,如有什么错误,望不吝指出。
上些篇:
- 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
//#include"linmath.h"
// 把linmath.h 放在 iostream 之前include会有错误,所以放到iostream 后include就好了
// 而这个错误正式 xkeycheck.h 文件内 #error 提示的,所以可以使用 #define _XKEYCHECK_H 这个头文件的引用标记宏
// 就可以避免对 xkeycheck.h 头文件的 include 了。
#include
#include
#include
// 使用 stb_image.h 的加载库
// github 源码:https://github.com/nothings/stb/blob/master/stb_image.h
#define STB_IMAGE_IMPLEMENTATION
#include
// 将之前的打印版本信息代码包含一下
#include
#include
#include
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
关注
打赏
最近更新
- 深拷贝和浅拷贝的区别(重点)
- 【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脚手架写一个简单的页面?