OpenGL/OpenGL ES 圖像渲染以及渲染問題

圖像渲染的實現(xiàn)

先看用一個平面著色器渲染出的一個甜甜圈

平面著色器渲染甜甜圈效果圖

代碼實現(xiàn):

  • main函數(shù)跺讯,程序入口。所以OpenGL處理圖形世剖、圖像都是鏈式形式间聊,以及基于OpenGL封裝的圖像處理框架也是鏈式編程
    gltSetWorkingDirectory(argv[0]);
    
    glutInit(&argc, argv);
   // 初始化窗口
    glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH | GLUT_STENCIL);
    glutInitWindowSize(800, 600);
    glutCreateWindow("ZB");
    // 注冊函數(shù)
    glutReshapeFunc(ChangeSize);
    glutSpecialFunc(SpecialKeys);
    glutDisplayFunc(RenderScene);
    
    GLenum err = glewInit();
    if (GLEW_OK != err) {
        fprintf(stderr, "GLEW Error:%s\n", glewGetErrorString(err));
        return 1;
    }
    // 主動觸發(fā),準備工作
    SetupRC();
    // 一個無限執(zhí)行的循環(huán)壳贪,負責一直處理窗口和操作系統(tǒng)的用戶輸入等操作
    glutMainLoop();
    return 0;
  • changeSize 通過glutReshapeFunc注冊為重塑函數(shù)财著,當?shù)谝淮蝿?chuàng)建窗口或屏幕大小發(fā)生改變時,會調(diào)用該函數(shù)調(diào)整窗口大小/視口大小
    // 保證高度不能為0
    if (h == 0) {
        h = 1;
    }
    
    // 將視口設(shè)置為窗口尺寸
    glViewport(0, 0, w, h);
    // 創(chuàng)建投影矩陣撑碴,并將它載入投影矩陣堆棧中
    viewFrustum.SetPerspective(35, float(w)/float(h), 1, 1000);
    projectionMatrix.LoadMatrix(viewFrustum.GetProjectionMatrix());
    
    // 初始化渲染管線
    transformPipeline.SetMatrixStacks(modelViweMatix, projectionMatrix);
  • SetupRC 設(shè)置需要渲染圖形相關(guān)頂點數(shù)據(jù)撑教、顏色值等,手動在main函數(shù)調(diào)用
    // 1. 設(shè)置背景色
    glClearColor(0.3, 0.3, 0.3, 1);
    
    // 2. 初始化著色器管理器
    shaderManager.InitializeStockShaders();
    
    // 3. 將相機向后移動7個單元醉拓,肉眼到物體的距離
    viewFrame.MoveForward(5.0);
    
    // 4. 創(chuàng)建一個甜甜圈
    /**
     void gltMakeTorus(GLTriangleBatch& torusBatch, GLfloat majorRadius, GLfloat minorRadius, GLint numMajor, GLint numMinor);
     參數(shù)1: GLTriangleBatch 容器幫助類
     參數(shù)2: 外邊緣半徑
     參數(shù)3: 內(nèi)邊緣半徑
     參數(shù)4伟姐、5: 主半徑和從半徑的細分單元數(shù)量
     */
    gltMakeTorus(torusBatch, 1, 0.3, 88, 33);
    
    // 5. 點的大小(方便點填充時亿卤,肉眼觀察)
    glPointSize(4.0);
  • RenderScene 通過glutDisplayFunc注冊為渲染函數(shù)愤兵。當屏幕發(fā)生變化或者開發(fā)者主動渲染會調(diào)用此函數(shù),用來實現(xiàn)數(shù)據(jù)->渲染過程
    // 1. 清除窗口和深度緩沖區(qū)
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    
    // 2. 把攝像機矩陣壓入模型矩陣中排吴,壓棧 -- 存儲一個狀態(tài)
    modelViweMatix.PushMatrix(viewFrame);
    
    // 3. 設(shè)置繪圖顏色
    GLfloat vRed[] = {1, 0, 0, 1};
    
    // 4. 使用平面著色器
    shaderManager.UseStockShader(GLT_SHADER_FLAT, transformPipeline.GetModelViewProjectionMatrix(), vRed);
    
    // 5. 繪制
    torusBatch.Draw();
    
    // 6. 出棧秆乳,繪制完成恢復(fù)  出棧 -- 恢復(fù)一個狀態(tài)
    modelViweMatix.PopMatrix();
    
    // 7. 強制執(zhí)行緩存區(qū)
    glutSwapBuffers();

到這里為止,編譯運行就能過出現(xiàn)上圖所示的效果圖。利用的是平面著色器屹堰。
相當?shù)膌ow肛冶。

下面在此基礎(chǔ)上進行酷炫的一波操作。

main函數(shù)中注冊了一個函數(shù)SpecialKeys扯键,顧名思義睦袖,特殊鍵位,這里控制的是上下左右鍵位

    // 1. 判斷方向
    if (key == GLUT_KEY_UP) {
        // 2. 根據(jù)方向調(diào)整觀察者位置
        // 參數(shù)1: 旋轉(zhuǎn)的弧度
        // 參數(shù)2荣刑、3馅笙、4:表示繞哪個軸進行旋轉(zhuǎn)
        viewFrame.RotateWorld(m3dDegToRad(-5), 1, 0, 0);
    }
    if (key == GLUT_KEY_DOWN) {
        viewFrame.RotateWorld(m3dDegToRad(5), 1, 0, 0);
    }
    if (key == GLUT_KEY_LEFT) {
        viewFrame.RotateWorld(m3dDegToRad(-5), 0, 1, 0);
    }
    if (key == GLUT_KEY_RIGHT) {
        viewFrame.RotateWorld(m3dDegToRad(5), 0, 1, 0);
    }
    // 3. 重新刷新
    glutPostRedisplay();

看實現(xiàn)效果

能夠旋轉(zhuǎn)的甜甜圈

在來一波更真實的操作,我們使用默認光源著色器來實現(xiàn)

    // 1. 清除窗口和深度緩沖區(qū)
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    
    // 2. 把攝像機矩陣壓入模型矩陣中
    modelViweMatix.PushMatrix(viewFrame);
    
    // 3. 設(shè)置繪圖顏色
    GLfloat vRed[] = {1, 0, 0, 1};
    
    // 4. 使用平面著色器
//    shaderManager.UseStockShader(GLT_SHADER_FLAT, transformPipeline.GetModelViewProjectionMatrix(), vRed);
    
    // 4.1 使用默認光源著色器
    // 通過光源厉亏、陰影效果跟體現(xiàn)立體效果
    // 參數(shù)1:GLT_SHADER_DEFAULT_LIGHT 默認光源著色器
    // 參數(shù)2:模型視圖矩陣
    // 參數(shù)3:投影矩陣
    // 參數(shù)4:基本顏色值
    shaderManager.UseStockShader(GLT_SHADER_DEFAULT_LIGHT, transformPipeline.GetModelViewMatrix(), transformPipeline.GetProjectionMatrix(), vRed);
    
    // 5. 繪制
    torusBatch.Draw();
    
    // 6. 出棧董习,繪制完成恢復(fù)
    modelViweMatix.PopMatrix();
    
    // 7. 強制執(zhí)行緩存區(qū)
    glutSwapBuffers();

效果圖如下:


未正背面剔除的渲染

可以看出,我們的渲染出了問題爱只。

問題分析

在使用默認光源著色器時阱飘,由于產(chǎn)生了光照,有光照的一面虱颗,按照原本的顏色顯示,而背光面蔗喂,則是黑暗的忘渔,我們看不見的。其實很好理解缰儿,太陽光照地球畦粮,迎光面是白天,背光面是黑夜乖阵。

在繪制3D場景的時候宣赔,我們需要決定哪些部分是對觀察者可見的,或者哪些部分是對觀察者不可見的瞪浸,對于不可見的部分儒将,應(yīng)該及早丟棄。例如在一個不透明的墻壁后对蒲,就不應(yīng)該有渲染钩蚊,這種情況叫做隱藏面消除

下面討論一下解決這個問題的方案。

解決問題的方案

油畫算法

先繪制場景中離觀察者較遠的物體蹈矮,在繪制較近的物體砰逻,如下圖


油畫算法

繪制順序依次是紅、黃泛鸟、灰蝠咆,這樣的話按序渲染能過解決隱藏面消除的問題。

但是隨之而來的會有一些不好的問題出現(xiàn)

  • 效率很低,重疊部分會進行多次繪制渲染刚操,浪費資源
  • 對于某些存在場景闸翅,無法區(qū)別遠近順序的,無法用該方法解決問題赡茸,如下圖
無法區(qū)別遠近

正背面剔除

首先需要確定一個問題缎脾,任何平面都有2個面,正面/背面占卧,意味著你一個時刻只能看到一面遗菠。

一個立方體圖形,從任何一個方向去觀察华蜒,最多可以看到3個面辙纬,意味著其他看不到的面,我們不需要去繪制它叭喜,如果能以某種方式去丟棄這部分數(shù)據(jù)贺拣,OpenGL在渲染的性能即可提高50%。

沒錯捂蕴,OpenGL能夠區(qū)別正面和背面譬涡,通過分析頂點數(shù)據(jù)的順序

OpenGL區(qū)別正背面

正面/背面區(qū)分

  • 正面:按照逆時針頂點鏈接順序的三角形面
  • 背面:按照順時針頂點連接順序的三角形面

立方體中的正背面


立方體中的正背面

分析:

  • 左側(cè)三角形頂點順序為:1->2->3; 右側(cè)三角形的頂點順序為:1->2->3
  • 當觀察者在右側(cè)時,則右邊的三角形方向為逆時針方向為正面啥辨,而左側(cè)的三角形為順時針則為反面
  • 當觀察者在左側(cè)時,則左邊的三?形方向為逆時針?方向為正面,?右側(cè)的三角形為順時針則為背面

總結(jié):
正面和背面是由三角形的頂點定義順序和觀察者方向共同決定的涡匀,隨著觀察者的角度方向的改變,正面背面也會跟著改變

相關(guān)代碼

// 開啟表面剔除(默認背面剔除)
void glEnable(GL_CULL_FACE);

// 關(guān)閉表面剔除(默認背面剔除)
void glDisable(GL_CULL_FACE);

// 用戶選擇剔除那個面(即可自定義剔除溉知,默認為正面)
void glCullFace(GLenum mode);
mode參數(shù)為:GL_FRONT, GL_BACK, GL_FRONT_AND_BACK, 默認為GL_BACK

// 用戶也可以指定正面
void glFrontFace(GLenum mode);
mode參數(shù)為:GL_CW, GL_CCW, 默認為GL_CCW

// 剔除正面實現(xiàn)
glCullFace(GL_BACK);
glFrontFace(GL_CW);
或
glCullface(GL_FRONT);

具體代碼實現(xiàn)

    // 1. 清除窗口和深度緩沖區(qū)
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    
    // 開啟正背面剔除
    glEnable(GL_CULL_FACE);
    glFrontFace(GL_CCW);
    glCullFace(GL_BACK);
    
    // 2. 把攝像機矩陣壓入模型矩陣中
    modelViweMatix.PushMatrix(viewFrame);
    
    // 3. 設(shè)置繪圖顏色
    GLfloat vRed[] = {1, 0, 0, 1};
    
    // 后面代碼和上面一樣陨瘩,不再重復(fù)

實現(xiàn)效果如下圖:

未進行深度測試的甜甜圈

可以看到,之前的問題已經(jīng)解決了级乍,可是又面臨了一個尷尬的問題舌劳,這個甜甜圈貌似有個很大的缺口,了解過圖形渲染的讀者肯定知道玫荣,這是深度問題甚淡,下面來了解一下。

深度

深度就是該像素點在3D世界中距離攝像機的距離捅厂,也就是Z值材诽。
深度緩存區(qū)就是一塊內(nèi)存區(qū)域,專門存儲著每個像素點(繪制在屏幕上的)深度值Z恒傻。Z越大脸侥,則距離屏幕越遠。

那么為什么需要深度緩沖區(qū)盈厘?
在不實用深度測試的時候睁枕,如果我們先繪制一個距離比較近的物體,在繪制距離遠的物體,則距離遠的位圖因為后繪制外遇,會把距離近的物體覆蓋掉注簿。有了深度緩沖區(qū)后,繪制物體的順序就不那么重要了跳仿。上面出現(xiàn)的大缺口诡渴,也就是這個問題造成的。

實際上菲语,只要存在深度緩沖區(qū)妄辩,OpenGL都會把像素的深度值寫入到緩沖區(qū)中,除非調(diào)用glDepthMask(GL_FALSE)來禁止寫入山上。

深度測試
深度緩沖區(qū)和顏色緩存區(qū)是對應(yīng)的眼耀。顏色緩存區(qū)存儲像素的顏色信息,而深度緩沖區(qū)存儲像素的深度信息佩憾。在決定是否繪制一個物體表面時哮伟,首先要將表面對應(yīng)的像素的深度值與當前深度緩存區(qū)中的值進行比較,如果大雨深度緩存區(qū)的值妄帘,則丟棄這部分楞黄,否則利用這個像素對應(yīng)的深度值和顏色值,分別更新深度緩存區(qū)和顏色緩存區(qū)抡驼。這個過程稱為深度測試鬼廓。

相關(guān)代碼

// 開啟深度測試
glEnable(GL_DEPTH_TEST);

// 在繪制場景前,清除顏色緩存區(qū)和深度緩沖區(qū)
glClearColor(0, 0, 0, 1);
glClear(GL_COLOR_BUFFER_BIT | GL_GEPTH_BUFFER_BIT);

清除深度緩沖區(qū)默認值為1.0婶恼,表示最大的深度值,深度值的范圍為(0,1)之間柏副。值越小表示越靠近觀察者勾邦,反正表示距離觀察者越遠。

下面有關(guān)深度測試的判斷式

指定深度測試判斷模式
void glDepthFunc(GLEnum mode);
打開/阻斷 深度緩存區(qū)寫入
void glDepthMask(GKBool value);
value : GL_TURE 開啟寫入 GL_FALSE 關(guān)閉寫入

深度測試判斷模式

最終的實現(xiàn)效果如下:

最終效果圖

ZFighting閃爍問題

為什么會出現(xiàn)ZFighting閃爍問題

因為開啟深度測試后割择,OpenGL就不會去繪制模型被遮擋的部分眷篇,這樣實現(xiàn)現(xiàn)實更加真實,但是由于深度緩存區(qū)精度的限制荔泳,對于深度相差無幾的情況下蕉饼,OpenGL就可能出現(xiàn)不能正確判斷兩者深度值,會導(dǎo)致深度測試的結(jié)果不可預(yù)測玛歌,現(xiàn)實出來的現(xiàn)象會交錯閃爍昧港。

深度相差無幾

解決方式

  • 第一步:啟用Polygon Offset方式解決
    讓深度值之間產(chǎn)生間隔,可以理解為在執(zhí)行深度測試前支子,將立方體的深度值做一些細微的增加创肥,于是就能將重疊的2個圖形深度值之間有所區(qū)分。
// 啟用Polygon Offset方式
glEnable(GL_POLYGON_OFFSET_FILL);

參數(shù)列表:
GL_POLYGON_OFFSET_POINT 對應(yīng)光柵化模式:GL_POINT
GL_POLYGON_OFFSET_LINE 對應(yīng)光柵化模式:GL_LINE
GL_POLYGON_OFFSET_FILL 對應(yīng)光柵化模式:GL_FILL

  • 第二步:指定偏移量

    • 通過glPolygon Offset 來指定. glPolygon Offset需要2個參數(shù): factor , units.
    • 每個Fragment 的深度值都會增加如下所示的偏移量:
      Offset = ( m * factor ) + ( r * units);
      m : 多邊形的深度的斜率的最大值,理解一個多邊形越是與近裁剪?平行叹侄,m就越接近于0.
      r : 能產(chǎn)生于窗口坐標系的深度值中可分辨的差異最小值.r是由具體是由具體OpenGL平臺指定的一個常量.
    • 一個?于0的Offset會把模型推到離你(攝像機)更遠的位置巩搏,相應(yīng)的?個小于0的Offset 會把模型拉近
    • 一般?言,只需要將-1.0 和 -1 這樣簡單賦值給glPolygon Offset 基本可以滿?足需求.
  • 第三步:關(guān)閉Polygon Offset

glDisable(GL_POLYGON_OFFSET_FILL);

OK趾代,到此為止贯底,我們完美的把這個甜甜圈給渲染出來了。上面遇到的一些問題也得已解決撒强。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末禽捆,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子尿褪,更是在濱河造成了極大的恐慌睦擂,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件杖玲,死亡現(xiàn)場離奇詭異顿仇,居然都是意外死亡,警方通過查閱死者的電腦和手機摆马,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進店門臼闻,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人囤采,你說我怎么就攤上這事述呐。” “怎么了蕉毯?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵乓搬,是天一觀的道長。 經(jīng)常有香客問我代虾,道長进肯,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任棉磨,我火速辦了婚禮江掩,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘乘瓤。我一直安慰自己环形,他們只是感情好,可當我...
    茶點故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布衙傀。 她就那樣靜靜地躺著抬吟,像睡著了一般。 火紅的嫁衣襯著肌膚如雪统抬。 梳的紋絲不亂的頭發(fā)上拗军,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天任洞,我揣著相機與錄音,去河邊找鬼发侵。 笑死交掏,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的刃鳄。 我是一名探鬼主播盅弛,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼叔锐!你這毒婦竟也來了挪鹏?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤愉烙,失蹤者是張志新(化名)和其女友劉穎讨盒,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體步责,經(jīng)...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡返顺,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了蔓肯。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片遂鹊。...
    茶點故事閱讀 39,690評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖蔗包,靈堂內(nèi)的尸體忽然破棺而出秉扑,到底是詐尸還是另有隱情,我是刑警寧澤调限,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布舟陆,位于F島的核電站,受9級特大地震影響耻矮,放射性物質(zhì)發(fā)生泄漏秦躯。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一淘钟、第九天 我趴在偏房一處隱蔽的房頂上張望宦赠。 院中可真熱鬧陪毡,春花似錦米母、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至桅滋,卻和暖如春慧耍,著一層夾襖步出監(jiān)牢的瞬間身辨,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工芍碧, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留煌珊,地道東北人。 一個月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓泌豆,卻偏偏與公主長得像定庵,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子踪危,可洞房花燭夜當晚...
    茶點故事閱讀 44,577評論 2 353

推薦閱讀更多精彩內(nèi)容