咋看站绪,似乎沒什么問題。但當(dāng)我們?cè)囍蛴倚D(zhuǎn)它的時(shí)候丽柿,會(huì)出現(xiàn)以下問題:
其實(shí)恢准, 這個(gè)“甜甜圈”是由很多個(gè)三角形組成的實(shí)體對(duì)象,其中一些三角形在“甜甜圈”的背面甫题,而另一些則在“甜甜圈”的正面馁筐。但我們看不到背面——至少我們應(yīng)該是看不到的(不考慮透明幾何體的特殊情況)。在默認(rèn)情況下坠非,第一個(gè)繪制的三角形可能會(huì)被后面繪制的三角形覆蓋,三角形繪制的順序就會(huì)一團(tuán)糟,所以會(huì)導(dǎo)致上圖的情況件蚕。對(duì)于這種問題可以有以下的解決辦法:
油畫法
對(duì)這些三角形進(jìn)行排序裆装,并且首先渲染那些較遠(yuǎn)的三角形,再在它們上方渲染那些較近的三角形潦闲,這種方式稱為“油畫法”攒菠。
也就是先繪制場(chǎng)景中離觀察者較遠(yuǎn)的物體,再繪制較近的物體歉闰。
-
例如下?的圖例:先繪制紅?部分辖众,再繪制??部分,最后再繪制灰?部分和敬,即可解決隱藏?消除的問題
-
使?油畫算法凹炸,只要將場(chǎng)景按照物理距離觀察者的距離遠(yuǎn)近排序,由遠(yuǎn)及近的繪制即可概龄。那么會(huì)出現(xiàn)什么問題? 如果三個(gè)三?形是疊加的情況还惠,油畫算法將?法處理。
這種方法在計(jì)算機(jī)圖形處理中是非常低效的私杜,主要原因有兩個(gè)蚕键。其中一個(gè)原因是,我們必須對(duì)任何發(fā)生幾何圖形重疊地方的每個(gè)像素進(jìn)行兩次寫操作衰粹,而在存儲(chǔ)其中進(jìn)行寫操作會(huì)使速度變慢锣光。另外一個(gè)原因是,對(duì)獨(dú)立的三角形進(jìn)行排序的開銷會(huì)過高铝耻。我們有更好的辦法誊爹。
在繪制3D場(chǎng)景的時(shí)候蹬刷,我們需要決定哪些部分是對(duì)觀察者可?的,或者哪些部分是對(duì)觀察者不可?的频丘。對(duì)于不可?的部分办成,應(yīng)該及早丟棄。例如在?個(gè)不透明的墻壁后搂漠,就不應(yīng)該渲染迂卢。這種情況叫做”隱藏?消除”(Hidden surface elimination)。所以延伸出了下面兩種方法桐汤,由OpenGL為我們提供而克,我們只需開啟即可:
正背?剔除(Face Culling)
-
背景
- 嘗試相信?個(gè)3D圖形,你從任何?個(gè)?向去觀察,最多可以看到?個(gè)?怔毛?
- 答案是员萍,最多3?。 從?個(gè)??體的任意位置和?向上看拣度,你不可能看到多于3個(gè)?碎绎。
- 那么思考? 我們?yōu)楹我嘤嗟娜ダL制那根本看不到的3個(gè)??
- 如果我們能以某種?式去丟棄這部分?jǐn)?shù)據(jù),OpenGL 在渲染的性能即可提?超過50%蜡娶。
-
解決問題
- 如何知道某個(gè)?在觀察者的視野中不會(huì)出現(xiàn)?
- 任何平?都有2個(gè)?混卵,正?/背?。意味著你?個(gè)時(shí)刻只能看到??窖张。
- OpenGL 可以做到檢查所有正?朝向觀察者的?幕随,并渲染它們。從?丟棄背?朝向的?宿接。這樣可以節(jié)約?元著?器的性能赘淮。
- 如何告訴OpenGL 你繪制的圖形,哪個(gè)?是正?睦霎,哪個(gè)?是背??
- 答案: 通過分析頂點(diǎn)數(shù)據(jù)的順序
分析頂點(diǎn)順序
-
正?/背?區(qū)分
- 正?: 按照逆時(shí)針頂點(diǎn)連接順序的三?形?
- 背?: 按照順時(shí)針頂點(diǎn)連接順序的三?形?
分析??體中的正背?
-
分析
- 左側(cè)三?形頂點(diǎn)順序?yàn)? 1—> 2—> 3梢卸;右側(cè)三?形的頂點(diǎn)順序?yàn)? 1—> 2—> 3。
- 當(dāng)觀察者在右側(cè)時(shí)副女,則右邊的三?形?向?yàn)槟鏁r(shí)針?向則為正?蛤高,?左側(cè)的三?形為順時(shí)針則為背?。
- 當(dāng)觀察者在左側(cè)時(shí)碑幅,則左邊的三?形為逆時(shí)針?向判定為正?戴陡,?右側(cè)的三?形為順時(shí)針判定為背?。
-
總結(jié)
- 正?和背?是由三?形的頂點(diǎn)定義順序和觀察者?向共同決定的沟涨。隨著觀察者的?度?向的改變恤批,正?背?也會(huì)跟著改變。
總之裹赴,對(duì)正面和背面三角形進(jìn)行區(qū)分的原因之一就是為了剔除喜庞。背面剔除能夠極大地提高性能诀浪,這種方式是非常高效的,在渲染的圖元裝配階段就整體拋棄了一些三角形延都,并且沒有執(zhí)行任何不恰當(dāng)?shù)墓鈻呕僮骼字怼R话阏趁嫣蕹慈缦路绞介_啟:
開啟表?剔除(默認(rèn)背?剔除)
void glEnable(GL_CULL_FACE);
關(guān)閉表?剔除(默認(rèn)背?剔除)
void glDisable(GL_CULL_FACE);
?戶選擇剔除那個(gè)?(正?/背?)
void glCullFace(GLenum mode);
mode參數(shù)為: GL_FRONT,GL_BACK,GL_FRONT_AND_BACK ,默認(rèn)GL_BACK
?戶指定繞序那個(gè)為正?
void glFrontFace(GLenum mode);
mode參數(shù)為: GL_CW,GL_CCW,默認(rèn)值:GL_CCW
例如,剔除正?實(shí)現(xiàn)(1)
glCullFace(GL_BACK);
glFrontFace(GL_CW);
例如,剔除正?實(shí)現(xiàn)(2)
glCullFace(GL_FRONT);
然后右鍵單擊示例程序,并選擇“開啟背面剔除”菜單選項(xiàng)晰房。所以接下來我們講到另一種高效消除隱藏表面的技術(shù),深度測(cè)試嫉你。
深度測(cè)試
什么是深度?
深度其實(shí)就是該像素點(diǎn)在3D世界中距離攝像機(jī)的距離,z值
什么是深度緩沖區(qū)?
深度緩存區(qū)躏惋,就是?塊內(nèi)存區(qū)域幽污,專?存儲(chǔ)著每個(gè)像素點(diǎn)(繪制在屏幕上的)深度值。深度值(z值)越?簿姨,則離攝像機(jī)就越遠(yuǎn)距误。
為什么需要深度緩沖區(qū)?
在不使?深度測(cè)試的時(shí)候,如果我們先繪制?個(gè)距離?較近的物理扁位,再繪制距離較遠(yuǎn)的物理准潭,則距離遠(yuǎn)的位圖因?yàn)楹罄L制,會(huì)把距離近的物體覆蓋掉。 有了深度緩沖區(qū)后域仇,繪制物體的順序就不那么重要的刑然。 實(shí)際上,只要存在深度緩沖區(qū)暇务,OpenGL 都會(huì)把像素的深度值寫?到緩沖區(qū)中泼掠。除?調(diào)用glDepthMask(GL_FALSE)來禁?寫?。
在繪制一個(gè)像素時(shí)垦细,將一個(gè)值(稱為z值)分配給它择镇,這個(gè)值表示它到觀察者的距離。然后括改,當(dāng)另外一個(gè)像素需要在屏幕上的同樣位置進(jìn)行繪制時(shí)腻豌,新像素的z值將已經(jīng)存儲(chǔ)的像素的z值進(jìn)行比較。如果新像素的z值比較大嘱能,那么它距離觀察者就比較近吝梅,這樣就在原來的像素上面,所以原來的像素就會(huì)被新的像素覆蓋焰檩。如果新像素的z值更低憔涉,那么它就必須位于原來像素的后面,不能遮住原來的像素析苫。在內(nèi)部兜叨,這個(gè)任務(wù)是通過深度緩沖區(qū)實(shí)現(xiàn)的穿扳,它存儲(chǔ)了屏幕上每個(gè)像素的深度值。這就是深度測(cè)試国旷。
深度緩沖區(qū)(DepthBuffer)和顏?緩存區(qū)(ColorBuffer)是對(duì)應(yīng)的矛物。顏?緩存區(qū)存儲(chǔ)像素的顏?信息,?深度緩沖區(qū)存儲(chǔ)像素的深度信息跪但。在決定是否繪制?個(gè)物體表?時(shí)履羞, ?先要將表?對(duì)應(yīng)的像素的深度值與當(dāng)前深度緩沖區(qū)中的值進(jìn)??較。如果?于深度緩沖區(qū)中的值屡久,則丟棄這部分忆首。否則利?這個(gè)像素對(duì)應(yīng)的深度值和顏?值。分別更新深度緩沖區(qū)和顏?緩存區(qū)被环。這個(gè)過程稱為”深度測(cè)試”糙及。
深度測(cè)試在繪制多個(gè)對(duì)象時(shí)能夠進(jìn)一步解決性能問題。就算背面剔除能夠消除位于對(duì)象背面的三角形筛欢,那么如果是重疊的獨(dú)立對(duì)象又該怎么辦呢浸锨?這就會(huì)出現(xiàn)圖6所示的問題,開啟深度測(cè)試將消除那些應(yīng)該被已存在像素覆蓋的像素版姑,這將節(jié)省可觀的存儲(chǔ)器帶寬柱搜。一般深度測(cè)試按如下方式開啟:
申請(qǐng)一個(gè)顏色緩沖區(qū)和一個(gè)深度緩沖區(qū)
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH | GLUT_STENCIL);
開啟深度測(cè)試
glEnable(GL_DEPTH_TEST);
在繪制場(chǎng)景前,清除顏?緩存區(qū),深度緩沖區(qū)
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
然后右鍵單擊示例程序,并選擇“開啟深度測(cè)試”菜單選項(xiàng)剥险。多邊形模式
多邊形(含三角形)不一定是實(shí)心的聪蘸。在默認(rèn)情況下,多邊形是作為實(shí)心圖形繪制的表制,但我們可以通過將多邊形指定為顯示輪廓或只有點(diǎn)(只顯示頂點(diǎn))來改變這種行為宇姚。可以在多邊形的兩面都應(yīng)用這個(gè)渲染模式夫凸,也可以只在正面或背面應(yīng)用浑劳。
void glPolygonMode(GLenum face,GLenum mode);
和表面剔除一樣,face參數(shù)的可用值為GL_FRONT夭拌、GL_BACK或GL_FRONT_AND_BACK魔熏。
mode參數(shù)的可用值為GL_FILL(默認(rèn)值)、GL_LINE或GL_POINT鸽扁。
然后右鍵單擊示例程序蒜绽,并選擇“設(shè)置線模式”菜單選項(xiàng)。z沖突(z-fighting)
- 為什么會(huì)出現(xiàn)z沖突問題
- 因?yàn)殚_啟深度測(cè)試后,OpenGL 就不會(huì)再去繪制模型被遮擋的部分骡和。這樣實(shí)現(xiàn)的顯示更加真實(shí)相赁。但是由于深度緩沖區(qū)精度的限制對(duì)于深度相差?常?的情況下相寇。(例如在同?平?上進(jìn)?2次繪制),OpenGL 就可能出現(xiàn)不能正確判斷兩者的深度值钮科,會(huì)導(dǎo)致深度測(cè)試的結(jié)果不可預(yù)測(cè)唤衫。顯示出來的現(xiàn)象時(shí)交錯(cuò)閃爍,前?2個(gè)畫?交錯(cuò)出現(xiàn)绵脯。
- 第?步: 啟? Polygon Offset ?式解決
- 解決?法:讓深度值之間產(chǎn)?間隔佳励。如果2個(gè)圖形之間有間隔,是不是意味著就不會(huì)產(chǎn)??涉蛆挫≡叱校可以理解為在執(zhí)?深度測(cè)試前將??體的深度值做?些細(xì)微的增加。于是就能將重疊的2個(gè)圖形深度值與之前有所區(qū)分悴侵。
//啟?Polygon Offset ?式
glEnable(GL_POLYGON_OFFSET_FILL)
參數(shù)列表:
GL_POLYGON_OFFSET_POINT 對(duì)應(yīng)光柵化模式: GL_POINT
GL_POLYGON_OFFSET_LINE 對(duì)應(yīng)光柵化模式: GL_LINE
GL_POLYGON_OFFSET_FILL 對(duì)應(yīng)光柵化模式: GL_FILL
- 第?步: 指定偏移量
- 通過glPolygonOffset 來指定楣导。glPolygonOffset 需要2個(gè)參數(shù): factor ,units
- 每個(gè)Fragment 的深度值都會(huì)增加如下所示的偏移量:
Offset = ( m * factor ) + ( r * units);
m : 多邊形的深度的斜率的最?值,理解?個(gè)多邊形越是與近裁剪?平?畜挨,m就越接近于0。
r : 能產(chǎn)?于窗?坐標(biāo)系的深度值中可分辨的差異最?值噩凹。r 是由具體是由具體OpenGL 平臺(tái)指定的?個(gè)常量巴元。 - ?個(gè)?于0的Offset 會(huì)把模型推到離你(攝像機(jī))更遠(yuǎn)的位置,相應(yīng)的?個(gè)?于0的Offset 會(huì)把模型拉近
- ?般??驮宴,只需要將-1.0 和 -1 這樣簡(jiǎn)單賦值給glPolygonOffset 基本可以滿?需求逮刨。
void glPolygonOffset(Glfloat factor,Glfloat units);
應(yīng)?到?段上總偏移計(jì)算?程式
Depth Offset = (DZ * factor) + (r * units);
DZ:深度值(Z值)
r:使得深度緩沖區(qū)產(chǎn)?變化的最?值
負(fù)值,將使得z值距離我們更近堵泽,?正值修己,將使得z值距離我們更遠(yuǎn),一般把factor和units設(shè)置為-1迎罗,-1即可
- 第三步: 關(guān)閉Polygon Offset
glDisable(GL_POLYGON_OFFSET_FILL)
示例程序的代碼如下:
//GLTool.h頭?件包含了?部分GLTool中類似C語?的獨(dú)?函數(shù)
#include "GLTools.h"
//矩陣的?具類.可以利于GLMatrixStack 加載單元矩陣/矩陣相乘/壓棧/出棧/縮放/平移/旋轉(zhuǎn)
#include "GLMatrixStack.h"
//矩陣?具類,表示位置.通過設(shè)置vOrigin, vForward ,vUp
#include "GLFrame.h"
//矩陣?具類,?來快速設(shè)置正/透視投影矩陣.完成坐標(biāo)從3D->2D映射過程.
#include "GLFrustum.h"
//三?形批次類,幫助類,利?它可以傳輸頂點(diǎn)/光照/紋理/顏?數(shù)據(jù)到存儲(chǔ)著?器中.
#include "GLBatch.h"
//變換管道類,?來快速在代碼中傳輸視圖矩陣/投影矩陣/視圖投影變換矩陣等.
#include "GLGeometryTransform.h"
//數(shù)學(xué)庫
#include <math.h>
//在Mac 系統(tǒng)下睬愤,`#include<glut/glut.h>` 在Windows 和 Linux上,我們使?freeglut的靜態(tài)庫版本并且需要添加?個(gè)宏
#ifdef __APPLE__
#include <glut/glut.h>
#else
#define FREEGLUT_STATIC
#include <GL/glut.h>
#endif
//設(shè)置觀察者視圖坐標(biāo)
GLFrame viewFrame;
//設(shè)置圖元繪制時(shí)的投影?式.
GLFrustum viewFrustum;
//幫助類/容器類
GLTriangleBatch torusBatch;
//模型視圖矩陣
GLMatrixStack modelViewMatrix;
//投影矩陣
GLMatrixStack projectionMatrix;
//變換管道,存儲(chǔ)投影/視圖/投影視圖變換矩陣
GLGeometryTransform transformPipeline;
//存儲(chǔ)著?器管理?具類.
GLShaderManager shaderManager;
//標(biāo)記:背?剔除纹安、深度測(cè)試
int iCull = 0;
int iDepth = 0;
//ChangeSize 函數(shù):?定義函數(shù).通過glutReshaperFunc(函數(shù)名)注冊(cè)為重塑函數(shù).當(dāng)屏幕??發(fā)?變化/或者第?次創(chuàng)建窗?時(shí),會(huì)調(diào)?該函數(shù)調(diào)整窗???/視???.
void ChangeSize(int w ,int h)
{
//設(shè)置視口窗口尺寸
glViewport(0, 0, w, h);
//創(chuàng)建投影矩陣尤辱,并將它載?投影矩陣堆棧中
viewFrustum.SetPerspective(35.0f, float(w)/float(h), 1.0f, 500.0f);
projectionMatrix.LoadMatrix(viewFrustum.GetProjectionMatrix());
// 初始化渲染管線
transformPipeline.SetMatrixStacks(modelViewMatrix, projectionMatrix);
}
//RenderScene 函數(shù):?定義函數(shù).通過glutDisplayFunc(函數(shù)名)注冊(cè)為顯示渲染函數(shù).當(dāng)屏幕發(fā)?變化/或者開發(fā)者主動(dòng)渲染會(huì)調(diào)?此函數(shù),?來實(shí)現(xiàn)數(shù)據(jù)->渲染過程
void RenderScene(void)
{
//清理緩存區(qū)(顏?,深度,模板緩存區(qū)等)
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT);
//根據(jù)設(shè)置iCull標(biāo)記來判斷是否開啟背?剔除
if (iCull)
{
glEnable(GL_CULL_FACE);
glFrontFace(GL_CCW);
glCullFace(GL_BACK);
}
else
{
glDisable(GL_CULL_FACE);
}
//根據(jù)設(shè)置iDepth標(biāo)記來判斷是否開啟深度測(cè)試
if (iDepth)
{
glEnable(GL_DEPTH_TEST);
}
else
{
glDisable(GL_DEPTH_TEST);
}
//把攝像機(jī)矩陣壓?模型矩陣中
modelViewMatrix.PushMatrix(viewFrame);
GLfloat vRed[] = {1.0f,0.0f,0.0f,1.0f};
//使用默認(rèn)光源著色器
//通過光源、陰影效果跟提現(xiàn)立體效果
//參數(shù)1:GLT_SHADER_DEFAULT_LIGHT 默認(rèn)光源著色器
//參數(shù)2:模型視圖矩陣
//參數(shù)3:投影矩陣
//參數(shù)4:基本顏色值
shaderManager.UseStockShader(GLT_SHADER_DEFAULT_LIGHT,transformPipeline.GetModelViewMatrix(),transformPipeline.GetProjectionMatrix(),vRed);
//繪制
torusBatch.Draw();
//繪制完畢則還原矩陣
modelViewMatrix.PopMatrix();
//交換緩存區(qū)
glutSwapBuffers();
}
//SetupRC 函數(shù): ?定義函數(shù),設(shè)置你需要渲染的圖形的相關(guān)頂點(diǎn)數(shù)據(jù)/顏?數(shù)據(jù)等數(shù)據(jù)裝備?作
void SetupRC()
{
//背景顏?
glClearColor(0.3f, 0.3f, 0.3f, 1.0f);
//存儲(chǔ)著?器管理器初始化
shaderManager.InitializeStockShaders();
//將相機(jī)向后移動(dòng)7個(gè)單元:?眼到物體之間的距離
viewFrame.MoveForward(7.0f);
//創(chuàng)建?個(gè)甜甜圈
//void gltMakeTorus(GLTriangleBatch& torusBatch, GLfloat majorRadius, GLfloat minorRadius, GLint numMajor, GLint numMinor);
//參數(shù)1:GLTriangleBatch 容器幫助類
//參數(shù)2:外邊緣半徑
//參數(shù)3:內(nèi)邊緣半徑
//參數(shù)4厢岂、5:主半徑和從半徑的細(xì)分單元數(shù)量
gltMakeTorus(torusBatch, 1.0f, 0.3f, 52, 26);
//點(diǎn)的??
glPointSize(4.0f);
}
//鍵位設(shè)置光督,通過不同的鍵位對(duì)其進(jìn)?設(shè)置
//控制Camera的移動(dòng),從?改變視?
void SpecialKeys(int key, int x, int y)
{
if (key == GLUT_KEY_UP)
{
viewFrame.RotateWorld(m3dDegToRad(-5.0f), 1.0f, 0.0f, 0.0f);
}
if (key == GLUT_KEY_DOWN)
{
viewFrame.RotateWorld(m3dDegToRad(5.0f), 1.0f, 0.0f, 0.0f);
}
if (key == GLUT_KEY_LEFT)
{
viewFrame.RotateWorld(m3dDegToRad(-5.0f), 0.0f, 1.0f, 0.0f);
}
if (key == GLUT_KEY_RIGHT)
{
viewFrame.RotateWorld(m3dDegToRad(5.0f), 0.0f, 1.0f, 0.0f);
}
glutPostRedisplay();
}
//右鍵菜單欄選項(xiàng)
void ProcessMenu(int value)
{
switch (value)
{
case 1:
iDepth = !iDepth;
break;
case 2:
iCull = !iCull;
break;
case 3:
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
break;
case 4:
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
break;
case 5:
glPolygonMode(GL_FRONT_AND_BACK, GL_POINT);
break;
}
glutPostRedisplay();
}
//main 函數(shù): 程序??.OpenGL 是?向過程編程.所以你會(huì)發(fā)現(xiàn)利?OpenGL處理圖形/圖像都是鏈?zhǔn)叫问?以及基于OpenGL封裝的圖像處理框架也是鏈?zhǔn)骄幊?int main(int argc, char* argv[])
{
glutInit(&argc, argv);
//申請(qǐng)一個(gè)顏色緩存區(qū)塔粒、深度緩存區(qū)结借、雙緩存區(qū)、模板緩存區(qū)
glutInitDisplayMode(GLUT_DOUBLE|GLUT_RGBA|GLUT_DEPTH|GLUT_STENCIL);
//設(shè)置窗口的尺寸
glutInitWindowSize(800, 800);
//設(shè)置窗口的名稱
glutCreateWindow("OpenGL 渲染技巧");
//注冊(cè)回調(diào)函數(shù)(改變尺寸)
glutReshapeFunc(ChangeSize);
//注冊(cè)顯示函數(shù)
glutDisplayFunc(RenderScene);
//特殊鍵位函數(shù)(上下左右)
glutSpecialFunc(SpecialKeys);
//右擊菜單
glutCreateMenu(ProcessMenu);
glutAddMenuEntry("開啟深度測(cè)試",1);
glutAddMenuEntry("開啟背面剔除",2);
glutAddMenuEntry("設(shè)置面模式", 3);
glutAddMenuEntry("設(shè)置線模式", 4);
glutAddMenuEntry("設(shè)置點(diǎn)模式", 5);
glutAttachMenu(GLUT_RIGHT_BUTTON);
//判斷一下是否能初始化glew庫卒茬,確保項(xiàng)目能正常使用OpenGL 框架
GLenum err = glewInit();
if (GLEW_OK != err) {
fprintf(stderr, "GLEW Error: %s\n", glewGetErrorString(err));
return 1;
}
//設(shè)置繪制數(shù)據(jù)
SetupRC();
//runloop運(yùn)行循環(huán)
glutMainLoop();
return 0;
}