OpenGL(6) —— 繪制一個立方體,面剔除和深度測試

旋轉(zhuǎn)的彩色立方體

代碼實踐

定義物體的顏色和局部空間中的坐標

首先我們定義六個面的頂點坐標,每個面由兩個三角形組成。

// 假設(shè)攝像機是由 Z 軸上方往下看
final float cubePosition[] = {
        // Front face(由兩個三角形的坐標定義)
        -1.0f, 1.0f, 1.0f,
        -1.0f, -1.0f, 1.0f,
        1.0f, 1.0f, 1.0f,
        -1.0f, -1.0f, 1.0f,
        1.0f, -1.0f, 1.0f,
        1.0f, 1.0f, 1.0f,
        // Right face
        1.0f, 1.0f, 1.0f,
        1.0f, -1.0f, 1.0f,
        1.0f, 1.0f, -1.0f,
        1.0f, -1.0f, 1.0f,
        1.0f, -1.0f, -1.0f,
        1.0f, 1.0f, -1.0f,
        // Back face
        1.0f, 1.0f, -1.0f,
        1.0f, -1.0f, -1.0f,
        -1.0f, 1.0f, -1.0f,
        1.0f, -1.0f, -1.0f,
        -1.0f, -1.0f, -1.0f,
        -1.0f, 1.0f, -1.0f,
        // Left face
        -1.0f, 1.0f, -1.0f,
        -1.0f, -1.0f, -1.0f,
        -1.0f, 1.0f, 1.0f,
        -1.0f, -1.0f, -1.0f,
        -1.0f, -1.0f, 1.0f,
        -1.0f, 1.0f, 1.0f,
        // Top face
        -1.0f, 1.0f, -1.0f,
        -1.0f, 1.0f, 1.0f,
        1.0f, 1.0f, -1.0f,
        -1.0f, 1.0f, 1.0f,
        1.0f, 1.0f, 1.0f,
        1.0f, 1.0f, -1.0f,
        // Bottom face
        1.0f, -1.0f, -1.0f,
        1.0f, -1.0f, 1.0f,
        -1.0f, -1.0f, -1.0f,
        1.0f, -1.0f, 1.0f,
        -1.0f, -1.0f, 1.0f,
        -1.0f, -1.0f, -1.0f,
};

然后我們再給每個頂點定義顏色:

final float[] cubeColor = {
        // Front face (red)
        0.0f, 1.0f, 0.0f, 1.0f, // 這個頂點的顏色與該面其他頂點顏色不一樣
        1.0f, 0.0f, 0.0f, 1.0f,
        1.0f, 0.0f, 0.0f, 1.0f,
        1.0f, 0.0f, 0.0f, 1.0f,
        1.0f, 0.0f, 0.0f, 1.0f,
        1.0f, 0.0f, 0.0f, 1.0f,
        // Right face (green)
        0.0f, 1.0f, 0.0f, 1.0f,
        0.0f, 1.0f, 0.0f, 1.0f,
        0.0f, 1.0f, 0.0f, 1.0f,
        0.0f, 1.0f, 0.0f, 1.0f,
        0.0f, 1.0f, 0.0f, 1.0f,
        0.0f, 1.0f, 0.0f, 1.0f,
        // Back face (blue)
        0.0f, 0.0f, 1.0f, 1.0f,
        0.0f, 0.0f, 1.0f, 1.0f,
        0.0f, 0.0f, 1.0f, 1.0f,
        0.0f, 0.0f, 1.0f, 1.0f,
        0.0f, 0.0f, 1.0f, 1.0f,
        0.0f, 0.0f, 1.0f, 1.0f,
        // Left face (yellow)
        1.0f, 1.0f, 0.0f, 1.0f,
        1.0f, 1.0f, 0.0f, 1.0f,
        1.0f, 1.0f, 0.0f, 1.0f,
        1.0f, 1.0f, 0.0f, 1.0f,
        1.0f, 1.0f, 0.0f, 1.0f,
        1.0f, 1.0f, 0.0f, 1.0f,
        // Top face (cyan)
        0.0f, 1.0f, 1.0f, 1.0f,
        0.0f, 1.0f, 1.0f, 1.0f,
        0.0f, 1.0f, 1.0f, 1.0f,
        0.0f, 1.0f, 1.0f, 1.0f,
        0.0f, 1.0f, 1.0f, 1.0f,
        0.0f, 1.0f, 1.0f, 1.0f,
        // Bottom face (magenta)
        1.0f, 0.0f, 1.0f, 1.0f,
        1.0f, 0.0f, 1.0f, 1.0f,
        1.0f, 0.0f, 1.0f, 1.0f,
        1.0f, 0.0f, 1.0f, 1.0f,
        1.0f, 0.0f, 1.0f, 1.0f,
        1.0f, 0.0f, 1.0f, 1.0f
};

我們讓 front face 其中有一個頂點顏色與該面上其他的頂點不一樣狈网,這樣在光柵化后,頂點之間的片段顏色就會有個動態(tài)過度的效果(可以在動圖里看到)笨腥。

定義模型矩陣

旋轉(zhuǎn)效果是模型矩陣的職責拓哺,而且每一幀旋轉(zhuǎn)的角度都不一樣,因此只能寫在 onDrawFrame() 中:

Matrix.setIdentityM(mModelMatrix, 0);
Matrix.translateM(mModelMatrix, 0, 0.0f, 0.0f, -5.0f);
Matrix.rotateM(mModelMatrix, 0, angleInDegrees, 1.0f, 1.0f, 0.0f);

上面的效果為脖母,先將物體向 z 軸負方向移動 5 個單位拓售,然后以 (1.0f, 1.0f, 0.0f) 為軸旋轉(zhuǎn) angleInDegrees 度。其中 angleInDegrees 在另一個線程中隨時間流逝不斷增加镶奉。

定義觀察矩陣

onSurfaceCreated() 中定義觀察空間中的觀察矩陣

// Position the eye behind the origin
final float eyeX = 0.0f;
final float eyeY = 0.0f;
final float eyeZ = -0.5f;
// We are looking toward the distance
float lookX = 0.0f;
float lookY = 0.0f;
float lookZ = -5.0f;
// Set our up vector. This is where our head would be pointing were we holding the camera.
float upX = 0.0f;
float upY = 1.0f;
float upZ = 0.0f;
Matrix.setLookAtM(mViewMatrix, 0, eyeX, eyeY, eyeZ, lookX, lookY, lookZ, upX, upY, upZ);

由上可以看到础淤,我們定義攝像機位置在 (0.0f, 0.0f, -0.5f) 處,觀察的中心點坐標為 (0.0f, 0.0f, -5.0f)(與模型矩陣中物體平移距離相同)哨苛,右向量為 (0.0f, 1.0f, 0.0f)鸽凶。

定義投影矩陣

因為投影矩陣中會設(shè)置近平面的寬高比,因此需要在 onSurfaceChanged() 中定義投影矩陣

final float ratio = (float) width / height;
final float left = -ratio;
final float right = ratio;
final float bottom = -1.0f;
final float top = 1.0f;
final float near = 1.0f;
final float far = 10.0f;
Matrix.frustumM(mProjectionMatrix, 0, left, right, bottom, top, near, far);

Matrix.frustumM 都是固定寫法建峭,其中攝像機距離近平面為 1玻侥,遠平面為 10。

著色器代碼

String vertexShader =
        "uniform mat4 u_MVPMatrix;           \n" // A constant representing the combined model/view/projection matrix.
+ "attribute vec4 a_Position;               \n" // Per-vertex position information we will pass in.
+ "attribute vec4 a_Color;                  \n" // Per-vertex color information we will pass in.
+ "varying vec4 v_Color;                    \n" // This will be passed into the fragment shader.
+ "void main()                              \n" // The entry point for our vertex shader.
+ "{                                        \n" //
+ "     v_Color = a_Color;                  \n" // Pass the color through to the fragment shader.
+ "     gl_Position = u_MVPMatrix           \n" // gl_Position is a special variable used to store the final position.
+ "                 * a_Position;           \n" // Multiply the vertex by the matrix to get the final point in
+ "}                                        \n"; // normalized screen coordinates.
String fragmentShader =
        "precision mediump float;           \n" // Set the default precision to medium. We don't need as high of a
        // precision in the fragment shader.
+ "varying vec4 v_Color;                    \n" // This is the color from the vertex shader interpolated across the
        // triangle per fragment.
+ "void main()                              \n" // The entry point for our fragment shader.
+ "{                                        \n" //
+ "     gl_FragColor = v_Color;             \n" // Pass the color directly through the pipeline.
+ "}                                        \n";

代碼中片段的顏色通過 varying vec4 v_Color 在頂點著色器和片段著色器之間共享亿蒸。頂點著色器接受一個模型矩陣凑兰、觀察矩陣掌桩、投影矩陣按順序變換后的一個結(jié)果矩陣 uniform mat4 u_MVPMatrix

繪制立方體

將頂點坐標上傳到 GPU 并使能姑食,將頂點的顏色上傳到 GPU 并使能波岛,將上述的三個矩陣相乘后的結(jié)果矩陣傳給頂點著色器中的 u_MVPMatrix。最后調(diào)用 glDrawArrays() 將立方體繪制到屏幕上音半。

cubePositionsBuffer.position(0);
glVertexAttribPointer(mPositionHandle, POSITION_DATA_SIZE, GL_FLOAT,
        false, 0, cubePositionsBuffer);
glEnableVertexAttribArray(mPositionHandle);
cubeColorsBuffer.position(0);
glVertexAttribPointer(mColorHandle, COLOR_DATA_SIZE, GL_FLOAT,
        false, 0, cubeColorsBuffer);
glEnableVertexAttribArray(mColorHandle);
Matrix.multiplyMM(mMVPMatrix, 0, mViewMatrix, 0, mModelMatrix, 0);
Matrix.multiplyMM(mMVPMatrix, 0, mProjectionMatrix, 0, mMVPMatrix, 0);
glUniformMatrix4fv(mMVPMatrixHandle, 1, false, mMVPMatrix, 0);
glDrawArrays(GL_TRIANGLES, 0, 36);

深度測試

完成上面的代碼后则拷,我們可以得到下圖的效果。

沒有深度測試的效果

可以看到有些面明顯是沒有被顯示曹鸠,而是被其他面遮擋了煌茬。這主要是因為,即使這個面在前面(即離攝像機更近)彻桃,但是它的繪制順序卻后于另一些面坛善,而被另一些面所遮擋了。要解決這個問題邻眷,我們可以開啟深度測試眠屎。
深度測試為屏幕上每一個像素點保存一個深度值,深度值在 (0, 1) 之間耗溜,值越小表示離觀察者越近。片段著色器中的每一個片段的像素省容,都需要先做深度測試抖拴,如果該像素比深度緩沖中對應(yīng)的深度值小,那么就更新該深度緩沖區(qū)腥椒,否則就拋棄該像素阿宅。最后繪制的結(jié)果就是,屏幕上該像素對應(yīng)的點顯示的就是該點對應(yīng)物體上最前面的那個點笼蛛。

代碼實踐

我們可以通過 glEnable(GL_DEPTH_TEST) 來開啟深度測試洒放,同時在每一次調(diào)用 onDrawFrame() 通過調(diào)用 glClear(GL_DEPTH_BUFFER_BIT) 來清除上一幀的深度緩存。

面剔除

對于這種立方體滨砍,位于物體背面的面往湿,我們有時是知道它一定會前面的面所蓋住的(例如本文中的立方體)。那么我們就可以告訴 OpenGL 當這些面處于物體背面時惋戏,就不在處理這些面领追。這就是面剔除
OpenGL 定義了物體面的正面和反面响逢。OpenGL 檢查所有正面朝向觀察者的面绒窑,渲染這些正面,丟棄所有的反面舔亭,這將為 OpenGL 節(jié)省一半的性能些膨。

定義正面蟀俊、反面

OpenGL 通過連接順序(Winding Order)來定義正、反面订雾。默認逆時針為正面肢预,順時針為反面。

連接順序

所以當我們定義物體面的頂點時葬燎,我們一定要想象它是面朝觀察者以逆時針方向定義頂點误甚。這樣當這個面旋轉(zhuǎn)到不需要觀察者看到的背面時,觀察者看到的就是正面谱净,因此 OpenGL 不會渲染它窑邦。如下圖,左邊的面就被剔除了:
面剔除

代碼實踐

我們通過 glEnable(GL_CULL_FACE) 來開啟面剔除壕探。對于本文中的立方體冈钦,我們開啟面剔除并關(guān)掉深度測試一樣可以達到正確的顯示效果。并且少了深度測試李请,從而更加節(jié)省了性能和開銷瞧筛。因此在本文例子中,使用面剔除更加合適导盅。

上文所提到的较幌,連接順序來定義正反面,只是 OpenGL 的默認規(guī)則是逆時針為正面白翻,你也可以通過 glFrontFace(GL_CW) 改變這個規(guī)則為順時針為正面乍炉。并且也可以通過 glCullFace(GL_FRONT) 來剔除正面。不過并不建議這樣做滤馍,因為大家都是約定俗成逆時針為正面岛琼,剔除反面。如果你特別指定規(guī)則巢株,會給其他人造成困擾槐瑞。


拓展

如果我們將深度測試面剔除組合使用會發(fā)生什么呢?下面以本文中的立方體阁苞,假設(shè)有一個面是按照順時針來定義的困檩,:

  • 開啟面剔除的情況下,我們將永遠也看不到這個面那槽,即使開啟了深度測試也將看不到窗看。
  • 關(guān)閉面剔除深度測試的情況下,我們將在某些角度看不到多個面(就是本文中第二個動圖)倦炒,這是因為后渲染的面會覆蓋了先渲染的面显沈。
  • 關(guān)閉面剔除,開啟深度測試,我們將看到正常的效果拉讯,但是物體上的每個點都進行了深度測試涤浇,這也是一筆性能開銷。

因此魔慷,我們應(yīng)該總是按照逆時針方向定義面的頂點只锭,并在某些情況下(比如本文的立方體)開啟面剔除來提高性能。如果面剔除不能處理某些情況院尔,比如某個面的反面也需要觀察者看到蜻展,那么我們就應(yīng)該關(guān)閉面剔除,打開深度測試邀摆,以保證物體被正確顯示纵顾。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市栋盹,隨后出現(xiàn)的幾起案子施逾,更是在濱河造成了極大的恐慌,老刑警劉巖例获,帶你破解...
    沈念sama閱讀 211,639評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件汉额,死亡現(xiàn)場離奇詭異,居然都是意外死亡榨汤,警方通過查閱死者的電腦和手機蠕搜,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,277評論 3 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來收壕,“玉大人妓灌,你說我怎么就攤上這事√淦鳎” “怎么了旬渠?”我有些...
    開封第一講書人閱讀 157,221評論 0 348
  • 文/不壞的土叔 我叫張陵俱萍,是天一觀的道長端壳。 經(jīng)常有香客問我,道長枪蘑,這世上最難降的妖魔是什么损谦? 我笑而不...
    開封第一講書人閱讀 56,474評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮岳颇,結(jié)果婚禮上照捡,老公的妹妹穿的比我還像新娘。我一直安慰自己话侧,他們只是感情好栗精,可當我...
    茶點故事閱讀 65,570評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般悲立。 火紅的嫁衣襯著肌膚如雪鹿寨。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,816評論 1 290
  • 那天薪夕,我揣著相機與錄音脚草,去河邊找鬼。 笑死原献,一個胖子當著我的面吹牛馏慨,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播姑隅,決...
    沈念sama閱讀 38,957評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼写隶,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了粤策?” 一聲冷哼從身側(cè)響起樟澜,我...
    開封第一講書人閱讀 37,718評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎叮盘,沒想到半個月后秩贰,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,176評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡柔吼,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,511評論 2 327
  • 正文 我和宋清朗相戀三年毒费,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片愈魏。...
    茶點故事閱讀 38,646評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡觅玻,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出培漏,到底是詐尸還是另有隱情溪厘,我是刑警寧澤,帶...
    沈念sama閱讀 34,322評論 4 330
  • 正文 年R本政府宣布牌柄,位于F島的核電站畸悬,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏珊佣。R本人自食惡果不足惜蹋宦,卻給世界環(huán)境...
    茶點故事閱讀 39,934評論 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望咒锻。 院中可真熱鬧冷冗,春花似錦、人聲如沸惑艇。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,755評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至思灌,卻和暖如春碰镜,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背习瑰。 一陣腳步聲響...
    開封第一講書人閱讀 31,987評論 1 266
  • 我被黑心中介騙來泰國打工绪颖, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人甜奄。 一個月前我還...
    沈念sama閱讀 46,358評論 2 360
  • 正文 我出身青樓柠横,卻偏偏與公主長得像,于是被迫代替她去往敵國和親课兄。 傳聞我的和親對象是個殘疾皇子牍氛,可洞房花燭夜當晚...
    茶點故事閱讀 43,514評論 2 348

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