01-OpenGL基礎渲染

《OpenGL文章匯總》
上一篇繪制了一個三角形《00-OpenGL繪制三角形》铅祸,本文接著探討基礎渲染

理論較多,先看個圖片,主要搞清楚下面的圖像如何繪制出來


0.gif

OpenGL所創(chuàng)建的物體和場景是由更小、更簡單的形狀組成的棵磷,并且是按照各種各種各樣獨一無二的方式進行排列和組合的野宜,組成3D物體的建筑塊饺鹃,稱為圖元囊陡。在OpenGL中,所有的圖元都是一維凭语、二維或三維的物體,從簡單的點和線到復雜的多邊形都是如此薄啥。

1.基礎圖形管線

一個單獨的點就是一個圖元辕羽,它只需要一個頂點。三角形則是另外一個例子垄惧,它是由三個頂點組成的圖元刁愿。基礎渲染管線接受三個頂點并將它們轉換成一個三角形赘艳。它還可能應用顏色酌毡、一個或多個紋理并且移動它們的位置。


image

I.客戶機-服務器

管線分為兩部分蕾管,上半部分是客戶機端枷踏,下半部分是服務器端。
客戶機端是存儲在CPU存儲器中的掰曾,并且在應用程序中執(zhí)行旭蠕,或者在主系統(tǒng)內存的驅動程序中執(zhí)行。驅動程序將渲染命令與數據組合起來旷坦,并發(fā)送到服務器執(zhí)行掏熬。

客戶機不斷的將數據塊和命令塊組合在一起并送入緩沖區(qū),然后這些緩沖區(qū)會發(fā)送到服務器執(zhí)行秒梅。服務器將執(zhí)行這些緩沖區(qū)的內容旗芬,與此同時客戶端又做好了發(fā)送下一個用于渲染的數據或信息的準備。

II.著色器

著色器必須從源代碼中編譯和鏈接到一起(這一點和C捆蜀、C++程序非常類似)才能使用疮丛。最終準備就緒的著色器程序隨后在第一階段構成頂點著色器,在第二階段構成片段著色器辆它。幾何著色器可以(選擇性地)安排在兩者之間誊薄,就像用來將數據來回傳遞的所有類型的反饋機制一樣。

頂點著色器處理從客戶機輸入的數據锰茉,應用變換呢蔫,或者進行其他類型的數學運算來計算光照效果、位移飒筑、顏色值等等片吊。為了渲染一個公有3個頂點的三角形,頂點著色器將執(zhí)行3次协屡,也就是為每個頂點執(zhí)行一次俏脊。在目前的硬件上有多個執(zhí)行單元同時運行,這就意味著所有這三個頂點都可以同時進行處理著瓶。今天的圖形處理器屬于大規(guī)模并行計算機。
3個頂點都做好了光柵化的準備。Primitive Assembly圖元組合框圖意在說明3個頂點已經組合在一起材原,而三角形已經逐個片段地進行了光柵化沸久。每個片段都通過執(zhí)行片段著色器而進行了填充,片段著色器會輸出我們將在屏幕上看到的最終顏色值余蟹。
有2中向OpenGL著色器床底渲染數據方法供程序員選擇卷胯,即屬性、uniform值和紋理威酒。

A.屬性

屬性就是一個對每個頂點都要作改變的數據元素窑睁。屬性值可以是浮點數、整數或布爾數據葵孤。屬性總是以四維向量的形式進行內部存儲的担钮,即使我們不會使用到所有4個分量。
屬性會從本地客戶機內存中賦值存儲在圖形硬件中的一個緩沖區(qū)上尤仍。屬性只供頂點著色器使用,對于片段著色器來說沒什么意義。對于每個頂點都有一個實際存儲值躺枕。

B.Uniform值

屬性是一個對每個頂點都要做改變的數據元素奸例。頂點位置本身就是一個屬性。屬性是一種對于整個批次的屬性都取統(tǒng)一值的單個值赡模。它是不變的田炭。通常設置完Uniform變量就緊接著發(fā)出渲染一個圖元批次的命令。Uniform變量實際上可以無次數限制地使用漓柑。Uniform變量一個最常見的應用是在頂點渲染中設置變換矩陣教硫。
Uniform值在本質上像屬性一樣,可以是浮點值欺缘、整數或布爾值栋豫,但和屬性不同的是,頂點著色器和片段著色器中都可以有Uniform變量谚殊。

C.紋理

從頂點著色器和片段著色器中都可以對紋理值進行采樣和篩選丧鸯。片段著色器對一個紋理進行采樣,并在一個三角形的表面上應用圖形數據嫩絮。

D.輸出

輸出數據是作為一個階段著色器的輸出定義的丛肢,而在后續(xù)階段的著色器則是作為輸入(In)定義的。輸出類型的數據可以簡單地從一個階段傳遞到下一個階段剿干,也可以以不同的方式插入蜂怎。客戶端的代碼接觸不到這些內部變量置尔,但是它們在頂點著色器和片段著色器中(還包括可選的幾何著色器)都進行了聲明杠步。頂點著色器為輸出變量分配了一個值,這個值是常量,也可以在圖元被光柵化時插入到頂點之間幽歼。片段著色器對應的同名輸入值接受這個常量或插入值朵锣。

2.創(chuàng)建坐標系

I.正投影

所有正投影空間范圍內的所有東西都會被顯示在屏幕上,而不存在照相機或視點坐標系的概念甸私。通過調用GLFrustum方法來完成

GLFrustum::SetOrthographic(GLfloat xMin,GLfloat xMax,GLfloat yMin,GLfloat yMax,GLfloat zMin,GLfloat zMax);

II.透視投影

透視投影會進行透視除法對距離觀察者很遠的對象進行縮短和收縮诚些。平截頭體是一個金字塔形被截短之后的形狀,它的觀察方向是從金字塔的尖端到寬闊端皇型,觀察者的視點與金字塔的尖端拉開一定距離诬烹。
GLFrustum類通過調用SetPerspective方法為我們構建一個平截頭體。

GLFrustum::SetPerspective(float fFov,float fAspect,float fNear,float fFar);

其中的參數分別在垂直方向上的視場角度弃鸦,窗口的寬度和高度的縱橫比绞吁,以及到近裁剪面和遠裁剪面之間的距離。寬度除以高度就能得到窗口或視口的縱橫比寡键。

3.使用存儲著色器

GLShaderManager在使用前必須進行初始化
shaderManger.InitializeStockShaders();

I.屬性

OpenGL支持多達16種可以為每個頂點設置的不同類型參數掀泳。這些參數編號為從0到15,并且可以頂點著色器中的任何指定變量相關聯西轩。存儲著色器為每個變量都使用一致的內部變量命名規(guī)則和相同的屬性槽员舵。

II.Uniform值

要對幾何圖形進行渲染,要為對象遞交屬性矩陣藕畔,但首先要綁定到我們想要使用的著色器程序上马僻,并提供程序的Uniform值。GLShaderManager類可以為我們完成這項工作注服,UseStockShader函數會選擇一個存儲著色器并提供這個著色器的Uniform值韭邓,這些工作通過一次函數調用就能完成。
GLShaderManager::UseStockShader(GLenum shader,...);

A.單位(Identity)著色器

單位(Identity)著色器只是簡單的使用默認笛卡爾坐標系(在所有坐標軸上的坐標范圍都是-1.0~1.0)溶弟。所有片段都應用同一種顏色女淑,幾何圖形為實心和未渲染的。這種著色器只使用一個屬性GLT_ATTRIBUTE_VERTEX辜御。vColor參數包含了要求的顏色鸭你。
GLShaderManger::UseStockShader(GLenum shader,...);

B.平面著色器

平面(Flat)著色器將統(tǒng)一著色器進行了擴展,允許為幾何圖形變換指定一個4x4變換矩陣擒权。左乘模型視圖矩陣和投影矩陣袱巨,經常被稱作"模型視圖投影矩陣"。這種著色器只使用一個屬性GLT_ATTRIBUTE_VERTEX碳抄。

GLShaderManager::UseStockShader(GLT_SHADER_FLAT,GLfloat mvp[16],GLfloat vColor[4]);

C.上色(Shaded)著色器

著色器唯一的Uniform值就是在幾何圖形中應用的變換矩陣愉老。GLT_ATTRIBUTE_VERTEX和GLT_ATTRIBUTE_COLOR在這種著色器中都會使用。顏色值將被平滑的插入頂點之間(成為平滑著色)剖效。

GLShaderManager::UseStockShader(GLT_SHADER_SHADED,GLfloat mvp[16]);

D.默認光源著色器

著色器創(chuàng)造出一種錯覺嫉入,類似于由位于觀察者位置的單漫射光所產生的效果焰盗。著色器使對象產生陰影和關照的效果。需要模型視圖矩陣咒林、投影矩陣和作為基本色的顏色值等Uniform值姨谷。所需的屬性有GLT_ATTRIBUTE_VERTEX何GLT_ATTRIBUTE_NORMAL。光照著色器都需要正規(guī)矩陣(normal matrix)作為Uniform值映九。著色器從模型視圖矩陣中推導出了正規(guī)矩陣。

E.點光源著色器

點光源著色器和默認光源著色器很相似瞎颗,但是光源位置可能是特定的件甥。這種著色器接受4個Uniform值,即模型視圖矩陣哼拔、投影矩陣引有、視點坐標系中的光源位置和對象的節(jié)本漫反射顏色。

GLShaderManager::UseStockShader(GLT_SHADER_POINT_LIGHT_DIFF,GLfloat mvMatrix[16],GLfloat pMatrix[16],GLfloat vLightPos[3],GLfloat vColor[4]);

F.紋理替換矩陣

著色器通過給定的模型視圖投影矩陣倦逐,使用綁定到nTextureUnit指定的紋理單元的紋理對幾何圖形進行變換譬正。片段顏色是直接從紋理樣本中直接獲取的。所需的屬性有GLT_ATTRIBUTE_VERTEX和GLT_ATTRIBUTE_NORMAL檬姥。
GLShaderManager::UseStockShader(GLT_SHADER_TEXTURE_REPLACE,GLfloat mvpMatrix[16],GLint nTextureUnit);

G.紋理調整著色器

著色器將一個基本色乘以一個取自紋理單元nTextureUnit的紋理曾我。所需的屬性有GLT_ATTRIBUTE_VERTEX和GLT_ATTRIBUTE_TEXTURE0。
GLShaderManager::UseStockShader(GLT_SHADER_TEXTURE_MODULATE,GLfloat mvpMatrix[16],GLfloat vColor,Glint nTextureUnit);

H.紋理光源著色器

將一個紋理通過漫反射照明計算進行調整(相乘)健民,光線在視覺空間中的位置是給定的抒巢。著色器接受5個Uniform值,即模型視圖矩陣秉犹、投影矩陣蛉谜、視覺空間中的光源位置、幾何圖形的基本色和將要使用的紋理單元崇堵。
所需的屬性有GLT_ATTRIBUTE_VERTEX型诚、GLT_ATTRIBUTE_NORMAL和GLT_ATTRIBUTE_TEXTURE0。
GLShaderManager::UseStockShader(GLT_SHADER_TEXTURE_POINT_LIGHT_DIFF,GLfloat mvMatrix,GLfloat pMatrix[16],GLfloat vLightPos[3],GLfloat vBaseColor[4],GLint nTextureUnit);

4.將點連接起來

確定視景體中的位置坐標鸳劳,將這些點狰贯、線和三角形從創(chuàng)建的3D空間投影到計算機屏幕上的2D圖形則是著色器程序和光柵化硬件要完成的工作。

I.點和線

從7個由定義的幾何圖元來開始繪制實心幾何圖形棍辕。
這些圖元將在一個包含給定圖元的所有頂點和相關屬性的單個批次中進行渲染暮现。實質上,在一個給定的批次中的所有頂點都會用于組成這些圖元中的一個楚昭。
GL_POINTS:每個頂點在屏幕上都是一個單獨的點
GL_LINES:每一對頂點定義了一個線段
GL_LINE_STRIP:一個從第一個頂點依次經過每個后續(xù)頂點而繪制的線條
GL_LINE_LOOP:和GL_LINE_STRIP相同栖袋,但最后一個頂點和第一個頂點也連接了起來
GL_TRIANGLES:每三個頂點定義了一個新的三角形
GL_TRIANGLE_STRIP:共用一個條帶(strip)上的頂點的一組三角形
GL_TRIANGLE_FAN:以一個圓點為中心呈扇形排列,共用相鄰頂點的一組三角形


0.gif

繪制的代碼放最后

A.點

點是最簡單的圖元抚太,每個特定的頂點在屏幕上都僅僅是一個單獨的點塘幅。默認情況下昔案,點的大小為一個像素〉缦保可以通過調用glPointSize(GLfloat size)改變默認點的大小踏揣。
void glPointSize(GLfloat size);
glPointSize函數接受一個參數,這個參數指定繪制點的近似尺寸(用像素表示)匾乓。

GLfloat sizes[2];//存儲支持的點大小范圍
GLfloat step;//存儲支持的點大小增量
//獲取支持的點大小范圍和步長(增量)
glGetFloatv(GL_POINT_SIZE_RANGE,sizes);
glGetFloatv(GL_POINT_SIZE_GRANULARITY,&step);

大小值數組由兩個元素組成捞稿,其中包含了glPointSize的最小和最大可用值。變量step保存了相鄰點大小值之間所允許的最小步長拼缝。OpenGL規(guī)范只需要支持一個點大小娱局,即1.0,指定一個允許范圍之外的點大小并不會被認為是一個錯誤咧七。根據哪個值離指定值最近來使用所允許的最大值或最小值代替衰齐。
點大小和其他幾何圖形不同,它并不會受到透視除法的影響继阻。
當它們離視點更遠時耻涛,它們看上去并不會變得更小;如果他們離視點更近,他們看上去也不會變得更大一些瘟檩。點總是正方形的像素抹缕,即便使用glPointSize增加點的大小,情況也不會發(fā)生變化墨辛。我們只能得到更大的正方形歉嗓!為了獲得圓點,我們必須在抗鋸齒模式下繪制點背蟆。
還可以通過使用程序點大小模式來設置點大小鉴分。
glEnable(GL_PROGRAM_POINT_SIZE);
允許我們通過編程在頂點著色器或幾何著色器中設置點大小
gl_PointSize = 5.0;

image

B.線

比點更進一步的是獨立的線段。一條線段是在兩個頂點之間繪制的带膀,所以一批線段應該包括偶數個頂點志珍,每個頂點都是線段的端點。
默認情況下垛叨,線段的寬度為一個像素伦糯。改變線段寬度的唯一方式是使用函數glLineWidth。
void glLineWidth(GLfloat width);

image

C.線帶

線帶連續(xù)地從一個頂點到下一個頂點繪制線段嗽元,以形成一個真正連接點的線條敛纲。為了用獨立的線段圍繞佛羅里達州的輪廓形成一個連續(xù)的線條,每個連接頂點都會被選定兩次剂癌。其中一次是作為一條線段的終點淤翔,另一次是作為下一條線段的起點。


image

D.線環(huán)

線環(huán)是線帶的一種簡單擴展佩谷,在線帶的基礎上額外增加了一條連接著一批次中最后一個點和第一個點的線段旁壮。


image

II.繪制3D三角形

一個多邊形就是一個封閉的圖形监嗜,它可能用顏色或紋理數據進行填充,也可能不進行填充抡谐,在OpenGL中裁奇,它是所有實體對象構建的基礎。

III.單獨的三角形

最簡單的實體多邊形就是三角形麦撵,它只有3個邊刽肠。光柵化硬件最歡迎三角形,而現在三角形是OpenGL中支持的唯一一種多邊形了免胃。每三個頂點定義一個新的三角形五垮。

A.環(huán)繞

順序與方向結合來指定頂點的方式成為環(huán)繞。OpenGL認為具有逆時針方向環(huán)繞的多邊形是正面的杜秸。
glFrontFace(GL_CW);
GL_CW參數告訴OpenGL順時針環(huán)繞的多邊形將被認為是正面的。為了把多邊形的正面重新恢復為逆時針環(huán)繞润绎,可以在這個函數中使用GL_CCW參數撬碟。

B.三角形帶

用前3個頂點指定第1個三角形之后,對于接下來的每個三角形莉撇,只需要再指定1個頂點呢蛤。需要繪制大量的三角形時,采用這種方法可以節(jié)省大量的程序代碼和數據存儲空間棍郎。第二個優(yōu)點是提高運算性能和節(jié)省帶寬其障。更少的頂點意味著數據從內存?zhèn)鬏數綀D形卡的速度更快,并且頂點著色器需要進行處理的次數也更少涂佃。


圖片.png

C.三角形扇

使用GL_TRIANGLE_FAN創(chuàng)建一組圍繞一個中心點的相連三角形励翼。
原點稍微升高一點,以使它有一點深度辜荠。


1

IV.一個簡單批次容器

GLTools庫中包含一個簡單的容器類汽抚,叫做GBatch。首先對批次進行初始化伯病,告訴這個類它代表哪種圖元造烁,其中包括的頂點數,以及(可選)一組或兩組紋理坐標午笛。
至少要復制一個由3分量(x,y,z)頂點組成的數組惭蟋。還可以選擇復制表面法線、顏色和紋理坐標药磺。
完成上述工作之后告组,可以調用End來表明已經完成了數據復制工作,并且將設置內部標記癌佩,以通知這個類包含哪些屬性惹谐。一旦調用end函數就不能添加新的屬性了持偏。
在RenderScene函數中,選擇了適當的存儲著色器并調用Draw函數

    //Blue background
    glClearColor(0.0f, 0.0f, 1.0f, 1.0f);
    
    shaderManager.InitializeStockShaders();
    //Load up a triangle
    
    GLfloat vVerts[] = {
      -0.5f,0.0f,0.0f,
       0.5f,0.0f,0.0f,
       0.0f,0.5f,0.0f,
    };
    
    triangleBatch.Begin(GL_TRIANGLES, 3);
    triangleBatch.CopyVertexData3f(vVerts);
    triangleBatch.End();
    GLfloat vRed[] = {1.0f,0.0f,0.0f,1.0f};
    shaderManager.UseStockShader(GLT_SHADER_IDENTITY,vRed);
    triangleBatch.Draw();
    
    //Perform the buffer swap to display the back buffer
    glutSwapBuffers();

V.不希望出現的幾何圖形

默認情況下氨肌,我們所渲染的每個點鸿秆、線或三角形都會在屏幕上進行光柵化,并且會按照在組合圖元批次時指定的順序進行排列怎囚,在某些特殊情況下會產生問題卿叽。
如果我們繪制一個有很多個三角形組成的實體對象,那么第一個繪制的三角形可能會被后面繪制的三角形覆蓋恳守。


2

對于這個問題考婴,一個可能的解決辦法是,對這些三角形進行排序催烘,并且首先渲染哪些較遠的三角形沥阱,再在它們上方渲染那些較近的三角形。這種方式成為油畫法伊群,在計算機圖形處理中是非常低效的考杉。主要原因有兩個。其一是必須對任何發(fā)生集合圖形重疊地方的每個像素進行兩次寫操作舰始,而在存儲其中進行寫操作會使速度變慢崇棠。其二是對獨立的三角形進行排序的開銷會過高。

A.正面和背面剔除

對正面和背面三角形進行區(qū)分的原因之一就是為了進行剔除丸卷。在渲染的圖元裝配階段就整體拋棄了一些三角形枕稀,并且沒有執(zhí)行任何不恰當的光柵化操作。
表面剔除按如下方式開啟
glEnable(GL_CULL_FACE);
按照如下方式關閉
glDisable(GL_CULL_FACE)
剔除正面還是背面由另外一個函數glCullFace控制的谜嫉。
void glCullFace(GLenum mode)
mode參數的可用值為GL_FRONT萎坷、GL_BACK或GL_FRONT_AND_BACK。要消除不透明物體的內部幾何圖形就需要兩行代碼沐兰。

glCullFace(GL_BACK);
glEnable(GL_CULL_FACE);
3

B.深度測試

深度測試是另外一種高效消除隱藏表面的技術食铐。在繪制一個像素時,將一個值(成為z值)分配給它,這個值表示它到觀察者的距離僧鲁。當另外一個像素需要在屏幕上的同樣位置進行繪制時虐呻,新像素的z值將與已經存儲的像素的z值進行比較。如果新像素的z值比較大寞秃,那么它距離觀察者就比較近斟叼,這樣就在原來的像素上面,所以原來的像素就會被新的像素覆蓋春寿。如果新像素的z值更低朗涩,那么它就必須位于原來像素的后面,不能遮住原來的像素绑改。在內部谢床,這個任務是通過深度緩沖區(qū)實現的兄一,它存儲了屏幕上每個像素的深度值。在內部识腿,這個任務是通過深度緩沖區(qū)實現的出革,它存儲了屏幕上每個像素的深度值。
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH);
啟用深度測試
glEnable(GL_DEPTH_TEDT);
未使用深度測試前渡讼,會有問題骂束,一部分與離我們更近卻恰好先進行繪制的另一部分重疊。

5

image

如果沒有深度緩沖區(qū)成箫,那么啟用深度測試的命令將被忽略展箱。
4.gif

首先繪制那些離觀察者較近的對象,然后再繪制那些較遠的對象蹬昌。深度測試將消除那些應該被已存在像素覆蓋的像素混驰。

C.多邊形模式

多邊形(含三角形)不一定是實心的。在默認情況下皂贩,多邊形是作為實心圖形繪制的栖榨,但我們可以通過將多邊形指定為顯示輪廓或只有點(只顯示頂點)來改變這種行為。函數glPolygonMode允許將多邊形渲染成實體先紫、輪廓或只有點。
void glPolygonMode(GLenum face,GLenum mode);
和表面剔除一樣筹煮,face參數的可用值為GL_FRONT遮精、GL_BACK或GL_FRONT_AND_BACK。而mode參數的可用值為GL_FILL(默認值)败潦、GL_LINE或GL_POINT本冲。
通過調用glPolygonMode將多邊形正面和背面設為線框模式,就能實現這種線框渲染劫扒。
glPolygonMode(GL_FRONT_AND_BACK,GL_LINE);

6

VI.多邊形偏移

前面深度測試是基于z值的不同而達到繪圖效果的不同檬洞,特殊情況若想在一架打飛機上繪制一架小飛機,則z值完全相等沟饥,若我們想要繪制實心幾何圖形但又要突出它的邊時添怔。
為了看到三角形的邊,使用glPolygonMode來繪制條帶贤旷,會生成粗黑線邊緣广料。

圖片.png

可以使用glPolygonOffset函數使我們可以調節(jié)片段的深度值,這樣就能使深度值產生偏移而并不實際改變3D空間中的物理位置幼驶。
void glPolygonOffset(GLfloat factor,GLfloat units);
應用到片段上的總偏移可以通過下面的方程式表示艾杏。
Depth Offset = (DZ x factor) + (r x units);
其中DZ是深度值(z值)相對于多邊形屏幕區(qū)域的變化量,而r則是使深度緩沖區(qū)值產生變化的最小值盅藻。并沒有一個硬性規(guī)定能夠找到一個萬無一失的值购桑,具體應用的時候需要試驗一下畅铭。

VII.裁剪

另外一種提高渲染性能的方法是只刷新屏幕上發(fā)生變化的部分。將OpenGL渲染限制在窗口中一個較小的矩形區(qū)域中勃蜘。OpenGL允許我們再將要進行渲染的窗口中指定一個裁剪框硕噩。在默認情況下,裁剪框與窗口同樣大小元旬,并且不會進行裁剪測試榴徐。
void glScissor(GLint x,GLint y,GLsizei width,GLsizei height);

image

5.混合

OpenGL渲染時會把顏色值放在顏色緩沖區(qū)中。每個片段的深度值也是放在深度緩沖區(qū)中的匀归。當深度測試被關閉(禁用)時坑资,新的顏色值簡單地覆蓋顏色緩沖區(qū)中已經存在的其他值。當深度測試被打開(啟用)時穆端,新的顏色片段只有當它們比原來的值更接近鄰近的裁剪平面時才會替換原來的顏色片段袱贮。如果打開了OpenGL的混合功能,那么下層的顏色值就不會被清除体啰。
glEnable(GL_BLEND);

I.組合顏色

當混合功能被啟用時攒巍,原顏色和目標顏色的組合方式是由混合方程式控制的。在默認情況下荒勇,混合方程式如下:
Cf = (Cs * S) + (Cd * D);
Cf是最終計算產生的顏色柒莉,Cs是源顏色,Cd則是目標顏色沽翔,S和D分別是源和目標混合因子兢孝。混合因子由如下函數進行設置仅偎。
glBlendFunc(GLenum S,GLenum D);

7

II.改變混合方程式

有五種混合方程式可供選擇
void glBlendEquation(GLenum mode);
GL_FUNC_ADD:Cf = (Cs * S) + (Cd * D)
GL_FUNC_SUBTRACT: Cf = (Cs * S) - (Cd * D)
GL_FUNC_REVERSE_SUBTRACT:Cf = (Cd * D) - (Cs * S)
GL_MIN:Cf = min(Cs,Cd)
GL_MAX:Cf = max(Cs,Cd)
除了glBlendFunc之外跨蟹,還可以利用下面的函數更加靈活的進行選擇。
void glBlendFuncSeparate(GLenum srcRGB,GLenum dstRGB,GLenum srcAlpha,GLenum dstAlpha);

其中glBlendFunc函數指定了源和目標RGBA值的混合函數橘沥,而glBlendFuncSeparate函數則允許為RGB和alpha成分單獨指定混合函數窗轩。
void glBlendColor(GLclampf red,GLclamp green,Glclampf blue,GLclampf alpha);

III.抗鋸齒

OpenGL混合功能的另一個用途是抗鋸齒。
為了消除圖元之間的鋸齒狀邊緣座咆,OpenGL使用混合功能來混合片段的顏色痢艺,也就是把像素的目標顏色與周圍像素的顏色進行混合。從本質上來說介陶,在任何圖元的邊緣上腹备,像素顏色會稍微延伸到相鄰的像素。
開啟抗鋸齒功能非常簡單斤蔓。必須啟用混合功能植酥。
還需確保把混合方程式設置為GL_ADD,在啟用混合功能并選擇正確的混合函數以及混合方程式之后,可以選擇調用glEnable函數對點友驮、直接和(或)多邊形(任何實心圖元)進行抗鋸齒處理漂羊。

glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);
glEnable(GL_POINT_SMOOTH);//Smooth out points
glEnable(GL_LINE_SMOOTH);//Smooth out lines
glEnable(GL_POLYGON_SMOOTH);//Smooth out polygon edges

在抗鋸齒和正常渲染模式間切換


8
//對菜單選擇做出反應,正確地重置標志
// Reset flags as appropriate in response to menu selections
void ProcessMenu(int value)
    {
    switch(value)
        {
        case 1:
            // Turn on antialiasing, and give hint to do the best
            // job possible.
            glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
            glEnable(GL_BLEND);
            glEnable(GL_POINT_SMOOTH);
            glHint(GL_POINT_SMOOTH_HINT, GL_NICEST);
            glEnable(GL_LINE_SMOOTH);
            glHint(GL_LINE_SMOOTH_HINT, GL_NICEST);
            glEnable(GL_POLYGON_SMOOTH);
            glHint(GL_POLYGON_SMOOTH_HINT, GL_NICEST);
            break;

        case 2:
            // Turn off blending and all smoothing
            glDisable(GL_BLEND);
            glDisable(GL_LINE_SMOOTH);
            glDisable(GL_POINT_SMOOTH);
            break;

        default:
            break;
        }
        
    // Trigger a redraw
    glutPostRedisplay();
    }

IV.多重采樣

抗鋸齒處理的最大優(yōu)點之一就是能夠使多邊形的邊緣更為平滑卸留,使渲染效果顯得更為自然和逼真走越。點和直線的平滑處理是得到廣泛支持的,但遺憾的是多邊形的平滑處理并沒有在所有的平臺上都得到實現耻瑟。
OpenGL新增了一個特性旨指,成為多重采樣,可以用來解決多邊形的平滑處理。
多重采樣會在已經包含了顏色喳整、深度和模板值的幀緩沖區(qū)就會添加一個額外的緩沖區(qū)谆构。所有的圖元在每個像素上都進行了多次采樣,其結果就存儲在這個緩沖區(qū)中框都。每次當這個像素進行更新時搬素,這些采樣值進行解析,以產生一個單獨的值魏保。
GLUT提供了一個位段(GLUT_MULTISAMPLE),允許請求這種幀緩沖區(qū)熬尺。為了請求一個多重采樣、完全顏色谓罗、帶深度的雙緩沖幀緩沖區(qū)粱哼,可以調用:
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH |GLUT_MULTISAMPLE);
可以使用glEnable/glDisable組合(使用GL_MULTISAMPLE標記)打開或關閉多重采樣:

glEnable(GL_MULTISAMPLE);或glDisable(GL_MULTISAMPLE);

使用多重采樣時,就不能同時使用點和直線的平滑處理檩咱。在一種特定的OpenGL實現中揭措,點和直線如果采用平滑處理可能會比使用多重采樣效果更好。當繪制點和直線時税手,可以關閉多重采樣蜂筹,在繪制其他實心幾何圖形時再打開多重采樣需纳。

glDisable(GL_MULTISAMPLE);
glEnable(GL_POINT_SMOOTH);
//Draw some smooth points
glDisable(GL_POINT_SMOOTH);
glEnable(GL_MULTISAMPLE);

當然芦倒,如果沒有多重采樣緩沖區(qū),OpenGL就當做GL_MULTISAMPLE是被禁用的不翩。
多種采樣緩沖區(qū)在默認情況下使用片段的RGB值兵扬,并不包括顏色的alpha成分】隍穑可以通過調用glEnable來修改這個行為器钟。

GL_SAMPLE_ALPHA_TO_COVERAGE ------使用alpha值
GL_SAMPLE_ALPHA_TO_ON -----將alpha值設為1并使用它
GL_SAMPLE_COVERAGE -----使用glSampleCoverage所設置的值
當使用GL_SAMPLE_COVERAGE時,glSampleConverage函數允許使用一個特定的值妙蔗,它是與片段覆蓋值進行按位與操作的結果傲霸。
void glsampleCoverage(GLclampf value,GLboolean invert);
這種對多重采樣操作的優(yōu)化并不是嚴格由OpenGL規(guī)范所規(guī)定的,其確切的結果可能因不同的OpenGL實現而異。

// Called to draw scene
void RenderScene(void)
    {
        // Clear blue window
        glClearColor(0.0f, 0.0f, 1.0f, 0.0f);
        glClear(GL_COLOR_BUFFER_BIT);
        
        // Now set scissor to smaller red sub region
        glClearColor(1.0f, 0.0f, 0.0f, 0.0f);
        glScissor(100, 100, 600, 400);
        glEnable(GL_SCISSOR_TEST);
        glClear(GL_COLOR_BUFFER_BIT);
        
        // Finally, an even smaller green rectangle
        glClearColor(0.0f, 1.0f, 0.0f, 0.0f);
        glScissor(200, 200, 400, 200);
        glClear(GL_COLOR_BUFFER_BIT);
        
        // Turn scissor back off for next render
        glDisable(GL_SCISSOR_TEST);

    glutSwapBuffers();
    }

七種圖元代碼

GLShaderManager     shaderManager;
GLMatrixStack       modelViewMatrix;
GLMatrixStack       projectionMatrix;
GLFrame             cameraFrame;
GLFrame             objectFrame;
GLFrustum           viewFrustum;

GLBatch             pointBatch;
GLBatch             lineBatch;
GLBatch             lineStripBatch;
GLBatch             lineLoopBatch;
GLBatch             triangleBatch;
GLBatch             triangleStripBatch;
GLBatch             triangleFanBatch;

GLGeometryTransform transformPipeline;
M3DMatrix44f        shadowMatrix;


GLfloat vGreen[] = { 0.0f, 1.0f, 0.0f, 1.0f };
GLfloat vBlack[] = { 0.0f, 0.0f, 0.0f, 1.0f };


// Keep track of effects step
int nStep = 0;


///////////////////////////////////////////////////////////////////////////////
// This function does any needed initialization on the rendering context. 
// This is the first opportunity to do any OpenGL related tasks.
void SetupRC()
    {
    // Black background
    glClearColor(0.7f, 0.7f, 0.7f, 1.0f );

    shaderManager.InitializeStockShaders();

    glEnable(GL_DEPTH_TEST);

    transformPipeline.SetMatrixStacks(modelViewMatrix, projectionMatrix);

    cameraFrame.MoveForward(-15.0f);
    
    //////////////////////////////////////////////////////////////////////
    // Some points, more or less in the shape of Florida
    GLfloat vCoast[24][3] = {{2.80, 1.20, 0.0 }, {2.0,  1.20, 0.0 },
                            {2.0,  1.08, 0.0 },  {2.0,  1.08, 0.0 },
                            {0.0,  0.80, 0.0 },  {-.32, 0.40, 0.0 },
                            {-.48, 0.2, 0.0 },   {-.40, 0.0, 0.0 },
                            {-.60, -.40, 0.0 },  {-.80, -.80, 0.0 },
                            {-.80, -1.4, 0.0 },  {-.40, -1.60, 0.0 },
                            {0.0, -1.20, 0.0 },  { .2, -.80, 0.0 },
                            {.48, -.40, 0.0 },   {.52, -.20, 0.0 },
                            {.48,  .20, 0.0 },   {.80,  .40, 0.0 },
                            {1.20, .80, 0.0 },   {1.60, .60, 0.0 },
                            {2.0, .60, 0.0 },    {2.2, .80, 0.0 },
                            {2.40, 1.0, 0.0 },   {2.80, 1.0, 0.0 }};
    
    // Load point batch
    pointBatch.Begin(GL_POINTS, 24);
    pointBatch.CopyVertexData3f(vCoast);
    pointBatch.End();
    
    // Load as a bunch of line segments
    lineBatch.Begin(GL_LINES, 24);
    lineBatch.CopyVertexData3f(vCoast);
    lineBatch.End();
    
    // Load as a single line segment
    lineStripBatch.Begin(GL_LINE_STRIP, 24);
    lineStripBatch.CopyVertexData3f(vCoast);
    lineStripBatch.End();
    
    // Single line, connect first and last points
    lineLoopBatch.Begin(GL_LINE_LOOP, 24);
    lineLoopBatch.CopyVertexData3f(vCoast);
    lineLoopBatch.End();
    
    // For Triangles, we'll make a Pyramid
    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};
    
    triangleBatch.Begin(GL_TRIANGLES, 12);
    triangleBatch.CopyVertexData3f(vPyramid);
    triangleBatch.End();
    

    // For a Triangle fan, just a 6 sided hex. Raise the center up a bit
    GLfloat vPoints[100][3];    // Scratch array, more than we need
    int nVerts = 0;
    GLfloat r = 3.0f;
    vPoints[nVerts][0] = 0.0f;
    vPoints[nVerts][1] = 0.0f;
    vPoints[nVerts][2] = 0.0f;

    for(GLfloat angle = 0; angle < M3D_2PI; angle += M3D_2PI / 6.0f) {
        nVerts++;
        vPoints[nVerts][0] = float(cos(angle)) * r;
        vPoints[nVerts][1] = float(sin(angle)) * r;
        vPoints[nVerts][2] = -0.5f;
        }

    // Close the fan
    nVerts++;
    vPoints[nVerts][0] = r;
    vPoints[nVerts][1] = 0;
    vPoints[nVerts][2] = 0.0f;
        
    // Load it up
    triangleFanBatch.Begin(GL_TRIANGLE_FAN, 8);
    triangleFanBatch.CopyVertexData3f(vPoints);
    triangleFanBatch.End();     
        
    // For triangle strips, a little ring or cylinder segment
    int iCounter = 0;
    GLfloat radius = 3.0f;
    for(GLfloat angle = 0.0f; angle <= (2.0f*M3D_PI); angle += 0.3f)
        {
        GLfloat x = radius * sin(angle);
        GLfloat y = radius * cos(angle);
            
        // Specify the point and move the Z value up a little   
        vPoints[iCounter][0] = x;
        vPoints[iCounter][1] = y;
        vPoints[iCounter][2] = -0.5;
        iCounter++;

        vPoints[iCounter][0] = x;
        vPoints[iCounter][1] = y;
        vPoints[iCounter][2] = 0.5;
        iCounter++;            
        }

    // Close up the loop
    vPoints[iCounter][0] = vPoints[0][0];
    vPoints[iCounter][1] = vPoints[0][1];
    vPoints[iCounter][2] = -0.5;
    iCounter++;
    
    vPoints[iCounter][0] = vPoints[1][0];
    vPoints[iCounter][1] = vPoints[1][1];
    vPoints[iCounter][2] = 0.5;
    iCounter++;            
        
    // Load the triangle strip
    triangleStripBatch.Begin(GL_TRIANGLE_STRIP, iCounter);
    triangleStripBatch.CopyVertexData3f(vPoints);
    triangleStripBatch.End();    
    }


/////////////////////////////////////////////////////////////////////////
void DrawWireFramedBatch(GLBatch* pBatch)
    {
    // Draw the batch solid green
    shaderManager.UseStockShader(GLT_SHADER_FLAT, transformPipeline.GetModelViewProjectionMatrix(), vGreen);
    pBatch->Draw();
    
    // Draw black outline
    glPolygonOffset(-1.0f, -1.0f);      // Shift depth values
    glEnable(GL_POLYGON_OFFSET_LINE);

    // Draw lines antialiased
    glEnable(GL_LINE_SMOOTH);
    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    
    // Draw black wireframe version of geometry
    glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
    glLineWidth(2.5f);
    shaderManager.UseStockShader(GLT_SHADER_FLAT, transformPipeline.GetModelViewProjectionMatrix(), vBlack);
    pBatch->Draw();
    
    // Put everything back the way we found it
    glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
    glDisable(GL_POLYGON_OFFSET_LINE);
    glLineWidth(1.0f);
    glDisable(GL_BLEND);
    glDisable(GL_LINE_SMOOTH);
    }


///////////////////////////////////////////////////////////////////////////////
// Called to draw scene
void RenderScene(void)
    {    
    // Clear the window with current clearing color
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);

    modelViewMatrix.PushMatrix();
        M3DMatrix44f mCamera;
        cameraFrame.GetCameraMatrix(mCamera);
        modelViewMatrix.MultMatrix(mCamera);

        M3DMatrix44f mObjectFrame;
        objectFrame.GetMatrix(mObjectFrame);
        modelViewMatrix.MultMatrix(mObjectFrame);

        shaderManager.UseStockShader(GLT_SHADER_FLAT, transformPipeline.GetModelViewProjectionMatrix(), vBlack);

        switch(nStep) {
            case 0:
                glPointSize(4.0f);
                pointBatch.Draw();
                glPointSize(1.0f);
                break;
            case 1:
                glLineWidth(2.0f);
                lineBatch.Draw();
                glLineWidth(1.0f);
                break;
            case 2:
                glLineWidth(2.0f);
                lineStripBatch.Draw();
                glLineWidth(1.0f);
                break;
            case 3:
                glLineWidth(2.0f);
                lineLoopBatch.Draw();
                glLineWidth(1.0f);
                break;
            case 4:
                DrawWireFramedBatch(&triangleBatch);
                break;
            case 5:
                DrawWireFramedBatch(&triangleStripBatch);
                break;
            case 6:
                DrawWireFramedBatch(&triangleFanBatch);
                break;
            }
        
    modelViewMatrix.PopMatrix();

    // Flush drawing commands
    glutSwapBuffers();
    }


// Respond to arrow keys by moving the camera frame of reference
void SpecialKeys(int key, int x, int y)
    {
    if(key == GLUT_KEY_UP)
        objectFrame.RotateWorld(m3dDegToRad(-5.0f), 1.0f, 0.0f, 0.0f);
    
    if(key == GLUT_KEY_DOWN)
        objectFrame.RotateWorld(m3dDegToRad(5.0f), 1.0f, 0.0f, 0.0f);
    
    if(key == GLUT_KEY_LEFT)
        objectFrame.RotateWorld(m3dDegToRad(-5.0f), 0.0f, 1.0f, 0.0f);
    
    if(key == GLUT_KEY_RIGHT)
        objectFrame.RotateWorld(m3dDegToRad(5.0f), 0.0f, 1.0f, 0.0f);
    
    glutPostRedisplay();
    }
    
///////////////////////////////////////////////////////////////////////////////
// A normal ASCII key has been pressed.
// In this case, advance the scene when the space bar is pressed
void KeyPressFunc(unsigned char key, int x, int y)
    {
    if(key == 32)
        {
        nStep++;

        if(nStep > 6)
            nStep = 0;
        }
        
    switch(nStep)
        {
        case 0: 
            glutSetWindowTitle("GL_POINTS");
            break;
        case 1:
            glutSetWindowTitle("GL_LINES");
            break;
        case 2:
            glutSetWindowTitle("GL_LINE_STRIP");
            break;
        case 3:
            glutSetWindowTitle("GL_LINE_LOOP");
            break;
        case 4:
            glutSetWindowTitle("GL_TRIANGLES");
            break;
        case 5:
            glutSetWindowTitle("GL_TRIANGLE_STRIP");
            break;
        case 6:
            glutSetWindowTitle("GL_TRIANGLE_FAN");
            break;
        }
                
    glutPostRedisplay();
    }

///////////////////////////////////////////////////////////////////////////////
// Window has changed size, or has just been created. In either case, we need
// to use the window dimensions to set the viewport and the projection matrix.
void ChangeSize(int w, int h)
    {
    glViewport(0, 0, w, h);
    viewFrustum.SetPerspective(35.0f, float(w) / float(h), 1.0f, 500.0f);
    projectionMatrix.LoadMatrix(viewFrustum.GetProjectionMatrix());
    modelViewMatrix.LoadIdentity();
    }

///////////////////////////////////////////////////////////////////////////////
// Main entry point for GLUT based programs
int main(int argc, char* argv[])
    {
    gltSetWorkingDirectory(argv[0]);
    
    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH | GLUT_STENCIL);
    glutInitWindowSize(800, 600);
    glutCreateWindow("GL_POINTS");
    glutReshapeFunc(ChangeSize);
    glutKeyboardFunc(KeyPressFunc);
    glutSpecialFunc(SpecialKeys);
    glutDisplayFunc(RenderScene);
        
    GLenum err = glewInit();
    if (GLEW_OK != err) {
        fprintf(stderr, "GLEW Error: %s\n", glewGetErrorString(err));
        return 1;
        }
    

    SetupRC();

    glutMainLoop();
    return 0;
    }
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末昙啄,一起剝皮案震驚了整個濱河市穆役,隨后出現的幾起案子,更是在濱河造成了極大的恐慌梳凛,老刑警劉巖耿币,帶你破解...
    沈念sama閱讀 221,548評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現場離奇詭異韧拒,居然都是意外死亡淹接,警方通過查閱死者的電腦和手機,發(fā)現死者居然都...
    沈念sama閱讀 94,497評論 3 399
  • 文/潘曉璐 我一進店門叛溢,熙熙樓的掌柜王于貴愁眉苦臉地迎上來塑悼,“玉大人,你說我怎么就攤上這事雇初÷K粒” “怎么了?”我有些...
    開封第一講書人閱讀 167,990評論 0 360
  • 文/不壞的土叔 我叫張陵靖诗,是天一觀的道長郭怪。 經常有香客問我选侨,道長徒爹,這世上最難降的妖魔是什么挤茄? 我笑而不...
    開封第一講書人閱讀 59,618評論 1 296
  • 正文 為了忘掉前任礁哄,我火速辦了婚禮众旗,結果婚禮上又厉,老公的妹妹穿的比我還像新娘校摩。我一直安慰自己几睛,他們只是感情好败晴,可當我...
    茶點故事閱讀 68,618評論 6 397
  • 文/花漫 我一把揭開白布浓冒。 她就那樣靜靜地躺著,像睡著了一般尖坤。 火紅的嫁衣襯著肌膚如雪稳懒。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,246評論 1 308
  • 那天慢味,我揣著相機與錄音场梆,去河邊找鬼。 笑死纯路,一個胖子當著我的面吹牛或油,可吹牛的內容都是我干的。 我是一名探鬼主播驰唬,決...
    沈念sama閱讀 40,819評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼顶岸,長吁一口氣:“原來是場噩夢啊……” “哼腔彰!你這毒婦竟也來了?” 一聲冷哼從身側響起辖佣,我...
    開封第一講書人閱讀 39,725評論 0 276
  • 序言:老撾萬榮一對情侶失蹤萍桌,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后凌简,有當地人在樹林里發(fā)現了一具尸體上炎,經...
    沈念sama閱讀 46,268評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 38,356評論 3 340
  • 正文 我和宋清朗相戀三年雏搂,在試婚紗的時候發(fā)現自己被綠了藕施。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,488評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡凸郑,死狀恐怖裳食,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情芙沥,我是刑警寧澤诲祸,帶...
    沈念sama閱讀 36,181評論 5 350
  • 正文 年R本政府宣布,位于F島的核電站而昨,受9級特大地震影響救氯,放射性物質發(fā)生泄漏。R本人自食惡果不足惜歌憨,卻給世界環(huán)境...
    茶點故事閱讀 41,862評論 3 333
  • 文/蒙蒙 一着憨、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧务嫡,春花似錦甲抖、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,331評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至去扣,卻和暖如春柱衔,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背厅篓。 一陣腳步聲響...
    開封第一講書人閱讀 33,445評論 1 272
  • 我被黑心中介騙來泰國打工秀存, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留捶码,地道東北人羽氮。 一個月前我還...
    沈念sama閱讀 48,897評論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像惫恼,于是被迫代替她去往敵國和親档押。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,500評論 2 359

推薦閱讀更多精彩內容

  • 一、基礎圖形管線 渲染管線(rendering pipeline - 它是一系列數據處理過程令宿,并且將應用程序的數據...
    蘇蘇慢跑閱讀 366評論 0 4
  • 一叼耙、基礎圖形管線 渲染管線(rendering pipeline),它是一系列數據處理過程粒没,并且將應用程序的數據轉...
    凡幾多閱讀 3,483評論 6 18
  • 學習目標: OpenGL 渲染結構 如何使用7種OpenGL基礎圖元 如何使用儲存著色器 如何使用Uniform屬...
    velue閱讀 1,874評論 0 0
  • OpenGl渲染架構 Client:這里的客戶端指的是我們在應用程序中編寫的C/C++代碼筛婉,以及OpenGL的相關...
    Maji1閱讀 272評論 0 1
  • 一、OpenGL與著色器 在OpenGL3.0之前癞松,OpenGL包含一個固定功能的管線爽撒,它可以在不使用著色器的情況...
    MirL閱讀 599評論 0 0