代碼實踐
定義物體的顏色和局部空間中的坐標
首先我們定義六個面的頂點坐標,每個面由兩個三角形組成。
// 假設(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)閉面剔除,打開深度測試邀摆,以保證物體被正確顯示纵顾。