1邑雅、初始化glfw
1.1 什么是glfw
glfw是一個(gè)輕量的可跨平臺(tái)的OpenGL庫函數(shù)嗦玖。主要是用來顯示窗口和捕捉窗口事件的一套API患雇。
1.2 創(chuàng)建一個(gè)窗口需要的原材料
- glad
- glfw
- glad.c文件
- 編譯器
- C/C++代碼
1.3 創(chuàng)建窗口的步驟
1.3.1 包含頭文件
// glad的頭文件需要在glfw的頭文件之上引入,引入后同時(shí)將glad.c文件復(fù)制到工程文件之下宇挫。
#include <glad/glad.h>
#include <GLFW/glfw3.h>
// 說明:如果出現(xiàn)glad頭文件無法提示苛吱,或者引入失敗,對(duì)于linux系統(tǒng)器瘪,
// 可以在/usr/local/include查看是否有g(shù)lad文件夾翠储,將glad文件夾的頭問價(jià)復(fù)制到這里即可
// 同時(shí)glad的頭文件還有一個(gè)KHR文件,也一并復(fù)制
1.3.2 初始化glfw
關(guān)于為什么要初始化glfw橡疼,我的理解是glfw是要作畫的工具援所,我們要做畫之前需要準(zhǔn)備一套完整的畫具,初始化glfw的過程就是準(zhǔn)備畫畫工具的一步欣除。
glfwInit();
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR,3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR,3);
glfwWindowHint(GLFW_OPENGL_PROFILE,GLFW_OPENGL_CORE_PROFILE);
1.3.3 創(chuàng)建一個(gè)窗口對(duì)象并加入上下文
GLFWwindow * window ;
window = glfwCreateWindow(800,600,"hello world",NULL,NULL);
// 對(duì)使用glfw創(chuàng)建出來的window對(duì)象進(jìn)行非空校驗(yàn)
if (window == NULL)
{
std::cout<<"Faile create window."<< std::endl;
glfwTerminate();
return -1;
}
// 將創(chuàng)建的window對(duì)象加入到上下文中住拭,此時(shí)的window對(duì)象非空
glfwMakeContextCurrent(window);
加入上下文簡單來說就是加入到當(dāng)前運(yùn)行的進(jìn)程中,讓窗口對(duì)象為當(dāng)前顯示的對(duì)象历帚。
為什么會(huì)有上下文
這個(gè)東西滔岳,可能會(huì)存在疑問,因?yàn)槲覀儾]有看到什么上下文啊挽牢。
解釋:
OpenGL本身可以將它看做一個(gè)狀態(tài)機(jī)谱煤,引擎的不斷循環(huán),就是為了讓這個(gè)狀態(tài)機(jī)持續(xù)運(yùn)行下去禽拔,顯而易見的刘离,只有狀態(tài)機(jī)工作了才會(huì)顯示你想要的東西。
進(jìn)行過JAVA開發(fā)的或者了解開發(fā)的朋友應(yīng)該知道奏赘,JVM虛擬機(jī)寥闪,我們的JAVA代碼試運(yùn)行在JVM之上的太惠,那么類似的磨淌,OpenGL代碼是運(yùn)行在狀態(tài)機(jī)上的,至少我們可以抽象的理解為這樣凿渊。狀態(tài)機(jī)通過函數(shù)去操作GPU從而實(shí)現(xiàn)圖形的渲染梁只。
在java中我們是通過運(yùn)行main函數(shù),從而啟動(dòng)JVM運(yùn)行了JAVA代碼埃脏。那么類似的搪锣,再加入上下文之前,OpenGL創(chuàng)建的窗口彩掐,需要使用
glfwMakeContextCurrent(window);
這個(gè)函數(shù)啟動(dòng)狀態(tài)機(jī)构舟,或者可以理解為將我創(chuàng)建好的窗口顯示在狀態(tài)機(jī)上。我們可以進(jìn)行一個(gè)小實(shí)驗(yàn)(實(shí)驗(yàn)結(jié)果就是我說的堵幽,不想觀看的可以跳過)
#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <iostream>
void pressToLine(GLFWwindow *window) {
if(glfwGetKey(window,GLFW_KEY_P) == GLFW_PRESS)
{
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
}else if(glfwGetKey(window,GLFW_KEY_F)==GLFW_PRESS)
{
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
}else if(glfwGetKey(window,GLFW_KEY_ESCAPE)==GLFW_PRESS) {
glfwSetWindowShouldClose(window,true);
}
}
int main()
{
// 初始化glfw
glfwInit();
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR,4);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR,6);
glfwWindowHint(GLFW_OPENGL_PROFILE,GLFW_OPENGL_CORE_PROFILE);
// 創(chuàng)建窗口對(duì)象
GLFWwindow *window;
window = glfwCreateWindow(800,600,"triangle",NULL,NULL);
// 判斷窗口對(duì)象創(chuàng)建是否成功
if(window == NULL)
{
std::cout << "創(chuàng)建窗口失敼烦5臁!努咐!" << std::endl;
glfwTerminate();
return -1;
}
//將創(chuàng)建的窗口對(duì)象加載到上下文
/**
* @brief 這一步總是容易忘記
*/
glfwMakeContextCurrent(window);
// 使用glad加載glfw函數(shù)指針
if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
{
std::cout << "Failed to initialize GLAD" << std::endl;
return -1;
}
// 頂點(diǎn)數(shù)據(jù)
float vertices[] = {
-0.5f, -0.5f, 0.0f,
0.5f, -0.5f, 0.0f,
0.0f, 0.5f, 0.0f
};
// 聲明頂點(diǎn)著色器和片段著色器
unsigned int vertexShader,fragmentShader;
// 創(chuàng)建頂點(diǎn)著色器
vertexShader = glCreateShader(GL_VERTEX_SHADER);
fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
// 編譯著色器源碼
const char* vertexShaderSource = "#version 330 core\n"
"layout (location = 0) in vec3 aPos;\n"
"void main()\n"
"{\n"
" gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);\n"
"}\0";
const char *fragmentShaderSource = "#version 460 core\n"
"out vec4 FragColor;\n"
"void main()\n"
"{\n"
" FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);\n"
"}\n\0";
// 將glsl源碼編譯
glShaderSource(vertexShader,1,&vertexShaderSource,NULL);
glShaderSource(fragmentShader,1,&fragmentShaderSource,NULL);
glCompileShader(vertexShader);
glCompileShader(fragmentShader);
// 校驗(yàn)編譯結(jié)果
int success;
// 定義一個(gè)錯(cuò)誤日志接收的變量
char infoLog[512];
glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success);
// 對(duì)結(jié)果進(jìn)行校驗(yàn)
if(!success) {
// 校驗(yàn)失敗苦蒿,打印錯(cuò)誤信息
glGetShaderInfoLog(vertexShader, 512, NULL, infoLog);
std::cout << "ERROR::SHADER::VERTEX::COMPILATION_FAILED\n" << infoLog << std::endl;
}
glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &success);
// 對(duì)結(jié)果進(jìn)行校驗(yàn)
if(!success) {
// 校驗(yàn)失敗,打印錯(cuò)誤信息
glGetShaderInfoLog(fragmentShader, 512, NULL, infoLog);
std::cout << "ERROR::SHADER::FRAGMENT::COMPILATION_FAILED\n" << infoLog << std::endl;
}
// 創(chuàng)建著色器程序?qū)ο? unsigned int shaderProgram;
shaderProgram = glCreateProgram();
// 將著色器添加進(jìn)去
glAttachShader(shaderProgram,vertexShader);
glAttachShader(shaderProgram,fragmentShader);
// 鏈接OpenGL
glLinkProgram(shaderProgram);
// 鏈接結(jié)果校驗(yàn)
glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success);
if(!success) {
glGetProgramInfoLog(shaderProgram, 512, NULL, infoLog);
std::cout << "ERROR::SHADER::PROGRAM::LINKING_FAILED\n" << infoLog << std::endl;
}
// 創(chuàng)建VBO和VAO
unsigned int VBO,VAO;
// 生成VBO和VAO對(duì)象
glGenBuffers(1,&VBO);
glGenVertexArrays(1,&VAO);
// 設(shè)置VAO和VBO
glBindVertexArray(VAO);
glBindBuffer(GL_ARRAY_BUFFER,VBO);
glBufferData(GL_ARRAY_BUFFER,sizeof(vertices),vertices,GL_STATIC_DRAW);
// 設(shè)置頂點(diǎn)屬性
glVertexAttribPointer(0,3,GL_FLOAT,GL_FALSE,3*sizeof(float),(void*)0);
glEnableVertexAttribArray(0);
// 編寫引擎
while(!glfwWindowShouldClose(window))
{
// 設(shè)置清楚層顏色
glClearColor(0.1f,0.2f,0.2f,1.0f);
glClear(GL_COLOR_BUFFER_BIT);
// 使用創(chuàng)建的著色器程序
glUseProgram(shaderProgram);
glBindVertexArray(VAO);
glDrawArrays(GL_TRIANGLES,0,3);
// 雙緩沖交換
glfwSwapBuffers(window);
// 檢測(cè)窗口事件
glfwPollEvents();
pressToLine(window);
}
glfwTerminate();
return 0;
}
運(yùn)行上面的文件渗稍,不出錯(cuò)的情況下佩迟,你會(huì)得到一個(gè)三角形,如下圖所示
當(dāng)前使用的窗口對(duì)象為:
// 創(chuàng)建窗口對(duì)象
GLFWwindow *window;
window = glfwCreateWindow(800,600,"triangle",NULL,NULL);
// 加入上下文的窗口對(duì)象也為該對(duì)象
glfwMakeContextCurrent(window);
現(xiàn)在我們創(chuàng)新創(chuàng)建一個(gè)window對(duì)象
// 創(chuàng)建測(cè)試窗口對(duì)象
GLFWwindow *TestWindow;
TestWindow = glfwCreateWindow(800,600,"triangle",NULL,NULL);
// 將顯示三角形的窗口先加入上下文
glfwMakeContextCurrent(window);
glfwMakeContextCurrent(TestWindow);
我們得到結(jié)果:
得到了一個(gè)黑色的窗口竿屹,這個(gè)是我們的TestWindow报强,按照正常邏輯,此時(shí)其實(shí)不應(yīng)該一直顯示羔沙,應(yīng)該一閃而過躺涝。
這個(gè)原因是因?yàn)椋覀兝L制三角形的引擎一直在工作扼雏,所以會(huì)導(dǎo)致狀態(tài)機(jī)一直在運(yùn)行坚嗜,CPU沒有結(jié)束進(jìn)程。
這也說明了诗充,我們只是結(jié)束了狀態(tài)機(jī)對(duì)于三角形頁面的生命周期苍蔬,但是CPU沒有結(jié)束我們程序進(jìn)程的生命周期。
我們可以再將TestWidow先加入上下文蝴蜓,window后加入上下文碟绑,我們就會(huì)得到三角形窗口。
結(jié)論:加入上下文函數(shù)茎匠,是運(yùn)行OpenGL代碼的前提格仲,他會(huì)將描述好的OpenGL代碼加載入狀態(tài)機(jī)進(jìn)行顯示。
1.3.4 初始化glad/加載glfw的函數(shù)指針
glad的主要作用就是加載glfw的函數(shù)指針诵冒,glad和glew是具有相似功能的凯肋,都是進(jìn)行函數(shù)指針的加載,我們?nèi)绻皇褂胓lad汽馋,那么代碼將變得臃腫但又必要侮东,其中下面這為作者就將不使用glad函數(shù)庫的代碼展示了,可以看到代碼量是真的大1尽G难拧!
https://www.zhihu.com/question/344133077
有興趣可以看一下铁蹈。
言歸正傳宽闲,我們繼續(xù)創(chuàng)建窗口的操作,進(jìn)行初始化glad
if(!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) {
std::cout << "glad init failed."<<std::endl;
glfwTerminate();
return -1;
}
這段代碼的核心含義就是當(dāng)加載glfw的函數(shù)指針失敗時(shí),將終止程序的運(yùn)行容诬,簡單來說就是加載glfw的函數(shù)指針來供程序的繼續(xù)運(yùn)行围辙。
1.3.5 創(chuàng)建窗口引擎
通過前面的步驟,我們可以實(shí)現(xiàn)窗口對(duì)象的創(chuàng)建放案,并且完成了將創(chuàng)建的窗口對(duì)象加載到了上下文中姚建,但是此時(shí)并沒有萬事大吉,創(chuàng)建的窗口對(duì)象如何一直顯示在屏幕上吱殉?這就用到了我們接下來要說的——引擎掸冤。
汽車發(fā)動(dòng)需要引擎,這世界上所有需要運(yùn)行起來的東西都需要一個(gè)引擎友雳。
創(chuàng)建窗口最簡單的引擎如下代碼所示:
while(!glfwWindowShouldClose(window))
{
glfwSwapBuffers(window);
glfwPollEvents();
}
當(dāng)我們一直不進(jìn)行關(guān)閉的需求時(shí)稿湿,那么判斷結(jié)果一直為真,將持續(xù)進(jìn)行循環(huán)內(nèi)操作押赊,從而實(shí)現(xiàn)在屏幕中表現(xiàn)為持續(xù)進(jìn)行顯示饺藤。
glfwPollEvents(); 這個(gè)函數(shù)是檢測(cè)窗口發(fā)生了哪些事件的函數(shù),如果我們不使用這個(gè)函數(shù)流礁,窗口依然可以創(chuàng)建涕俗,但是你的窗口是無法正常關(guān)閉,處于不可控狀態(tài)神帅,所以這個(gè)函數(shù)是必要的再姑,glfwSwapBuffers(window);
這個(gè)函數(shù)是雙緩沖的應(yīng)用。
雙緩沖
什么是雙緩沖找御,簡而言之就是兩個(gè)緩沖區(qū)元镀,一個(gè)緩沖區(qū)負(fù)責(zé)屏幕上顯示,另一個(gè)緩沖區(qū)負(fù)責(zé)下一幀屏幕顯示霎桅,從而達(dá)到實(shí)時(shí)進(jìn)行渲染的目的栖疑。在雙緩沖之前,使用的是單緩沖技術(shù)滔驶,那么單緩沖帶來的副作用就是讓屏幕在持續(xù)渲染時(shí)總是會(huì)閃爍一下遇革,這是由于第二幀畫面無法在對(duì)第一幀畫面結(jié)束后,立即完成渲染瓜浸。
雙緩沖的實(shí)現(xiàn)方式就是將澳淑,下一幀要渲染的窗口畫面存放在一個(gè)緩沖區(qū)中比原,另一個(gè)緩沖區(qū)存放當(dāng)前正在顯示的窗口畫面插佛。
1.3.6 程序的終止
其實(shí)完成上面的操作,便可以實(shí)現(xiàn)窗口的創(chuàng)建和關(guān)閉量窘,但是我們總是要養(yǎng)成一個(gè)好的習(xí)慣雇寇,在完成使用后,進(jìn)行手動(dòng)的關(guān)閉。
glfwTerminate();
return 0;
1.3.7 創(chuàng)建窗口的完整代碼如下:
#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <iostream>
int main() {
glfwInit();
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR,3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR,3);
glfwWindowHint(GLFW_OPENGL_PROFILE,GLFW_OPENGL_CORE_PROFILE);
GLFWwindow * window ;
window = glfwCreateWindow(800,600,"hello world",NULL,NULL);
if (window == NULL)
{
std::cout<<"Faile create window."<< std::endl;
glfwTerminate();
return -1;
}
glfwMakeContextCurrent(window);
// glad 加載glfw函數(shù)指針
if(!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) {
std::cout << "glad init failed."<<std::endl;
glfwTerminate();
return -1;
}
while(!glfwWindowShouldClose(window))
{
// glClearColor(0.1f,0.2f,0.2f,1.0f);
// glClear(GL_COLOR_BUFFER_BIT);
glfwSwapBuffers(window);
glfwPollEvents();
}
glfwTerminate();
return 0;
}
2锨侯、窗口渲染
2.1 對(duì)創(chuàng)建的窗口進(jìn)行顏色的渲染
glClearColor(0.1f,0.2f,0.2f,1.0f);
glClear(GL_COLOR_BUFFER_BIT);
只需要將上述兩句代碼加載雙緩沖運(yùn)行之前進(jìn)行即可嫩海,在執(zhí)行g(shù)lClear函數(shù)時(shí)會(huì)將在glClearColor中設(shè)置的顏色渲染到窗口上。
其中GL_COLOR_BUFFER_BIT這個(gè)為要進(jìn)行渲染的選項(xiàng)囚痴,目前只是想將窗口進(jìn)行渲染叁怪,因此我們只需要使用GL_COLOR_BUFFER_BIT這個(gè)即可,還有一個(gè)深度的選項(xiàng)和一個(gè)模型的選項(xiàng)深滚,分別為GL_DEPTH_BUFFER_BIT奕谭、GL_STENCIL_BUFFER_BIT。
2.2 通過鍵盤輸入完成關(guān)閉窗口
void overWindow(GLFWwindow *window){
bool res = glfwGetKey(window,GLFW_KEY_ESCAPE) == GLFW_PRESS;
if (res)
{
glfwSetWindowShouldClose(window,true);
}
}
對(duì)于鍵盤輸入事件兩件事情是最重要的痴荐,
- 第一個(gè)就是glfwGetKey()函數(shù)血柳,他是獲取按鍵輸入的函數(shù),通過glfw按壓宏定義GLFW_PRESS來進(jìn)行判斷返回值與宏是否相等生兆,
- 從而完成對(duì)鍵盤是否按壓進(jìn)行判斷难捌,將判斷結(jié)果的bool值放在分支判斷中,
- 如果按壓之后鸦难,將glfwWindowShouldClose函數(shù)的返回值設(shè)置為true根吁,此時(shí)引擎的判斷就會(huì)為false,引擎停止工作合蔽,glfw執(zhí)行關(guān)閉窗口操作婴栽,程序正常退出。
- 這也是1.3.7中為什么需要在最后添加一個(gè)glfw的關(guān)閉函數(shù)的調(diào)用的原因辈末。
3愚争、視口
通常我們會(huì)將視口與窗口進(jìn)行混淆,窗口就是一個(gè)創(chuàng)建的窗口挤聘,視口是在窗口之上的轰枝,我們所有的繪制的圖形是在一個(gè)視口中進(jìn)行的,當(dāng)超過這個(gè)視口的大小之后组去,圖形會(huì)被隱藏鞍陨,不會(huì)繪制出來。
大多數(shù)我們的一個(gè)窗口只有一個(gè)視口从隆。
如果我們進(jìn)行縮放窗口時(shí)希望同時(shí)將視口進(jìn)行縮放诚撵,那么就設(shè)置一個(gè)回調(diào)函數(shù)即可。
void framebuffer_size_callback(GLFWwindow* window, int width, int height)
{
glViewport(0, 0, width, height);
}
// 注冊(cè)回調(diào)函數(shù)到glfw
glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);
至于注冊(cè)位置我們應(yīng)該放在glfw初始化完成之后键闺,也就是將glfw加入到上下文之后寿烟,在進(jìn)行回調(diào)函數(shù)的注冊(cè)。