一、基礎圖形管線
渲染管線(rendering pipeline),它是一系列數據處理過程,并且將應用程序的數據轉換到最終渲染的圖像。下圖是OpenGL 4.3 版本的管線。
OpenGL 中的 圖元 只不過是頂點的集合以預定義的方式結合在一起罷了。
通過最近學習 OpenGL 的藍寶書(《OpenGL超級寶典》),學到了基礎渲染這塊,為了加深理解,按照書中優化了一下渲染管線的流程圖,并在圖中添加上了翻譯和自己的理解,加深自己的印象并幫助更多學習 OpenGL 的朋友們更好的學習。
OpenGL渲染管線簡化流程圖:
1、客戶端-服務器
管線上半部分是客戶端,下半部分是服務器。就 OpenGL 而言,客戶端是存儲在 CPU 存儲器中的,驅動程序將渲染命令與數據組合起來發給服務器執行。
服務器和客戶端在功能上是異步的。客戶端不斷的將數據和命令組合在一起送入緩沖區,緩沖區再發送到服務器執行。
2、著色器
上圖中最大的框代表的是 頂點著色器 和 片元著色器。著色器是使用GLSL編寫的程序。
頂點著色器:頂點著色器處理從客戶端輸入的數據,用數學運算來計算光照效果、位移、顏色值等。有幾個頂點,頂點著色器就要執行幾次。
上圖中的 圖元組合(Primitive Assembly)框圖意在說明3個頂點已經組合在了一起。
片元著色器:片元著色器來計算片元的最終顏色(盡管在下一個階段(逐片元的操作)時可能還會改變顏色一次)和它的深度值。在這里我們會使用紋理映射的方式,對頂點處理階段所計算的顏色值進行補充。如果我們覺得不應該繼續繪制某個片元,在片元著色器中還可以終止這個片元的處理,這一步叫做片元的丟棄(discard)。
頂點的著色器和片元著色器之間的區別: 頂點著色(包括細分和幾何著色)決定了一個圖元應該位于屏幕的什么位置,而片元著色使用這些信息來決定某個片元的顏色應該是什么。
著色器的渲染:
頂點著色器(必要)
細分著色器(可選)
幾何著色器(可選)
片元著色器(必要)
3、三種向OpenGL 著色器傳遞渲染數據的?法
屬性:就是對?個頂點都要作出改變的數據元素。實際上,頂點位置本身就是一個屬性.。屬性可以是浮點類型,整型,布爾類型等。
Uniform值:通過設置 Uniform 變量就緊接著發送一個圖元批次處理命令。Uniform 變量實際上可以無限次的使?。 設置一個應用于整個表?面的單個顏色值,還可也是一個時間值。
使用下面的函數:
// Use a stock shader, and pass in the parameters needed
GLint UseStockShader(GLT_STOCK_SHADER nShaderID, ...);
傳遞不用的 Uniform 參數可以使用不同的存儲著色器:
首先定義一個顏色黑色 vBlack:
GLfloat vBlack[] = { 0.0f, 0.0f, 0.0f, 1.0f };
聲明一個全局的存儲著色器變量 shaderManager:
GLShaderManager shaderManager;
shaderManager.UseStockShader(GLT_SHADER_FLAT, transformPipeline.GetModelViewProjectionMatrix(), vBlack);
//使用 單位著色器
//參數1:簡單的使用默認笛卡爾坐標系(-1,1),所有片段都應用一種顏色。GLT_SHADER_IDENTITY
//參數2:著色器顏色
shaderManager.UseStockShader(GLT_SHADER_IDENTITY, vGreen);
GLShaderManger::UseStockShader(GLT_SHADER_IDENTITY,GLfloat mvp[16],GLfloat vColor[4])
紋理:對紋理進行采樣和篩選。紋理數據的作用不僅僅是表現圖形。很多圖形文件格式都是以無符號字節形式對顏色分量進行存儲的,但我們仍然可以設置浮點紋理。這就是說,任何大型浮點數據塊(例如消耗資源很大的函數的大型查詢表)都可以通過這種方式傳遞給著色器。
4、使用存儲著色器
在OpenGL 核心框架中,并沒提供任何內建渲染管線,在提交一個幾何圖形進行渲染之前,必須實現一個著色器。著色器由GLTools 的 C++ 類 GLShaderManager 管理。他們能夠滿足進行基本渲染的基本要求,要求不高的程序員,這些存儲著色器已經足以滿足他們的需求。但隨著時間和經驗的提升,大部分開發者可能不滿足于此,會著手去寫著色器,手寫的我會在以后的文章里再寫出來。
4.1著色器的使用
1)GLShaderManager 的初始化:
GLShaderManager shaderManager;
shaderManager.InitializeStockShaders();
2)GLShaderManager 屬性:
存儲著色器為每個變量都使用一致的內部變量命名規則和相同的屬性槽(Attribute Slot)。下表列出了這些屬性:
表-GLShaderManager 預定義的標識符
標識符 | 描述 |
---|---|
GLT_ATTRIBUTE_VERTEX | 3分量(x, y, z)頂點位置 |
GLT_ATTRIBUTE_COLOR | 4分量(r, g, b, a)顏色值 |
GLT_ATTRIBUTE_NORMAL | 3分量(x, y, z)表面法線 |
GLT_ATTRIBUTE_TEXTURE0 | 第一對 2 分量(s ,t)紋理坐標 |
GLT_ATTRIBUTE_TEXTURE1 | 第二對 2 分量(s ,t)紋理坐標 |
4.2 GLShanderManager 的 uniform 值
一般情況,要對幾何圖形進行渲染,我們需要為讀寫遞交屬性矩陣,首先要綁定到我們想要使用的著色程序上,并提供程序的 Uniform 值。GLShanderManager 類可以(暫時)為我們完成這項工作。
useStockShader 函數會選擇一個存儲著色器并提供這個著色器的 Uniform 值,這些工作通過一次函數調用就能完成:
GLShaderManager::UseStockShader(GLenum shader, ... ...);
在 C 語言(或 C++ 語言)中,......表示函數接受一個可變的參數數量。就這個函數本身而言,它根據我們選擇的著色器,從堆棧中提取正確的參數,這些參數就是特定著色器要求的 Uniform 值。
(1) 單位(Identity)著色器 GLT_SHADER_IDENTITY
參數1:單位著色器
參數2:顏色值
GLShaderManager::UseStockShader(GLT_SHADER_IDENTITY, GLfoat vColor[4]);
單位(Identity)著色器: 只是簡單地使用默認的笛卡爾坐標系(坐標范圍-1.0~1.0)。所有片段都應用同一種顏色,結合圖形為實心和未渲染的。這種著色器只使用一個屬性
GLT_ATTRIBUTE_VERTEX
。vColor參數包含了要求的顏色。
(2) 平面(Flat)著色器 GLT_SHADER_FLAT
參數1:平面著色器
參數2:允許變化的4*4矩陣
參數3:顏色值
GLShaderManager::UseStockShader(GLT_SHADER_FLAT, GLfoat mvp[16], GLfloat vColor[4]);
平面(Flat)著色器:將單位著色器進行了擴展,允許為集合圖形變換指定一個 4 x 4 的變換矩陣。經常被稱作“模型師徒投影矩陣”。這種著色器只使用一個屬性
GLT_ATTRIBUTE_VERTEX
。
(3) 上色(Shaded)著色器 GLT_SHADER_SHADED
GLShaderManager::UseStockShader(GLT_SHADER_SHADED, GLfoat mvp[16]);
上色(Shaded)著色器:唯一的 Uniform 值就是在幾何圖形中應用的變換矩陣。GLT_ATTRIBUTE_VERTEX 和 GLT_ATTRIBUTE_COLOR 在這種著色器中都會使用。顏色值將被平滑地插入頂點之間(稱為平滑著色)。
(4) 默認光源著色器 GLT_SHADER_DEFAULT_LIGHT
參數1:默認光源著色器
參數2:模型視圖矩陣
參數3:投影矩陣
參數4:顏色值
GLShaderManager::UseStockShader(GLT_SHADER_DEFAULT_LIGHT, GLfoat mvMatrix[16], GLfloat pMatrix[16], GLfloat vColor[4]);
默認光源著色器:這種著色器使對象產生陰影和光照的效果。需要模型視圖矩陣、投影矩陣和作為基本色的顏色值等 Uniform 值。所需的屬性有 GLT_ATTRIBUTE_VERTEX(頂點分量)和 GLT_ATTRIBUTE_NORMAL(表面法線)。
(5) 點光源著色器 GLT_SHADER_POINT_LIGHT_DIFF
參數1:點光源著色器
參數2:模型視圖矩陣
參數3:投影矩陣
參數4:視點坐標系中的光源位置
參數5:顏色值
GLShaderManager::UseStockShader(GLT_SHADER_POINT_LIGHT_DIFF, GLfoat mvMatrix[16], GLfloat pMatrix[16], GLfloat vLightPos[3], GLfloat vColor[4]);
點光源著色器:和默認光源著色器很相似,但光源位置可能是待定的。接受 4 個 Uniform 值,即模型視圖矩陣、投影矩陣、視點坐標系中的光源位置和對象的基本漫反射顏色。同樣所需的屬性有 GLT_ATTRIBUTE_VERTEX(頂點分量)和 GLT_ATTRIBUTE_NORMAL(表面法線)。
(6) 紋理替換矩陣 GLT_SHADER_TEXTURE_REPLACE
GLShaderManager::UseStockShader(GLT_SHADER_TEXTURE_REPLACE, GLfoat mvMatrix[16], GLint nTextureUnit);
紋理替換矩陣:著色器通過給定的模型視圖投影矩陣,使用綁定到 nTextureUnit(紋理單元) 指定的紋理單元的紋理對幾何圖形進行變換。片段顏色是從紋理樣本中直接獲取的。所需的屬性有 GLT_ATTRIBUTE_VERTEX(頂點分量)和 GLT_ATTRIBUTE_NORMAL(表面法線)。
(7) 紋理調整著色器 GLT_SHADER_TEXTURE_MODULATE
GLShaderManager::UseStockShader(GLT_SHADER_TEXTURE_MODULATE, GLfoat mvMatrix[16], GLfloat vColor, GLint nTextureUnit);
紋理調整著色器:這種著色器將一個基本色乘以一個取自紋理單元 nTextureUnit 的紋理。所需的屬性有 GLT_ATTRIBUTE_VERTEX(頂點分量)和 GLT_ATTRIBUTE_TEXTURE0(紋理坐標)。
(8) 紋理光源著色器 GLT_SHADER_TEXTURE_POINT_LIGHT_DIFF
參數1:紋理光源著色器
參數2:模型視圖矩陣
參數3:投影矩陣
參數4:視點坐標系中的光源位置
參數5:幾何圖形的基本色
參數6:將要使用的紋理單元
GLShaderManager::UseStockShader(GLT_SHADER_TEXTURE_POINT_LIGHT_DIFF, GLfloat mvMatrix, GLfoat mvMatrix[16], GLfloat vLightPos[3], GLfloat vBaseColor[4], GLint nTextureUnit);
紋理光源著色器:這種著色器將一個紋理通過漫反射照明計算進行調整(相乘),光線在視覺空間中的位置是給定的。這種著色器接受 5 個 Uniform 值,即模型視圖矩陣、投影矩陣、視覺空間中的光源位置、幾何圖形的基本色和將要使用的紋理單元。所需的屬性有 GLT_ATTRIBUTE_VERTEX(頂點分量)、GLT_ATTRIBUTE_NORMAL(表面法線)和 GLT_ATTRIBUTE_TEXTURE0(紋理坐標)。
二、OpenGL 基礎圖元
OpenGL圖元的模式標識
圖元類型 | OpenGL 枚舉量 |
---|---|
點 | GL_POINTS |
線 | GL_LINES |
條帶線 | GL_LINE_STRIP |
循環線 | GL_LINE_LOOP |
獨立三角形 | GL_TRIANGLES |
三角形條帶 | GL_TRIANGLE_STRIP |
三角形扇面 | GL_TRIANGLE_FAN |
1、點和線
(1)點
點 是最簡單的圖像。每個特定的頂點在屏幕上都僅僅是一個單獨的點。默認的情況下,點的大小是一個像素的大小。我們可通過調用glPointSize改變默認點的大小:
void glPointSize(GLfloat size);
// 1.最簡單也是最常用的 4.0f,表示點的大小
glPointSize(4.0f);
// 2.設置點的大小范圍和點與點之間的間隔
GLfloat sizes[2] = {2.0f,4.0f};
GLfloat step = 1.0f;
// 獲取點大小范圍和最小步長(增量)
glGetFloatv(GL_POINT_SIZE_RANGE,sizes);
glGetFloatv(GL_POINT_GRAULARITY,&step);
還可以通過使用程序點大小模式來設置點大小。
// 3.通過使用程序點大小模式來設置點大小
glEnable(GL_PROGRAM_POINT_SIZE);
// 這種模式下允許我們通過編程在頂點著色器或幾何著色器中設置點大小。著色器內建變量:gl_PointSize,并且可以在著色器源碼直接寫
gl_PointSize = 5.0;
(2)線
比點更進一步的是獨立線段。一個線段就是2個頂點之間繪制的。
默認情況下,線段的寬度是一個像素。改變線段唯一的方式是使用函數 glLineWidth:
void glLineWidth(GLfloat width);
// 設置獨立線段寬度為1.5f;
glLineWidth(1.5f);
(3)線帶
線帶(line strip)連續地從一個頂點到下一個頂點繪制的線段,以形成一個真正連接點的線條。
(為了把圖形連接起來,每個連接的頂點會被選定2次。一次作為線段的終點、一次作為下一條線段的起點),這次是作為GL_LINE_STRIP
繪制的。
(4)線環
線環(line loop)是線帶的一種簡單拓展,在線帶的基礎上額外增加了一條連接著一批次中最后一個點和第一個點的線段。
2、繪制三角形
可能存在的最簡單的實體多邊形就是三角形,它只有3個邊。光柵化硬件最歡迎三角形。并且現在OpenGL已經是OpenGL中支持的唯一一種多邊形。每3個頂點定義一個新的三角形。
(1)單獨的三角形
如下圖是使用 GL_TRIANGLES
繪制的兩個三角形:
繪制金字塔
下面繪制4個三角形組成金字塔形的三角形。我們可以使用方向鍵來旋轉金字塔,從不同角度進行觀察。但是這個金字塔木有底面,所以我們可以看到它的內部。
代碼如下:
//通過三角形創建金字塔
GLfloat vPyramid[12][3] = {
-2.0f, 0.0f, -2.0f,
2.0f, 0.0f, -2.0f,
0.0f, 4.0f, 0.0f,
2.0f, 0.0f, -2.0f,
2.0f, 0.0f, 2.0f,
0.0f, 4.0f, 0.0f,
2.0f, 0.0f, 2.0f,
-2.0f, 0.0f, 2.0f,
0.0f, 4.0f, 0.0f,
-2.0f, 0.0f, 2.0f,
-2.0f, 0.0f, -2.0f,
0.0f, 4.0f, 0.0f};
//GL_TRIANGLES 每3個頂點定義一個新的三角形
triangleBatch.Begin(GL_TRIANGLES, 12);
triangleBatch.CopyVertexData3f(vPyramid);
triangleBatch.End();
(2)環繞
將順時針方向繪制的三角形用逆時針的方式繪制。
如下圖,在繪制第一個三角形時,線條是按照從V0-V1,再到V2。最后再回到V0的一個閉合三角形。 這個是沿著頂點順時針方向。
這種順序與方向結合來指定頂點的方式稱為 環繞。
下圖的2個三角形的環繞方向完全相反。
正面與背面:
在默認的情況下,OpenGL認為具有逆時針方向環繞的多邊形是 正面的。而右側的順時針方向三角形是三角形的 背面。
為什么區分正背面很重要?
因為我們常常希望為一個多邊形的正面和背面分別設置不同的物理特征。我們可以完全隱藏一個多邊形的背面,或者給它設置一種不同的顏色和反射屬性。紋理圖像在背面三角形中也是相反的。在一個場景中,使所有的多邊形保持環繞方向的一致,并使用正面多邊形來繪制所有實心物體的表面是非常重要的。
如果想改變OpenGL這個默認行為,可以調用下面的函數:
glFrontFace(GL_CW);
參數:GL_CW | GL_CCW
GL_CCW:表示傳入的mode會選擇逆時針為前向
GL_CW:表示順時針為前向。
默認:GL_CCW。逆向時針為前向。
(3) 三角地帶
對于很多表面和形狀來說,我們可能需要繪制幾個相連的三角形。我們可以使用GL_TRIANGLE_STRIP
圖元繪制一串相連的三角形。從而節省大量的時間。
使用三角帶而不是分別指定每個三角形,這樣做有兩個優點:
- 用前3個頂點指定第1個三角形之后,對于接下來的每一個三角形,只需要再指定1個頂點。需要繪制大量的三角形時,采用這種方法可以節省大量的程序代碼和數據存儲空間。
- 提供運算性能和節省帶寬。更少的頂點意味著數據從內存傳輸到圖形卡的速度更快,并且頂點著色器需要處理的次數也更少了。
(4) 三角形扇
除了三角形帶之外,還可以使用GL_TRIANGLE_FAN
創建一組圍繞一個中心點的相連三角形。通過4個頂點所產生的包括3個三角形的三角形扇。 第一個頂點 V0 構建了扇形的原點,用前3個頂點指定了最初的三角形之后,后續的每個頂點都和原點(V0)以及之前緊挨著它的那個頂點(Vn-1)形成接下來的三角形。
3、一個簡單批次容器
GLTools 庫中包含額一個簡單的容器類,叫做 GLBatch。這個類可以作為7種圖元的簡單批次容器使用。而且它知道在使用GL_ShaderManager
支持的任意存儲著色器時如何對圖元進行渲染。
使用 GLBatch 類非常簡單。首先對批次進行初始化,告訴這個類它代表哪種圖元,其中包括的頂點數,以及(可選)一組或兩組紋理坐標。
參數1:圖元
參數2:頂點數
參數3:一組或者2組紋理坐標(可選)
void GLBatch::Begain(GLeunm primitive,GLuint nVerts,GLuint nTexttureUnints = 0);
然后,至少要復制一個由3分量(x, y, z)頂點組成的數組。
//復制表面法線
void GLBatch::CopyVertexData3f(GLfloat *vVerts);
還可以選擇復制表面發現、顏色和紋理坐標。
//復制表面法線
void GLBatch::CopyNormalDataf(GLfloat *vNorms);
//復制顏色
void GLBatch::CopyColorData4f(GLfloat *vColors);
//復制紋理坐標
void GLBatch::CopyTexCoordData2f(GLFloat *vTextCoords,GLuint uiTextureLayer);
完成上述工作以后,可調用End來表明已經完成了數據復制工作,并且將設置內部標記,以通知這個類包含哪些屬性。
//結束繪制
void GLBatch::End(void);
實際上,可以在任何我們想要的時候進行復制,只要不改變累的大小即可。
而一旦調用End函數,就不能再增加新的屬性了(也就是說我們現在也不能確定是否要有表面法線了)。
關于提交屬性的 OpengGL 實際內部運行機制實際上比這要復雜的多。GLBatch 類只是一個便利類(convenience class),就像使用GLUT一樣方便。
以上的總結參考了并部分摘抄了以下文章,非常感謝以下作者的分享!:
1、《OpenGL超級寶典 第5版》
2、《OpenGL編程指南(英文第八版)》
3、作者CC老師_HelloCoder的《004--OpenGL 圖元》
轉載請備注原文出處,不得用于商業傳播——凡幾多