系列推薦文章:
OpenGL/OpenGL ES入門:圖形API以及專業名詞解析
OpenGL/OpenGL ES入門:渲染流程以及固定存儲着色器
OpenGL/OpenGL ES入門:圖像渲染實現以及渲染問題
OpenGL/OpenGL ES入門:基礎變換 - 初識向量/矩陣
OpenGL/OpenGL ES入門:紋理初探 - 經常使用API解析
OpenGL/OpenGL ES入門: 紋理應用 - 紋理座標及案例解析(金字塔)
OpenGL/OpenGL ES入門: 頂點着色器與片元着色器(OpenGL過渡OpenGL ES)
OpenGL/OpenGL ES入門: GLKit以及API簡介
OpenGL/OpenGL ES入門: GLKit使用以及案例算法
先看用一個平面着色器渲染出的一個甜甜圈編程
main
函數,程序入口。因此OpenGL
處理圖形、圖像都是鏈式形式,以及基於OpenGL
封裝的圖像處理框架也是鏈式編程gltSetWorkingDirectory(argv[0]);
glutInit(&argc, argv);
// 初始化窗口
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH | GLUT_STENCIL);
glutInitWindowSize(800, 600);
glutCreateWindow("ZB");
// 註冊函數
glutReshapeFunc(ChangeSize);
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;
複製代碼
changeSize
經過glutReshapeFunc
註冊爲重塑函數,當第一次建立窗口或屏幕大小發生改變時,會調用該函數調整窗口大小/視口大小// 保證高度不能爲0
if (h == 0) {
h = 1;
}
// 將視口設置爲窗口尺寸
glViewport(0, 0, w, h);
// 建立投影矩陣,並將它載入投影矩陣堆棧中
viewFrustum.SetPerspective(35, float(w)/float(h), 1, 1000);
projectionMatrix.LoadMatrix(viewFrustum.GetProjectionMatrix());
// 初始化渲染管線
transformPipeline.SetMatrixStacks(modelViweMatix, projectionMatrix);
複製代碼
SetupRC
設置須要渲染圖形相關頂點數據、顏色值等,手動在main
函數調用// 1. 設置背景色
glClearColor(0.3, 0.3, 0.3, 1);
// 2. 初始化着色器管理器
shaderManager.InitializeStockShaders();
// 3. 將相機向後移動7個單元,肉眼到物體的距離
viewFrame.MoveForward(5.0);
// 4. 建立一個甜甜圈
/**
void gltMakeTorus(GLTriangleBatch& torusBatch, GLfloat majorRadius, GLfloat minorRadius, GLint numMajor, GLint numMinor);
參數1: GLTriangleBatch 容器幫助類
參數2: 外邊緣半徑
參數3: 內邊緣半徑
參數四、5: 主半徑和從半徑的細分單元數量
*/
gltMakeTorus(torusBatch, 1, 0.3, 88, 33);
// 5. 點的大小(方便點填充時,肉眼觀察)
glPointSize(4.0);
複製代碼
RenderScene
經過glutDisplayFunc
註冊爲渲染函數。當屏幕發生變化或者開發者主動渲染會調用此函數,用來實現數據->渲染過程// 1. 清除窗口和深度緩衝區
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// 2. 把攝像機矩陣壓入模型矩陣中,壓棧 -- 存儲一個狀態
modelViweMatix.PushMatrix(viewFrame);
// 3. 設置繪圖顏色
GLfloat vRed[] = {1, 0, 0, 1};
// 4. 使用平面着色器
shaderManager.UseStockShader(GLT_SHADER_FLAT, transformPipeline.GetModelViewProjectionMatrix(), vRed);
// 5. 繪製
torusBatch.Draw();
// 6. 出棧,繪製完成恢復 出棧 -- 恢復一個狀態
modelViweMatix.PopMatrix();
// 7. 強制執行緩存區
glutSwapBuffers();
複製代碼
到這裏爲止,編譯運行就能過出現上圖所示的效果圖。利用的是平面着色器。 至關的low。緩存
下面在此基礎上進行酷炫的一波操做。bash
在main
函數中註冊了一個函數SpecialKeys
,顧名思義,特殊鍵位,這裏控制的是上下左右鍵位框架
// 1. 判斷方向
if (key == GLUT_KEY_UP) {
// 2. 根據方向調整觀察者位置
// 參數1: 旋轉的弧度
// 參數二、三、4:表示繞哪一個軸進行旋轉
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();
複製代碼
看實現效果函數
在來一波更真實的操做,咱們使用默認光源着色器來實現oop
// 1. 清除窗口和深度緩衝區
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// 2. 把攝像機矩陣壓入模型矩陣中
modelViweMatix.PushMatrix(viewFrame);
// 3. 設置繪圖顏色
GLfloat vRed[] = {1, 0, 0, 1};
// 4. 使用平面着色器
// shaderManager.UseStockShader(GLT_SHADER_FLAT, transformPipeline.GetModelViewProjectionMatrix(), vRed);
// 4.1 使用默認光源着色器
// 經過光源、陰影效果跟體現立體效果
// 參數1:GLT_SHADER_DEFAULT_LIGHT 默認光源着色器
// 參數2:模型視圖矩陣
// 參數3:投影矩陣
// 參數4:基本顏色值
shaderManager.UseStockShader(GLT_SHADER_DEFAULT_LIGHT, transformPipeline.GetModelViewMatrix(), transformPipeline.GetProjectionMatrix(), vRed);
// 5. 繪製
torusBatch.Draw();
// 6. 出棧,繪製完成恢復
modelViweMatix.PopMatrix();
// 7. 強制執行緩存區
glutSwapBuffers();
複製代碼
效果圖以下: post
能夠看出,咱們的渲染出了問題。性能
在使用默認光源着色器時,因爲產生了光照,有光照的一面,按照本來的顏色顯示,而背光面,則是黑暗的,咱們看不見的。其實很好理解,太陽光照地球,迎光面是白天,背光面是黑夜。測試
在繪製3D場景的時候,咱們須要決定哪些部分是對觀察者可見的,或者哪些部分是對觀察者不可見的,對於不可見的部分,應該及早丟棄。例如在一個不透明的牆壁後,就不該該有渲染,這種狀況叫作隱藏面消除
下面討論一下解決這個問題的方案。
先繪製場景中離觀察者較遠的物體,在繪製較近的物體,以下圖
繪製順序依次是紅、黃、灰,這樣的話按序渲染能過解決隱藏面消除的問題。
可是隨之而來的會有一些很差的問題出現
首先須要肯定一個問題,任何平面都有2個面,正面/背面,意味着你一個時刻只能看到一面。
一個立方體圖形,從任何一個方向去觀察,最多能夠看到3個面,意味着其餘看不到的面,咱們不須要去繪製它,若是能以某種方式去丟棄這部分數據,OpenGL
在渲染的性能便可提升50%。
沒錯,OpenGL
可以區別正面和背面,經過分析頂點數據的順序
正面/背面區分
立方體中的正背面
分析:
總結: 正面和背面是由三角形的頂點定義順序和觀察者方向共同決定的,隨着觀察者的角度方向的改變,正面背面也會跟着改變
相關代碼
// 開啓表面剔除(默認背面剔除)
void glEnable(GL_CULL_FACE);
// 關閉表面剔除(默認背面剔除)
void glDisable(GL_CULL_FACE);
// 用戶選擇剔除那個面(便可自定義剔除,默認爲正面)
void glCullFace(GLenum mode);
mode參數爲:GL_FRONT, GL_BACK, GL_FRONT_AND_BACK, 默認爲GL_BACK
// 用戶也能夠指定正面
void glFrontFace(GLenum mode);
mode參數爲:GL_CW, GL_CCW, 默認爲GL_CCW
// 剔除正面實現
glCullFace(GL_BACK);
glFrontFace(GL_CW);
或
glCullface(GL_FRONT);
複製代碼
具體代碼實現
// 1. 清除窗口和深度緩衝區
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// 開啓正背面剔除
glEnable(GL_CULL_FACE);
glFrontFace(GL_CCW);
glCullFace(GL_BACK);
// 2. 把攝像機矩陣壓入模型矩陣中
modelViweMatix.PushMatrix(viewFrame);
// 3. 設置繪圖顏色
GLfloat vRed[] = {1, 0, 0, 1};
// 後面代碼和上面同樣,再也不重複
複製代碼
實現效果以下圖:
能夠看到,以前的問題已經解決了,但是又面臨了一個尷尬的問題,這個甜甜圈貌似有個很大的缺口,瞭解過圖形渲染的讀者確定知道,這是深度問題,下面來了解一下。
深度就是該像素點在3D世界中距離攝像機的距離,也就是Z值。
深度緩衝區就是一塊內存區域,專門存儲着每一個像素點(繪製在屏幕上的)深度值Z。Z越大,則距離屏幕越遠。
那麼爲何須要深度緩衝區?
在不實用深度測試的時候,若是咱們先繪製一個距離比較近的物體,在繪製距離遠的物體,則距離遠的位圖由於後繪製,會把距離近的物體覆蓋掉。有了深度緩衝區後,繪製物體的順序就不那麼重要了。上面出現的大缺口,也就是這個問題形成的。
實際上,只要存在深度緩衝區,OpenGL
都會把像素的深度值寫入到緩衝區中,除非調用glDepthMask(GL_FALSE)
來禁止寫入。
深度測試
深度緩衝區和顏色緩衝區是對應的。顏色緩衝區存儲像素的顏色信息,而深度緩衝區存儲像素的深度信息。在決定是否繪製一個物體表面時,首先要將表面對應的像素的深度值與當前深度緩衝區中的值進行比較,若是大於深度緩衝區的值,則丟棄這部分,不然利用這個像素對應的深度值和顏色值,分別更新深度緩衝區和顏色緩衝區。這個過程稱爲深度測試。
相關代碼
// 開啓深度測試
glEnable(GL_DEPTH_TEST);
// 在繪製場景前,清除顏色緩衝區和深度緩衝區
glClearColor(0, 0, 0, 1);
glClear(GL_COLOR_BUFFER_BIT | GL_GEPTH_BUFFER_BIT);
複製代碼
清除深度緩衝區默認值爲1.0,表示最大的深度值,深度值的範圍爲(0,1)之間。值越小表示越靠近觀察者,反正表示距離觀察者越遠。
下面有關深度測試的判斷式
指定深度測試判斷模式
void glDepthFunc(GLEnum mode);
打開/阻斷 深度緩衝區寫入
void glDepthMask(GKBool value);
value :GL_TURE
開啓寫入GL_FALSE
關閉寫入
最終的實現效果以下:
由於開啓深度測試後,OpenGL
就不會去繪製模型被遮擋的部分,這樣實現現實更加真實,可是因爲深度緩衝區精度的限制,對於深度相差無幾的狀況下,OpenGL
就可能出現不能正確判斷二者深度值,會致使深度測試的結果不可預測,現實出來的現象會交錯閃爍。
Polygon Offset
方式解決// 啓用Polygon Offset方式
glEnable(GL_POLYGON_OFFSET_FILL);
參數列表:
GL_POLYGON_OFFSET_POINT 對應光柵化模式:GL_POINT
GL_POLYGON_OFFSET_LINE 對應光柵化模式:GL_LINE
GL_POLYGON_OFFSET_FILL 對應光柵化模式:GL_FILL
複製代碼
第二步:指定偏移量
glPolygon Offset
來指定. glPolygon Offset
須要2個參數: factor
, units
.Fragment
的深度值都會增長以下所示的偏移量:OpenGL
平臺指定的一個常量.Offset
會把模型推到離你(攝像機)更遠的位置,相應的⼀個小於0的Offset
會把模型拉近glPolygon Offset
基本能夠滿⾜足需求.第三步:關閉Polygon Offset
glDisable(GL_POLYGON_OFFSET_FILL);
複製代碼
OK,到此爲止,咱們完美的把這個甜甜圈給渲染出來了。上面遇到的一些問題也得已解決。