您好,登錄后才能下訂單哦!
翻譯自《 OpenGL Programming Guide》(8th) 第一章,標題為 Introduction to OpenGL。
紅寶書第八版和第七版的最大的區別就是OpenGL的版本從OpenGL2.X變成了OpenGL4.X,渲染流水線也從固定流水變化為可編程流水線,shader滿天飛...
好,進入正文。
此文主要內容如下:
1.介紹OpenGL的作用,告訴你OpenGL在計算機圖形學中能做什么,不能做什么;
2.介紹OpenGL程序的常用結構;
3.介紹OpenGL渲染流水線的每個階段。
OpenGL是一組應用程接口(Application programming interface),即它是一個能夠操縱計算機圖形硬件的程序庫。OpenGL4.3版包含了3500多個函數接口,用于創建圖像,操作物體等,一切都是為了創建交互性三維計算機圖形程序。
OpenGL是按照流水線型設計的,和硬件無關,這讓它能夠運行于各種各樣的圖形硬件上。同時它也是軟件無關的,可以運行于不同的操作系統,而只需操作系統只需提供一個讓OpenGL運行的GUI庫,同樣的OpenGL也還會提供描述三維模型或者讀取圖片文件的方法,你需要做的是將一系列三維圖元(比如點,線,三角形), 來組成三維物體。
OpenGL并不是一個新事物,1.0版本在1994年6月友硅谷圖形計算機系統開發出來,后續有很多OpenGL的其它版本,也有很多基于OpenGL開發的軟件庫用于更簡單快速地進行應用程序開發,比如游戲開發,科學或者醫學可視化系統地開發,抑或僅僅是為了顯示圖像。越是新版本的OpenGL,和原版本的OpenGL差異就越大。
下面的一個列表簡單地描述了OpenGL渲染一張圖片需要用到的操作。
● 初始化用于顯示圖元的數據;
● 將輸入的圖元作為輸入,在其上執行各種Shaders,計算出圖元的位置,顏色和其他的渲染屬性;
● 將圖元的數學描述轉化為用于顯示在屏幕上的片段(fragment),這個過程稱為柵格化;
● 最后執行fragment shader,處理上面得到的fragments,輸出的是fragments的最終顏色和位置;
● 還可能會執行一個額外的fragment處理,進行混合,透明之類的操作。
OpenGL是一個client - server系統,你寫的應用程序被當作client,運行子啊圖形硬件上的OpenGL實現作為服務端,在一些OpenGL實現中,client和server是運行在不同的機器上的,之間用網絡連接,在這種情況下,client的命令通過網絡由協議進行傳輸,服務器收到命令之后生成最終圖像。
譯者注: OpenGL 內部是一個巨大的狀態機,你所做的大部分是對這個狀態機進行讀取和設置工作。在對物體渲染的時候, OpenGL 會根據狀態機中的當前狀態來進行渲染,好比我們寫OpenGL 程序調用 API 實際上是在寫配置文件一樣。 OpenGL 的狀態很多, 涵蓋光照,紋理,隱藏面消除,霧等等。
OpenGL可以用來做很多事,那么一個也可能是非常復雜的,但是OpenGL應用程序的基本結構通常是類似的:
● 初始化狀態機中的各種狀態變量(譯者注:狀態變量型別是一些C數據類型的 typedef, 有 GLfloat, GLboolean, GLint, GLuint 等等);
● 指定要渲染的物體。
在看代碼之前,我們來介紹一些圖形學名詞,前面我們提到的渲染,指的是計算機從一些模型創建出一張圖像的過程,OpenGL只是渲染系統的一種,還有其他的方式,比如ray-tracing,但是用ray-tracing的系統也可能用OpenGL來顯示圖像或者用于計算。
我們的模型或者物體,用術語來說的話,它們是由一系列圖元來確定的,包括點,線,三角形,它們都是由頂點確定的。
另一個使用OpenGL至關重要的概念是Shader,它們是在圖形硬件中執行的程序,最好的理解方式是將shader當成為GPU(Graphics
Processing Unit)特別編譯的一些小程序.OpenGL包含了編譯shader的工具。
在OpenGL中有四個shader處理階段可以使用,最普遍的是vertex shaders,用于處理頂點,還有fragment shaders,用于處理柵格化時候的片段,vertex和fragment shaders在每一個OpenGL程序中都會用到。
最終生成的圖像包含了顯示在屏幕上的一系列像素。一個像素是顯示器上的最小顯示單位。像素的值存放在 frame buffer中,然后傳輸至顯示設備,frame buffer是由圖形硬件管理的一個存儲區。
下圖顯示了一個簡單的OpenGL程序的輸出,在窗口中渲染了兩個三角形,源碼如下:
/////////////////////////////////////////////////////////////////////// // // triangles.cpp // /////////////////////////////////////////////////////////////////////// #include <iostream> using namespace std; #include "vgl.h" #include "LoadShaders.h" enum VAO_IDs { Triangles, NumVAOs }; enum Buffer_IDs { ArrayBuffer, NumBuffers }; enum Attrib_IDs { vPosition = 0 }; GLuint VAOs[NumVAOs]; GLuint Buffers[NumBuffers]; const GLuint NumVertices = 6; //--------------------------------------------------------------------- // // init // void init(void) { glGenVertexArrays(NumVAOs, VAOs); glBindVertexArray(VAOs[Triangles]); 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 } }; glGenBuffers(NumBuffers, Buffers); glBindBuffer(GL_ARRAY_BUFFER, Buffers[ArrayBuffer]); glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); ShaderInfo shaders[] = { { GL_VERTEX_SHADER, "triangles.vert" }, { GL_FRAGMENT_SHADER, "triangles.frag" }, { GL_NONE, NULL } }; GLuint program = LoadShaders(shaders); glUseProgram(program); glVertexAttribPointer(vPosition, 2, GL_FLOAT, GL_FALSE, 0, BUFFER_OFFSET(0)); glEnableVertexAttribArray(vPosition); } //--------------------------------------------------------------------- // // display // void display( void) { glClear(GL_COLOR_BUFFER_BIT); glBindVertexArray(VAOs[Triangles]); glDrawArrays(GL_TRIANGLES, 0, NumVertices); glFlush(); } //--------------------------------------------------------------------- // // main // int main(int argc, char** argv) { glutInit(&argc, argv); glutInitDisplayMode(GLUT_RGBA); glutInitWindowSize(512, 512); glutInitContextVersion(4, 3); glutInitContextProfile(GLUT_CORE_PROFILE); glutCreateWindow(argv[0]); if (glewInit()) { cerr << "Unable to initialize GLEW ... exiting" << endl; exit(EXIT_FAILURE); } init(); glutDisplayFunc(display); glutMainLoop(); }
簡單地說一下上面的例子做了哪些事,我會在后面詳細地解釋,所以現在看不懂也不用著急。
● 在最開始,我們添加了相應的頭文件,圣米格了一些全局變量和其他的有用的結構體;
● init()是用于初始化后面程序要用到的一些數據,這些大部分是后面渲染要到的定點信息,或者是用于紋理映射要用到的圖像信息,在本例的init()中中,我們首先是指定了要渲染的兩個三角形的位置信息,之后初始化要使用的shader,這次我們只用到了vertex shader和 fragment shader。LoadShaders函數就是用于加載給GPU執行的Shader。init()最后部分做的事稱為 shader plumbing ,在這里將數據和shader中的變量進行綁定;
● display函數是程序中真正執行渲染的部分。函數中通過調用OpenGL的函數進行渲染,基本上所有的display函數會做相同的三個步驟:
1.用glClear() 函數清理窗口;
2.調用OpenGL相應的函數來渲染;
3.將渲染出的圖像用于顯示。
● 最后,mian函數中做了很多的事情 - 創建窗口,調用init,進入時間循環,這里還有一些gl開頭的函數,但和其他的函數又有一些不同,簡單地說,它們是用于在不同的操作系統中寫OpenGL的工具庫:GLUT和GLEW.
OpenGL實現了通常所說的渲染流水線。這個流水線分為一系列不同的階段,能夠將應用程序提供給OpenGL的數據轉化為一幅最終的渲染圖。下面的圖為OpenGL4.3的流水線,這個流水線從發布至今已經進化了非常多。
OpenGL在最初將我們提供的圖形數據(頂點和圖元)傳入到一系列的shader 階段:vertex shading,tesselation shading,然后是geometry shading,這些都在柵格化之前做完。rasterizer會將在裁剪區域的所有圖元生成fragments,然后對每一個生成的fragment執行fragment shader。
就如你所看到的,shaders在創建OpenGL應用程序中扮演了一個非常重要的角色。 你有權利去決定去使用哪個shader 階段,在每個階段中做哪些事情。并不是每一個階段都是必須;實際上,只有vertex shaders和fragment shaders才是一定要用到的。Tessellation 和 geometry shaders只是可選項。
現在,我們對每一個階段都做一下更加深入地了解,這樣你對整體就有更好的拿捏。我知道這些東西對你來說可能有點無法理解,但現在最好是硬著頭皮看一下。你最后一定會明白理解一些理論會讓你在OpenGL的路上走得更遠。
準備向OpenGL發送數據
OpenGL要求將所有的數據都存儲在buffer對象中,所謂buffer對象就是OpenGL server維護的內存塊。將數據存放在buffer中有很多種方法,但是最常用的一種是用 glBufferData()函數,在初始化buffer之前,還有一些額外的工作要做。
向OpenGL發送數據
當我們初始化好buffers之后,我們可以用OpenGL的繪制函數來繪制幾何圖元,比如glDrawArray().
OpenGL中的繪制通常意味著將頂點信息傳送給OpenGL server。一個頂點意味著一個信息的集合,集合中有你想要的任何信息,幾乎一定會包含頂點的位置信息,其他的值(比如法線)將會決定像素的最終值。
Vertex Shading
對于每一個需要渲染的頂點,vertex shader都會去處理和頂點相關的數據。根據在柵格化之前要激活哪些shader,vertex shader可能會非常簡單,可能僅僅是將數據拷貝傳遞到下一個階段 - 我們常稱為是 pass-through shader。對于一個很復雜的vertex shader ,用于計算頂點在屏幕中的位置(通常會用到矩陣變換),計算頂點光照等等。
一個復雜的應用程序可能會有多個vertex shader,但每次只能執行一個。
Tesselation Shading
當vertex shader將每個相關的頂點都處理過一遍之后,如果tessellation shader 階段被激活,tessellation shader將會繼續處理這些數據,tesselation用 patchs來描述一個物體,在這個階段可以用一些相對簡單的patch圖形來細分模型來提供更好的外觀, Tesselation Shading 階段可以用兩個shader來處理,一個用于處理patch 數據,一個用于生成最終形狀。
Geometry Shading
下一個shader階段是geometry Shading,在這個階段可以在柵格化之前處理單個的集合圖元,比如添加一些圖元。這個階段也是可選的,但是非常有用。
圖元組裝
前面的所有階段都是在針對頂點的信息操作,圖元組裝階段將頂點組裝成一組相關聯的幾何圖元,為后面的裁剪和柵格化做準備。
裁剪
有些點會在視口(你打算渲染的窗口)的外面,所以需要將和頂點相關的圖元進行一些處理,將不在視口中的圖元裁剪掉,這個過程叫做裁剪,是在OpenGL中自動處理的。
柵格化
裁剪之后馬上要做的就是柵格化,裁剪之后的圖元都傳遞到 raseriser 中生成fragment。可以將fragment當作是“候選像素”,這些像素存儲在framebuffer中。柵格化之后得到的fragment還是能夠改變顏色,處理這些fregments在下面兩個階段,fragment shading和 per fragment 操作。
Fragment Shading
可編程的最后一個階段是fragment shading,在這里你可以控制fragments的顏色。在這個階段,shader可以決定fragments的最終顏色(雖然在下一個階段,per-fragment操作會最后一次改變顏色),fragment shaders非常地有用,在這里可以處理 texture mapping。如果一個fragment不應該被繪制,fragment shader也可以停止一個fragment的處理,這個過程稱為 fragment discard。
一個很好的思考頂點shader和片段shader的不同點的方法是:vertex shading決定圖元在屏幕中處于什么位置,fragment shading用前面的信息來決定fragment的顏色。
Per-Fragment Operations
這里指的是一些額外的fragment 處理,這是對每個fragment處理的最后階段。在這里fragments的可見性由深度測試和模板測試決定。
詳解例子(略)
《 OpenGL Programming Guide》(8th) 下載
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。