-------------------------------------------------------------------------------------------------------------------------------
就像學習其他編程語言一樣,為了順利寫下第一個OpenGL程序
我們必須不辭辛苦的先鋪好磚塊,搭建好環境……
所以接下來讓我先把所需要的庫的環境安置好,再開始coding。
-------------------------------------------------------------------------------------------------------------------------------
【安裝環境】
本來想簡單點直接用glut和glew的庫包含就好,但是為了配合該書的例子還是決定用OpenGL編程指南第八版的環境來配置。
關於該配置,我發現了一個前輩已經把詳細過程寫在他的csdn博客上,大家可以去看看,非常簡單,里面有源碼的下載地址。
http://blog.csdn.net/qq821869798/article/details/45247241
另外為了不用每次新建工程都要配置一次,我直接把include和lib目錄下的文件拷貝到vs2012的安裝目錄
例如我的目錄是C:\Program Files (x86)\Microsoft Visual Studio 11.0\VC
拷貝到該目錄下對應的include以及lib下,就可以了。以后每次新建工程就不必都重復那些步驟了。
接下來,就寫下OpenGL程序驗證下。關於代碼我會盡量注釋,希望大家能看得清楚。
------------------------------------------------------------------------------------------------------------------------------
其實我一直覺得的把代碼先跑通然后再仔細研究代碼會更加清楚,所以我先呈上該書第一個例子的運行結果。

--------------------------------------------------------------------------------------------------------------------------------
如果你的環境配置正確,代碼也按照書上敲打的話,運行后可能會出現停止運行的情況
glutInitContextVersion(4, 3);將這一行代碼注釋 即可解決,暫時不知道為什么,看看后面能不能找到解釋。
--------------------------------------------------------------------------------------------------------------------------------
正如書中所說的,創建窗口、接收鼠標和鍵盤輸入等等這些功能並不屬於OpenGL,只是利用了第三方軟件庫,
正如例子里面用到的glut、glew,這些工具簡化了我們的開發,讓我們可以更加專注於OpenGL的本質。
首先頭文件以及一些變量定義
#include<iostream> using namespace std; #include "vgl.h" #include "LoadShaders.h" enum VA0_IDs { Triangles, NumVAOs }; enum Buffer_IDs { ArrayBuffer, NumBuffers }; enum Attrib_IDs { vPosition = 0, vColor = 1 }; GLuint VAOs[NumVAOs]; GLuint Buffers[NumBuffers]; const GLuint NumVertices = 6;
接着先進入main函數,看看主要做了什么。
int main(int argc, char** argv){ /** 該函數用來初始化GLUT庫,之后會與當前窗口系統產生交互 關於命令行參數,可以指定窗口大小、位置或顏色類型等 因此glutInitWindowSize、glutInitDisplayMode或glutInitWindowPosition等可以在glutInit之前運行 但是當命令行有參數時,glutInit會移除之前的操作,比如設置窗口大小等等。 */ glutInit(&argc, argv); /** 該函數指定了窗口顏色類型 除了GLUT_RGBA(指定 RGBA 顏色模式的窗口)外,還有其他類型,比如 GLUT_RGB(指定 RGB 顏色模式的窗口)、 GLUT_DEPTH(窗口使用深度緩存)、 GLUT_STENCIL(窗口使用模板緩存)等等 可查閱GLUT文檔 https://www.opengl.org/documentation/specs/glut/spec3/node12.html#SECTION00033000000000000000 */ glutInitDisplayMode(GLUT_RGBA); /** 接下來這兩個函數看名字就已經知道其用途 分別是設置窗口大小以及窗口位置 不過我們還是根據實際設備的尺寸動態進行設置比較好 */ glutInitWindowSize(256, 256); glutInitWindowPosition(300, 300); /** 從名字可以得知,使用某個指定版本的OpenGL 但是我使用4.3的時候運行會崩潰 換成3.0之后便可以,原因待查。 */ glutInitContextVersion(3, 0); /** OpenGL3.2后提出兩種模式 兼容模式compatibility profile,該模式保證1.0以后的所有特性都可以使用。 核心模式core profile,該模式可以確保使用的只是其最新特性 */ //glutInitContextProfile(GLUT_CORE_PROFILE); //glutInitContextProfile(GLUT_COMPATIBILITY_PROFILE); /** 創建一個窗口,參數為窗口標題 創建完的窗口會關聯OpenGL的上下文環境 該函數API官方說明: The display state of a window is initially for the window to be shown. But the window's display state is not actually acted upon until glutMainLoop is entered. This means until glutMainLoop is called, rendering to a created window is ineffective because the window can not yet be displayed. 簡單的說,就是在執行了glutMainLoop函數之后,對窗口的渲染才起作用。 如果不調用glutMainLoop,窗口不會顯示。 */ glutCreateWindow(argv[0]); if(!GL_ARB_vertex_array_object) std::cout << "GLEW_ARB_vertex_array_object not available." << std::endl; /** 該函數屬於另外一個輔助庫GLEW(OpenGL Extension Wrangler),該庫是C/C++的擴展庫,方便我們獲取OpenGL擴展的各種函數。 而這里說明下openGL在Windows下的情況。 萬惡的微軟為了推自己的D3D,所以默認對openGL的支持是很有限的。 從openGL1.1版本開始就再也沒有升級了,差不多都十多年了。 所以現在Windows下對於openGL的支持,全靠顯卡廠商。 正因為此,更新到最新的顯卡驅動也是非常必須的。 對於不一樣的顯卡,支持openGL1的版本也是不一樣的,具體需要上各家網站查看。 譬如我的GT750,就支持openGL4.3 雖然安裝完驅動后就支持最新的openGL了,但是微軟並沒有提供直接的openGL API,導致使用起來比較繁瑣。 於是,GLEW得用處就來了,他其實就是對這些繁瑣的事情進行的封裝,使得程序員可以很方便的調用glxxx的openGL函數。 所以,GLEW簡化獲取函數地址的過程,減少了我們的工作量! 因此此函數初始化后,我們就可以在之后的代碼里面方便地使用相關的gl函數。 */ if (glewInit()){ cerr << "Unable to initialize GLEW ... exiting" << endl; exit(EXIT_FAILURE); } /** 這些函數是我自己加進去的,可以通過以下方法獲取自己系統中的OpenGL信息: */ const char* version = (const char*)glGetString(GL_VERSION); printf("OpenGL 版本:%s\n", version); const char* extensions = (const char*)glGetString(GL_EXTENSIONS); //printf("OpenGL 擴展:%s\n", extensions); const char* renderer = (const char*)glGetString(GL_RENDERER); printf("OpenGL 顯卡:%s\n", renderer); const char* vendor = (const char*)glGetString(GL_VENDOR); printf("OpenGL 開發商:%s\n", vendor); /** 該函數初始化OpenGL的相關數據,為之后的渲染做准備。 接下來會詳細解析 */ init(); /** 該函數為窗口設置了回調函數,即在窗口有更新時,該回調函數就會執行。 參數是指函數的地址,一個函數指針。 */ glutDisplayFunc(display); /** 該函數是個無限循環函數,即死循環。 它會一直處理已經被注冊的回調函數,以及用戶輸入等操作。 */ glutMainLoop(); return 0; }
之后我們進去初始化函數init看看
void init(void){ /** 該函數原型為void glGenVertexArrays(GLsizei n, GLuint *arrays); 作用是返回n個未使用的對象名保存到數組arrays中, 作為頂點數組對象(Vertex Array Object),常用簡寫VAO替代。 在OpenGL中,VAO負責管理和頂點(Vertices)集合相關聯的各種數據。 但是這些數據我們是保存到頂點緩存對象中(Vertex Buffer Object),簡稱VBO。之后我們會詳細介紹 現在我們只需知道VAO是一個對象,其中包含一個或者更多的VBOs。 */ glGenVertexArrays(NumVAOs, VAOs); /** 該函數原型為void glBindVertexArray(GLuint array); 作用簡單來說,就是綁定對象(Bind An Object) 對於這個函數來說,VAOs[Triangles]里面保存着glGenVertexArrays執行完后返回的對象名,而它作為參數傳入該函數。 第一次調用時OpenGL內部會分配這個對象所需的內存並且將它作為當前對象。 書上關於這個綁定對象的定義用設置鐵路的道岔開關來描述,我覺得還是挺好理解的: 【一旦設置了開關,從這條線路通過的所有列車都會駛向對應的軌道,如果設置到另外一個狀態, 那么之后經過的立車都會駛向另外一條軌道】 OpenGL也是如此,當前綁定了哪個對象,之后的操作都是針對於這個對象,除非重新綁定了其他對象。 一般來說,有兩種情況需要我們綁定對象: 1、創建對象並初始化它所對應的數據時 2、下次我們准備使用這個對象而它並不是當前所綁定的對象時 */ glBindVertexArray(VAOs[Triangles]); /** 定義了兩個三角形的坐標 此處需要了解,當前的坐標系是以屏幕為中心的,x軸正方向向右,y軸正方向向上。 范圍分別為[-1,1]、[-1,1] */ GLfloat vertices[NumVertices][2] = { { -0.90, -0.90 }, // Triangle 1 { 0.85, -0.90 }, { -0.90, 0.85 }, { 0.90, -0.85 }, // Triangle 2 { 0.90, 0.90 }, { -0.85, 0.90 } }; /** 該函數原型為void glGenBuffers(GLsizei n, GLuint *buffers); 作用是返回n個當前未使用的對象名保存到buffers數組中,作為頂點緩存對象(Vertex Buffer Objects),簡稱VBO。 VBO是指OpenGL服務端(OpenGL server)分配和管理的一塊內存區域。幾乎所有傳入OpenGL的數據都是存儲在VBO當中。 */ glGenBuffers(NumBuffers, Buffers); /** 該函數原型為void glBindBuffer(GLenum target, GLuint buffer); 作用是為當前已分配的名稱綁定不同類型的緩存對象。簡單的說,就是初始化頂點緩存對象。 由於OpenGL里有很多種不同類型的緩存對象,因此綁定緩存對象時需要指定對應類型, 用參數target表示。在這個例子中,是將頂點數據保存到緩存當中,因此使用GL_ARRAY_BUFFER類型來表示。 目前緩存對象的類型共有8種,分別用於不同的OpenGL功能實現。以后介紹到VBO的時候再詳細解釋。 */ glBindBuffer(GL_ARRAY_BUFFER, Buffers[ArrayBuffer]); /** 上面的glBindBuffer中只是初始化了VBO,而下面這個函數則是把頂點數據(vertex data)傳遞到緩存對象中。 它主要做了兩件事 1、分配頂點數據所需的存儲空間 2、將數據從應用程序的數組中拷貝到OpenGL服務端的內存中 函數原型為 void glBufferData(GLenum target, GLsizeptr size, const GLvoid* data, GLenum usage); target用於表示緩存的對象的類型 size用於表示要存儲數據的大小,也即是OpenGL在分服務端內存分配的內存大小 data用於表示要存儲的數據的指針 usage用於設置分配數據之后的OpenGL的讀取和寫入方式 */ glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); //--------------------------------------------------------------------------------- /** 上面的代碼 我們創建並綁定了VAO 以及定義了頂點數據,並將之傳送到VBO中。 那么這個時候VAO還沒有和VBO產生聯系,在該函數末尾我們再詳細說明。 接下來我們先看看着色器。 */ //--------------------------------------------------------------------------------- /** ShaderInfo該結構體的定義位於頭文件LoadShaders.h中,如果有興趣可以先看看,這里暫時不介紹 我們先只需知道 1、對於每一個OpenGL程序,當它所使用的OpenGL版本高於或等於3.1時,都需要指定至少兩個着色器: 頂點着色器以及片段着色器,下面通過LoadShaders函數來完成這個任務。 2、我們需要額外編寫兩個文件,就下面例子來說,一個是triangles.vert頂點着色器 另外一個是triangles.frag片段着色器。兩個文件在末尾會貼出來。 3、着色器所采用的語言是OpenGL着色語言GLSL,與C++非常類似。 關於着色器的詳細信息,下一篇再詳細介紹。 */ ShaderInfo shaders[] = { { GL_VERTEX_SHADER, "triangles.vert" }, { GL_FRAGMENT_SHADER, "triangles.frag"}, { GL_NONE, NULL} }; GLuint program = LoadShaders(shaders); glUseProgram(program); /** 函數原型:void glVertexAttribPointer(GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const GLvoid* pointer); 之前我們調用了glBindData所傳遞給緩存的只是數據,之后我們要使用它,還必須指定數據類型。 所以該函數完成的主要任務是: 1、告訴OpenGL,該存儲數據的格式 2、因為我們使用着色器來編程,因此在頂點着色器階段,我們會使用該函數來給着色器中的in類型的屬性變量傳遞數據。 那么它們是怎么聯系起來的呢? 便是通過第一個參數index,指明了着色器程序中變量的下標的作用。 例如:layout( location=index ) in vec4 position; 如果這個index和glVertexAttribPointer的第一個參數一樣,那么相關緩存區的數據就會傳遞到這個position變量中去。 3、該函數執行之后,會影響改變VAO的狀態,VBO會被復制保存到VAO中。之后如果改變了當前所綁定的緩存對象,也不會改變到VAO里的對象。 */ glVertexAttribPointer(vPosition, 2, GL_FLOAT, GL_FALSE, 0, BUFFER_OFFSET(0)); /** 因為默認情況下,頂點屬性數組是不可訪問的,所以我們需要調用以下函數激活它。 范圍為0到GL_MAX_VERTEX_ATTRIBS-1 */ glEnableVertexAttribArray(vPosition); }
再之后我們進入渲染函數display
void display(void){ /** 清除指定的緩存數據並且重新設置為當前的清除值 參數可以通過邏輯或操作來指定多個數值參數。 GL_COLOR_BUFFER_BIT 顏色緩存 GL_DEPTH_BUFFER_BIT 深度緩存 GL_STENCIL_BUFFER_BIT 模板緩存 而OpenGL默認的清除顏色是黑色 如果要指定其他顏色,可以調用glClearColor 另外我們還需要知道,因為OpenGL是一種狀態機,因此對它的所有設定OpenGL都會保留。 所以對於glClearColor,最好的調用方法是放在初始化方法中,因為這樣它只會被調用一次 如果放在display中,OpenGL這樣每一幀都會調用它重復設置清除顏色值,會降低運行效率。 */ glClear(GL_COLOR_BUFFER_BIT); /** 該函數我們在初始化函數見過,不過這次的功能不太一樣 之前是初始化,現在是激活該頂點數組對象。 現在使用它作為頂點數據所使用的頂點數組。 */ glBindVertexArray(VAOs[Triangles]); /** 函數原型void glDrawArrays(GLenum mode, GLint first, GLsizei count); 設置了渲染模式為GL_TRIANGLES,起始位置位於緩存的0偏移位置,一共渲染NumVertices個元素。 以后我們會繼續學習各種圖元 */ glDrawArrays(GL_TRIANGLES, 0, NumVertices); /** 該函數強制吧所有進行中的OpenGL命令立即完成並傳輸到OpenGL服務端處理。 */ glFlush(); }
接下來是關於着色器部分的代碼,由於內容過長,決定一分為二
地址在【第二篇 Hello OpenGL 續】http://www.cnblogs.com/MyGameAndYOU/p/4681710.html
如果有發現我的內容有錯誤,懇請告訴我,我會改正,謝謝!
2015.07.26
廣州