LearnGL - 學習筆記目錄
本人才疏學淺,如有什麼錯誤,望不吝指出。
上些篇:
- LearnGL - 06 - Matrix - 矩陣01,瞭解了矩陣就是定義一個座標空間的軸向
- LearnGL - 06.1 - Matrix - 矩陣02,瞭解了一些矩陣的乘法的維度限制
- 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還有什麼錯誤
}