OpenGL學習日記1_創建窗口

讓我們試試能不能讓GLFW正常工作。首先,新建一個.cpp文件,然後把下面的代碼粘貼到該文件的最前面。注意,之所以定義GLEW_STATIC宏,是因爲我們使用的是GLEW靜態的鏈接庫。

// GLEW
#define GLEW_STATIC
#include <GL/glew.h>
// GLFW
#include <GLFW/glfw3.h>

Attention

請確認在包含GLFW的頭文件之前包含了GLEW的頭文件。在包含glew.h頭文件時會引入許多OpenGL必要的頭文件(例如GL/gl.h),所以你需要在包含其它依賴於OpenGL的頭文件之前先包含GLEW。

接下來我們創建main函數,在這個函數中我們將會實例化GLFW窗口:

int main()
{
    glfwInit();
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
    glfwWindowHint(GLFW_RESIZABLE, GL_FALSE);

    return 0;
}

首先,我們在main函數中調用glfwInit函數來初始化GLFW,然後我們可以使用glfwWindowHint函數來配置GLFW。glfwWindowHint函數的第一個參數代表選項的名稱,我們可以從很多以GLFW_開頭的枚舉值中選擇;第二個參數接受一個整形,用來設置這個選項的值。該函數的所有的選項以及對應的值都可以在 GLFW’s window handling 這篇文檔中找到。如果你現在編譯你的cpp文件會得到大量的 undefined reference (未定義的引用)錯誤,也就是說你並未順利地鏈接GLFW庫。

由於本站的教程都是基於OpenGL 3.3版本展開討論的,所以我們需要告訴GLFW我們要使用的OpenGL版本是3.3,這樣GLFW會在創建OpenGL上下文時做出適當的調整。這也可以確保用戶在沒有適當的OpenGL版本支持的情況下無法運行。我們將主版本號(Major)和次版本號(Minor)都設爲3。我們同樣明確告訴GLFW我們使用的是核心模式(Core-profile),並且不允許用戶調整窗口的大小。在明確告訴GLFW使用核心模式的情況下,使用舊版函數將會導致invalid operation(無效操作)的錯誤,而這不正是一個很好的提醒嗎?在我們不小心用了舊函數時報錯,就能避免使用一些被廢棄的用法了。如果使用的是Mac OS X系統,你還需要加下面這行代碼到你的初始化代碼中這些配置才能起作用:

glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);

Important

請確認您的系統支持OpenGL3.3或更高版本,否則此應用有可能會崩潰或者出現不可預知的錯誤。如果想要查看OpenGL版本的話,在Linux上運行glxinfo,或者在Windows上使用其它的工具(例如OpenGL Extension Viewer)。如果你的OpenGL版本低於3.3,檢查一下顯卡是否支持OpenGL 3.3+(不支持的話你的顯卡真的太老了),並更新你的驅動程序,有必要的話請更新顯卡。

接下來我們創建一個窗口對象,這個窗口對象存放了所有和窗口相關的數據,而且會被GLFW的其他函數頻繁地用到。

GLFWwindow* window = glfwCreateWindow(800, 600, "LearnOpenGL", nullptr, nullptr);
if (window == nullptr)
{
    std::cout << "Failed to create GLFW window" << std::endl;
    glfwTerminate();
    return -1;
}
glfwMakeContextCurrent(window);

glfwCreateWindow函數需要窗口的寬和高作爲它的前兩個參數;第三個參數表示這個窗口的名稱(標題),這裏我們使用"LearnOpenGL",當然你也可以使用你喜歡的名稱;最後兩個參數我們暫時忽略,先設置爲空指針就行。它的返回值GLFWwindow對象的指針會在其他的GLFW操作中使用到。創建完窗口我們就可以通知GLFW將我們窗口的上下文設置爲當前線程的主上下文了。

GLEW

在之前的教程中已經提到過,GLEW是用來管理OpenGL的函數指針的,所以在調用任何OpenGL的函數之前我們需要初始化GLEW。

glewExperimental = GL_TRUE;
if (glewInit() != GLEW_OK)
{
    std::cout << "Failed to initialize GLEW" << std::endl;
    return -1;
}

請注意,我們在初始化GLEW之前設置glewExperimental變量的值爲GL_TRUE,這樣做能讓GLEW在管理OpenGL的函數指針時更多地使用現代化的技術,如果把它設置爲GL_FALSE的話可能會在使用OpenGL的核心模式時出現一些問題。

視口(Viewport)

在我們開始渲染之前還有一件重要的事情要做,我們必須告訴OpenGL渲染窗口的尺寸大小,這樣OpenGL才只能知道怎樣相對於窗口大小顯示數據和座標。我們可以通過調用glViewport函數來設置窗口的維度(Dimension):

int width, height;
glfwGetFramebufferSize(window, &width, &height);

glViewport(0, 0, width, height);

glViewport函數前兩個參數控制窗口左下角的位置。第三個和第四個參數控制渲染窗口的寬度和高度(像素),這裏我們是直接從GLFW中獲取的。我們從GLFW中獲取視口的維度而不設置爲800*600是爲了讓它在高DPI的屏幕上(比如說Apple的視網膜顯示屏)也能正常工作

我們實際上也可以將視口的維度設置爲比GLFW的維度小,這樣子之後所有的OpenGL渲染將會在一個更小的窗口中顯示,這樣子的話我們也可以將一些其它元素顯示在OpenGL視口之外。

Important

OpenGL幕後使用glViewport中定義的位置和寬高進行2D座標的轉換,將OpenGL中的位置座標轉換爲你的屏幕座標。例如,OpenGL中的座標(-0.5, 0.5)有可能(最終)被映射爲屏幕中的座標(200,450)。注意,處理過的OpenGL座標範圍只爲-1到1,因此我們事實上將(-1到1)範圍內的座標映射到(0, 800)和(0, 600)。

準備好你的引擎

我們可不希望只繪製一個圖像之後我們的應用程序就立即退出並關閉窗口。我們希望程序在我們明確地關閉它之前不斷繪製圖像並能夠接受用戶輸入。因此,我們需要在程序中添加一個while循環,我們可以把它稱之爲遊戲循環(Game Loop),它能在我們讓GLFW退出前一直保持運行。下面幾行的代碼就實現了一個簡單的遊戲循環:

while(!glfwWindowShouldClose(window))
{
    glfwPollEvents();
    glfwSwapBuffers(window);
}
  • glfwWindowShouldClose函數在我們每次循環的開始前檢查一次GLFW是否被要求退出,如果是的話該函數返回true然後遊戲循環便結束了,之後爲我們就可以關閉應用程序了。
  • glfwPollEvents函數檢查有沒有觸發什麼事件(比如鍵盤輸入、鼠標移動等),然後調用對應的回調函數(可以通過回調方法手動設置)。我們一般在遊戲循環的開始調用事件處理函數。
  • glfwSwapBuffers函數會交換顏色緩衝(它是一個儲存着GLFW窗口每一個像素顏色的大緩衝),它在這一迭代中被用來繪製,並且將會作爲輸出顯示在屏幕上。

Important

雙緩衝(Double Buffer)

應用程序使用單緩衝繪圖時可能會存在圖像閃爍的問題。 這是因爲生成的圖像不是一下子被繪製出來的,而是按照從左到右,由上而下逐像素地繪製而成的。最終圖像不是在瞬間顯示給用戶,而是通過一步一步生成的,這會導致渲染的結果很不真實。爲了規避這些問題,我們應用雙緩衝渲染窗口應用程序。緩衝保存着最終輸出的圖像,它會在屏幕上顯示;而所有的的渲染指令都會在緩衝上繪製。當所有的渲染指令執行完畢後,我們交換(Swap)前緩衝和後緩衝,這樣圖像就立即呈顯出來,之前提到的不真實感就消除了。

最後一件事

當遊戲循環結束後我們需要正確釋放/刪除之前的分配的所有資源。我們可以在main函數的最後調用glfwTerminate函數來釋放GLFW分配的內存。

glfwTerminate();
return 0;

這樣便能清理所有的資源並正確地退出應用程序。現在你可以嘗試編譯並運行你的應用程序了,如果沒做錯的話,你將會看到如下的輸出:

如果你看見了一個非常無聊的黑色窗口,那麼就對了!如果你沒得到正確的結果,或者你不知道怎麼把所有東西放到一起,請到這裏參考源代碼。

如果程序編譯有問題,請先檢查連接器選項是否正確,IDE中是否導入了正確的目錄(前面教程解釋過)。並且請確認你的代碼是否正確,直接對照上面提供的源代碼就行了。如果還是有問題,歡迎評論,我或者其他人可能會幫助你的。

輸入

我們同樣也希望能夠在GLFW中實現一些鍵盤控制,這可以通過使用GLFW的回調函數(Callback Function)來完成。回調函數事實上是一個函數指針,當我們設置好後,GLWF會在合適的時候調用它。按鍵回調(KeyCallback)是衆多回調函數中的一種。當我們設置了按鍵回調之後,GLFW會在用戶有鍵盤交互時調用它。該回調函數的原型如下所示:

void key_callback(GLFWwindow* window, int key, int scancode, int action, int mode);

按鍵回調函數接受一個GLFWwindow指針作爲它的第一個參數;第二個整形參數用來表示按下的按鍵;action參數表示這個按鍵是被按下還是釋放;最後一個整形參數表示是否有Ctrl、Shift、Alt、Super等按鈕的操作。GLFW會在合適的時候調用它,併爲各個參數傳入適當的值。

void key_callback(GLFWwindow* window, int key, int scancode, int action, int mode)
{
    // 當用戶按下ESC鍵,我們設置window窗口的WindowShouldClose屬性爲true
    // 關閉應用程序
    if(key == GLFW_KEY_ESCAPE && action == GLFW_PRESS)
        glfwSetWindowShouldClose(window, GL_TRUE);
}    

在我們(新創建的)key_callback函數中,我們檢測了鍵盤是否按下了Escape鍵。如果鍵的確按下了(不釋放),我們使用glfwSetwindowShouldClose函數設定WindowShouldClose屬性爲true從而關閉GLFW。main函數的while循環下一次的檢測將爲失敗,程序就關閉了。

最後一件事就是通過GLFW註冊我們的函數至合適的回調,代碼是這樣的:

glfwSetKeyCallback(window, key_callback);  

除了按鍵回調函數之外,我們還能我們自己的函數註冊其它的回調。例如,我們可以註冊一個回調函數來處理窗口尺寸變化、處理一些錯誤信息等。我們可以在創建窗口之後,開始遊戲循環之前註冊各種回調函數。

渲染

我們要把所有的渲染(Rendering)操作放到遊戲循環中,因爲我們想讓這些渲染指令在每次遊戲循環迭代的時候都能被執行。代碼將會是這樣的:

// 程序循環
while(!glfwWindowShouldClose(window))
{
    // 檢查事件
    glfwPollEvents();

    // 渲染指令
    ...

    // 交換緩衝
    glfwSwapBuffers(window);
}

爲了測試一切都正常工作,我們使用一個自定義的顏色清空屏幕。在每個新的渲染迭代開始的時候我們總是希望清屏,否則我們仍能看見上一次迭代的渲染結果(這可能是你想要的效果,但通常這不是)。我們可以通過調用glClear函數來清空屏幕的顏色緩衝,它接受一個緩衝位(Buffer Bit)來指定要清空的緩衝,可能的緩衝位有GL_COLOR_BUFFER_BITGL_DEPTH_BUFFER_BITGL_STENCIL_BUFFER_BIT。由於現在我們只關心顏色值,所以我們只清空顏色緩衝。

glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);

注意,除了glClear之外,我們還調用了glClearColor來設置清空屏幕所用的顏色。當調用glClear函數,清除顏色緩衝之後,整個顏色緩衝都會被填充爲glClearColor裏所設置的顏色。在這裏,我們將屏幕設置爲了類似黑板的深藍綠色。

Important

你應該能夠回憶起來我們在 OpenGL 這節教程的內容,glClearColor函數是一個狀態設置函數,而glClear函數則是一個狀態應用的函數。


最終代碼如下:

// GLEW
#define GLEW_STATIC
#include <glew.h>
// GLFW
#include <glfw3.h>

void key_callback(GLFWwindow* window, int key, int scancode, int action, int mode)
{
	// 當用戶按下ESC鍵,我們設置window窗口的WindowShouldClose屬性爲true
	// 關閉應用程序
	if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS)
		glfwSetWindowShouldClose(window, GL_TRUE);

	// 程序循環
	while (!glfwWindowShouldClose(window))
	{
		// 檢查事件
		glfwPollEvents();

		// 渲染指令
		glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
		glClear(GL_COLOR_BUFFER_BIT);
		
		// 交換緩衝
		glfwSwapBuffers(window);
	}	
}

int main()
{
	glfwInit();
	glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
	glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
	glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
	glfwWindowHint(GLFW_RESIZABLE, GL_FALSE);

	GLFWwindow*window = glfwCreateWindow(800, 600, "張旭", nullptr, nullptr);
	if (window == nullptr)
	{
		//std::cout << "Failed to create GLFW window" << std::endl;
		glfwTerminate();
		return -1;
	}
	glfwMakeContextCurrent(window);

	glewExperimental = GL_TRUE;
	if (glewInit() != GLEW_OK)
	{
		//std::cout << "Failed to initialize GLEW" << std::endl;
		return -1;
	}

	int width, height;
	glfwGetFramebufferSize(window, &width, &height);
	glViewport(0, 0, width, height);//第三個和第四個參數控制渲染窗口的寬度和高度

	glfwSetKeyCallback(window, key_callback);//聲明key_callback

	while (!glfwWindowShouldClose(window))
	{
		glfwPollEvents();
		glfwSwapBuffers(window);
		
	}

	glfwTerminate();
	return 0;
}


發佈了25 篇原創文章 · 獲贊 27 · 訪問量 9萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章