上篇博客中我們提到了OpenGLES中繪制的兩種方法,頂點法和索引法。之前我們所使用的都是頂點法,這次繪制立方體使用索引法來繪制立方體。
構建立方體
上篇博客講到正方形的繪制,立方體是是由六個正方形組成,我們將這六個正方形繪制出來,立方體就繪制完畢了。既然選擇用索引法來繪制,立方體擁有八個頂點,我們先將這八個頂點列出來,放到一個數組中:
final float cubePositions[] = {
-1.0f,1.0f,1.0f, //正面左上0
-1.0f,-1.0f,1.0f, //正面左下1
1.0f,-1.0f,1.0f, //正面右下2
1.0f,1.0f,1.0f, //正面右上3
-1.0f,1.0f,-1.0f, //反面左上4
-1.0f,-1.0f,-1.0f, //反面左下5
1.0f,-1.0f,-1.0f, //反面右下6
1.0f,1.0f,-1.0f, //反面右上7
};
正面由032和021兩個三角形組成,其他面諸如此類拆分,得到索引數組:
final short index[]={
0,3,2,0,2,1, //正面
0,1,5,0,5,4, //左面
0,7,3,0,4,7, //上面
6,7,4,6,4,5, //后面
6,3,7,6,2,3, //右面
6,5,1,6,1,2 //下面
};
如果使用單一顏色,最后繪制出來的立方體不方便看效果,所以我們來繪制多種顏色的立方體出來。定義各個頂點的顏色:
//八個頂點的顏色,與頂點坐標一一對應
float color[] = {
0f,1f,0f,1f,
0f,1f,0f,1f,
0f,1f,0f,1f,
0f,1f,0f,1f,
1f,0f,0f,1f,
1f,0f,0f,1f,
1f,0f,0f,1f,
1f,0f,0f,1f,
};
繪制立方體
確定好立方體坐標和顏色后,我們需要創建頂點著色器和片元著色器,可使用與Android OpenGLES2.0(三)——等腰直角三角形和彩色的三角形中彩色三角形相同的頂點著色器和片元著色器。
接著其他的步驟與繪制三角形相同:
- 初始化坐標數據、索引數據、顏色數據,具體操作為將坐標數據、顏色數據分別寫入到獨自的FloatBuffer中,將索引數據寫入到ShortBuffer中
- 創建OpenGL2.0程序,將頂點著色器和片元著色器加入到程序中,并鏈接程序。
- 使用創建的OpenGLES2.0程序,寫入變換矩陣、頂點坐標數據及顏色數據。
- 索引法繪制出所有頂點坐標組成的三角形,得到一個立方體。
如果我們僅僅只做了以上事情,往往我們得不到一個正確的立方里,反而會出現比較奇怪的立方體,比如這樣的:
這是因為我們沒有開啟深度測試GLES20.glEnable(GLES20.GL_DEPTH_TEST)
,并在繪制前清除深度緩存導致GLES20.glClear(GLES20.GL_DEPTH_BUFFER_BIT)
的。
加入后,我們即可得到正常的立方體:
openGL里常出現深度測試,關于深度測試內容如下:
(1)什么是深度?
深度其實就是該象素點在3d世界中距離攝象機的距離(繪制坐標),深度緩存中存儲著每個象素點(繪制在屏幕上的)的深度值!
深度值(Z值)越大,則離攝像機越遠。
深度值是存貯在深度緩存里面的,我們用深度緩存的位數來衡量深度緩存的精度。深度緩存位數越高,則精確度越高,目前的顯卡一般都可支持16位的Z Buffer,一些高級的顯卡已經可以支持32位的Z Buffer,但一般用24位Z Buffer就已經足夠了。
(2)為什么需要深度?
在不使用深度測試的時候,如果我們先繪制一個距離較近的物體,再繪制距離較遠的物體,則距離遠的物體因為后繪制,會把距離近的物體覆蓋掉,這樣的效果并不是我們所希望的。而有了深度緩沖以后,繪制物體的順序就不那么重要了,都能按照遠近(Z值)正常顯示,這很關鍵。
實際上,只要存在深度緩沖區,無論是否啟用深度測試,OpenGL在像素被繪制時都會嘗試將深度數據寫入到緩沖區內,除非調用了glDepthMask(GL_FALSE)來禁止寫入。這些深度數據除了用于常規的測試外,還可以有一些有趣的用途,比如繪制陰影等等。
(3)啟用深度測試
使用 glEnable(GL_DEPTH_TEST);
在默認情況是將需要繪制的新像素的z值與深度緩沖區中對應位置的z值進行比較,如果比深度緩存中的值小,那么用新像素的顏色值更新幀緩存中對應像素的顏色值。
但是可以使用glDepthFunc(func)來對這種默認測試方式進行修改。
其中參數func的值可以為GL_NEVER(沒有處理)、GL_ALWAYS(處理所有)、GL_LESS(小于)、GL_LEQUAL(小于等于)、GL_EQUAL(等于)、GL_GEQUAL(大于等于)、GL_GREATER(大于)或GL_NOTEQUAL(不等于),其中默認值是GL_LESS。
一般來將,使用glDepthFunc(GL_LEQUAL);來表達一般物體之間的遮擋關系。
(4)啟用了深度測試,那么這就不適用于同時繪制不透明物體。
具體實現
public class Cube extends Shape{
private FloatBuffer vertexBuffer,colorBuffer;
private ShortBuffer indexBuffer;
private final String vertexShaderCode =
"attribute vec4 vPosition;" +
"uniform mat4 vMatrix;"+
"varying vec4 vColor;"+
"attribute vec4 aColor;"+
"void main() {" +
" gl_Position = vMatrix*vPosition;" +
" vColor=aColor;"+
"}";
private final String fragmentShaderCode =
"precision mediump float;" +
"varying vec4 vColor;" +
"void main() {" +
" gl_FragColor = vColor;" +
"}";
private int mProgram;
final int COORDS_PER_VERTEX = 3;
final float cubePositions[] = {
-1.0f,1.0f,1.0f, //正面左上0
-1.0f,-1.0f,1.0f, //正面左下1
1.0f,-1.0f,1.0f, //正面右下2
1.0f,1.0f,1.0f, //正面右上3
-1.0f,1.0f,-1.0f, //反面左上4
-1.0f,-1.0f,-1.0f, //反面左下5
1.0f,-1.0f,-1.0f, //反面右下6
1.0f,1.0f,-1.0f, //反面右上7
};
final short index[]={
6,7,4,6,4,5, //后面
6,3,7,6,2,3, //右面
6,5,1,6,1,2, //下面
0,3,2,0,2,1, //正面
0,1,5,0,5,4, //左面
0,7,3,0,4,7, //上面
};
float color[] = {
0f,1f,0f,1f,
0f,1f,0f,1f,
0f,1f,0f,1f,
0f,1f,0f,1f,
1f,0f,0f,1f,
1f,0f,0f,1f,
1f,0f,0f,1f,
1f,0f,0f,1f,
};
private int mPositionHandle;
private int mColorHandle;
private float[] mViewMatrix=new float[16];
private float[] mProjectMatrix=new float[16];
private float[] mMVPMatrix=new float[16];
private int mMatrixHandler;
//頂點個數
private final int vertexCount = cubePositions.length / COORDS_PER_VERTEX;
//頂點之間的偏移量
private final int vertexStride = COORDS_PER_VERTEX * 4; // 每個頂點四個字節
public Cube(View mView) {
super(mView);
ByteBuffer bb = ByteBuffer.allocateDirect(
cubePositions.length * 4);
bb.order(ByteOrder.nativeOrder());
vertexBuffer = bb.asFloatBuffer();
vertexBuffer.put(cubePositions);
vertexBuffer.position(0);
ByteBuffer dd = ByteBuffer.allocateDirect(
color.length * 4);
dd.order(ByteOrder.nativeOrder());
colorBuffer = dd.asFloatBuffer();
colorBuffer.put(color);
colorBuffer.position(0);
ByteBuffer cc= ByteBuffer.allocateDirect(index.length*2);
cc.order(ByteOrder.nativeOrder());
indexBuffer=cc.asShortBuffer();
indexBuffer.put(index);
indexBuffer.position(0);
int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER,
vertexShaderCode);
int fragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER,
fragmentShaderCode);
//創建一個空的OpenGLES程序
mProgram = GLES20.glCreateProgram();
//將頂點著色器加入到程序
GLES20.glAttachShader(mProgram, vertexShader);
//將片元著色器加入到程序中
GLES20.glAttachShader(mProgram, fragmentShader);
//連接到著色器程序
GLES20.glLinkProgram(mProgram);
}
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
//開啟深度測試
GLES20.glEnable(GLES20.GL_DEPTH_TEST);
}
@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
//計算寬高比
float ratio=(float)width/height;
//設置透視投影
Matrix.frustumM(mProjectMatrix, 0, -ratio, ratio, -1, 1, 3, 20);
//設置相機位置
Matrix.setLookAtM(mViewMatrix, 0, 5.0f, 5.0f, 10.0f, 0f, 0f, 0f, 0f, 1.0f, 0.0f);
//計算變換矩陣
Matrix.multiplyMM(mMVPMatrix,0,mProjectMatrix,0,mViewMatrix,0);
}
@Override
public void onDrawFrame(GL10 gl) {
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT| GLES20.GL_DEPTH_BUFFER_BIT);
//將程序加入到OpenGLES2.0環境
GLES20.glUseProgram(mProgram);
//獲取變換矩陣vMatrix成員句柄
mMatrixHandler= GLES20.glGetUniformLocation(mProgram,"vMatrix");
//指定vMatrix的值
GLES20.glUniformMatrix4fv(mMatrixHandler,1,false,mMVPMatrix,0);
//獲取頂點著色器的vPosition成員句柄
mPositionHandle = GLES20.glGetAttribLocation(mProgram, "vPosition");
//啟用三角形頂點的句柄
GLES20.glEnableVertexAttribArray(mPositionHandle);
//準備三角形的坐標數據
GLES20.glVertexAttribPointer(mPositionHandle, 3,
GLES20.GL_FLOAT, false,
0, vertexBuffer);
//獲取片元著色器的vColor成員的句柄
mColorHandle = GLES20.glGetAttribLocation(mProgram, "aColor");
//設置繪制三角形的顏色
// GLES20.glUniform4fv(mColorHandle, 2, color, 0);
GLES20.glEnableVertexAttribArray(mColorHandle);
GLES20.glVertexAttribPointer(mColorHandle,4,
GLES20.GL_FLOAT,false,
0,colorBuffer);
//索引法繪制正方體
GLES20.glDrawElements(GLES20.GL_TRIANGLES,index.length, GLES20.GL_UNSIGNED_SHORT,indexBuffer);
//禁止頂點數組的句柄
GLES20.glDisableVertexAttribArray(mPositionHandle);
}
}