【附源碼】【Android 3D OpenGL】開發之三——漂亮的金字塔【MacroCheng原創】
一、OpenGL面可見原理
在OpenGL中,有一個概念叫做彎曲(winding),意思是vertices繪製時的順序。與現實世界中的對象不同,OpenGL中的多邊形一般沒有兩個面。他們只有一個面,一般是正面,一個三角形只有當其正面面對觀察者的時候纔可以被看到。可以配置OpenGL將一個多邊形作爲兩面的,但是默認情況下三角形只有一個可見的面。知道了那邊是多邊形的正面以後,OpenGL就可以少做一半的計算量。如果設置兩面都可視,則需要多的計算。
雖然有時候一個多邊形會獨立地顯示,但是你或許不是非常需要它的背面顯示,經常一個三角形是一個更大的對象的一部分,多邊形的一面將在這個物體的內部,所以永遠也不會被看到。這個沒有被顯示的一面被稱作背面,OpenGl通過繪製的順序來確定那個面是正面哪個是背面。頂點按照逆時針繪製的是正面(默認是這樣,但是可以被改變)。因爲OpenGL能很容易地確定哪些三角形對用戶是可視的,它就可以通過使用Backface Culling來避免爲那些不顯示在前面的多邊形做無用功。我們將在下一篇文章裏討論視角的問題,但是你現在可以想象它爲一個虛擬攝像機,或者通過一個虛擬的窗口來觀察OpenGL的世界。
在上面的示意圖中,左邊青綠色的的三角形是背面,將不會被繪製,因爲它相對於觀察者來說是順時針的。而在右邊的這個三角形是正面,將會被繪製,因爲繪製頂點的順序相對於觀察者來說是逆時針的。
二、金字塔
2.1、金字塔模型
2.2、金字塔的對象類Pyramid.java
package com.macrocheng.opengl3d01;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.nio.ShortBuffer;
public class Pyramid {
public Pyramid() {
// TODO Auto-generated constructor stub
initPyramid();
}
//金字塔頂點的Buffer
private FloatBuffer vertexBuffer;
public FloatBuffer getVertexBuffer() {
return vertexBuffer;
}
public void setVertexBuffer(FloatBuffer vertexBuffer) {
this.vertexBuffer = vertexBuffer;
}
//金字塔顏色數組的Buffer
private FloatBuffer colorBuffer;
public FloatBuffer getColorBuffer() {
return colorBuffer;
}
public void setColorBuffer(FloatBuffer colorBuffer) {
this.colorBuffer = colorBuffer;
}
//金字塔索引值得Buffer
private ShortBuffer indexBuffer;
public ShortBuffer getIndexBuffer() {
return indexBuffer;
}
public void setIndexBuffer(ShortBuffer indexBuffer) {
this.indexBuffer = indexBuffer;
}
private int numberOfPoint = 4*3;
public int getNumberOfPoint() {
return numberOfPoint;
}
/**
* 初始化金字塔
*/
private void initPyramid()
{
float[] coords = {
-0.5f, -0.5f, 0.5f, // 0
0.5f, -0.5f, 0.5f, // 1
0f, -0.5f, -0.5f, // 2
0f, 0.5f, 0f, // 3
};
float[] colors = {
1f, 0f, 0f, 1f, // point 0 red
0f, 1f, 0f, 1f, // point 1 green
0f, 0f, 1f, 1f, // point 2 blue
1f, 1f, 1f, 1f, // point 3 white
};
short[] indicesArray = {
0, 1, 3, // rwg
0, 2, 1, // rbg
0, 3, 2, // rbw
1, 2, 3, // bwg
};
//float類型有四個字節,分配內存
ByteBuffer vbb = ByteBuffer.allocateDirect(coords.length*4);
vbb.order(ByteOrder.nativeOrder());
vertexBuffer = vbb.asFloatBuffer();
//short類型有2個字節,分配內存
ByteBuffer ibb = ByteBuffer.allocateDirect(indicesArray.length*2);
ibb.order(ByteOrder.nativeOrder());
indexBuffer = ibb.asShortBuffer();
ByteBuffer cbb = ByteBuffer.allocateDirect(colors.length * 4);
cbb.order(ByteOrder.nativeOrder());
colorBuffer = cbb.asFloatBuffer();
vertexBuffer.put(coords);
indexBuffer.put(indicesArray);
colorBuffer.put(colors);
vertexBuffer.position(0);
indexBuffer.position(0);
colorBuffer.position(0);
}
}
三、視角聲明
//讓每個不同的面都可見
gl.glEnable(GL10.GL_CULL_FACE);
//逆時針方向的面爲前面,前面就被畫出來
gl.glFrontFace(GL10.GL_CCW);
//gl.glFrontFace(GL10.GL_CW);
//後面就不需要被畫出來
gl.glCullFace(GL10.GL_BACK);
將這三條語句放在onSurfaceCreated函數裏面,就可以設置好這個視角,然後所有的反向面都是可見的,然後順時針方向繪成的面都不可見。
3.1、VortexRenderer.java
因爲VortexRenderer類有改動,所有就貼出所有的源碼
package com.tcl.macrocheng.opengl3d02;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.nio.ShortBuffer;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;
import org.apache.http.util.ByteArrayBuffer;
import android.opengl.GLSurfaceView;
public class VortexRenderer implements GLSurfaceView.Renderer {
private static final String LOG = VortexRenderer.class.getSimpleName();
private float red = 0.9f;
private float green = 0.2f;
private float blue = 0.2f;
private FloatBuffer colorBuffer;
@Override
public void onDrawFrame(GL10 gl) {
// TODO Auto-generated method stub
//定義定點的顏色
gl.glClearColor(0f, 0f, 0f, 1.0f);
//清除顏色的Buffer然後讓現實上面我們通過glClearColor來定義的顏色
gl.glClear(GL10.GL_COLOR_BUFFER_BIT);
gl.glLoadIdentity();
gl.glRotatef(xAngle, 1f, 0f, 0f);
gl.glRotatef(yAngle, 0f, 1f, 0f);
/**
* 第一個參數是大小,也是頂點的維數。我們使用的是x,y,z三維座標。
* 第二個參數,GL_FLOAT定義buffer中使用的數據類型。
* 第三個變量是0,是因爲我們的座標是在數組中緊湊的排列的,沒有使用offset。
* 最後,第四個參數頂點緩衝。
*/
gl.glVertexPointer(3, GL10.GL_FLOAT, 0, vertexBuffer);
/**
* 參數4表示RGBA(RGBA剛好是四個值),其餘的幾個參數大家都比較熟悉了。
*/
gl.glColorPointer(4, GL10.GL_FLOAT, 0, colorBuffer);
/**
* 將所有這些元素畫出來。第一個參數定義了什麼樣的圖元將被畫出來。
* 第二個參數定義有多少個元素,
* 第三個是indices使用的數據類型。
* 最後一個是繪製頂點使用的索引緩衝。
*/
gl.glDrawElements(GL10.GL_TRIANGLES, nrOfVertices*3, GL10.GL_UNSIGNED_SHORT, indexBuffer);
//gl.glColorPointer(4, GL10.GL_FLOAT, 0, colorBuffer);
//gl.glDrawElements(GL10.GL_TRIANGLE_FAN, nrOfVertices, GL10.GL_UNSIGNED_SHORT, indexBuffer);
}
@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
// TODO Auto-generated method stub
gl.glViewport(0, 0, width, height);
}
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
// TODO Auto-generated method stub
//讓每個不同的面都可見
gl.glEnable(GL10.GL_CULL_FACE);
//逆時針方向的面爲前面,前面就被畫出來
gl.glFrontFace(GL10.GL_CCW);
//gl.glFrontFace(GL10.GL_CW);
//後面就不需要被畫出來
gl.glCullFace(GL10.GL_BACK);
gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
gl.glEnableClientState(GL10.GL_COLOR_ARRAY);
initTriangle();
}
public void setColor(float r,float g,float b)
{
red = r;
green = g;
blue = b;
}
//保存索引
private ShortBuffer indexBuffer;
//爲三角形保存座標
private FloatBuffer vertexBuffer;
//x需要多少個定點
private int nrOfVertices = 4;
//初始化一個三角形
public void initTriangle()
{
float[] coords = {
// -0.5f,-0.5f,0f,//x1,y1,z1
// 0.5f,0.5f,0f,//x2,y2,z2
// 0f,0.5f,0f //x3,y3,z3
-0.5f, -0.5f, 0.5f, // 0
0.5f, -0.5f, 0.5f, // 1
0f, -0.5f, -0.5f, // 2
0f, 0.5f, 0f, // 3
};
float[] colors = {
// 1f, 0f, 0f, 1f, // point 1
// 0f, 1f, 0f, 1f, // point 2
// 0f, 0f, 1f, 1f, // point 3
1f, 0f, 0f, 1f, // point 0 red
0f, 1f, 0f, 1f, // point 1 green
0f, 0f, 1f, 1f, // point 2 blue
1f, 1f, 1f, 1f, // point 3 white
};
short[] indicesArray = {
0, 1, 3, // rwg
0, 2, 1, // rbg
0, 3, 2, // rbw
1, 2, 3, // bwg
};
//float類型有四個字節,分配內存
ByteBuffer vbb = ByteBuffer.allocateDirect(coords.length*4);
vbb.order(ByteOrder.nativeOrder());
vertexBuffer = vbb.asFloatBuffer();
//short類型有2個字節,分配內存
ByteBuffer ibb = ByteBuffer.allocateDirect(indicesArray.length*2);
ibb.order(ByteOrder.nativeOrder());
indexBuffer = ibb.asShortBuffer();
ByteBuffer cbb = ByteBuffer.allocateDirect(colors.length * 4);
cbb.order(ByteOrder.nativeOrder());
colorBuffer = cbb.asFloatBuffer();
vertexBuffer.put(coords);
indexBuffer.put(indicesArray);
colorBuffer.put(colors);
vertexBuffer.position(0);
indexBuffer.position(0);
colorBuffer.position(0);
}
//x座標旋轉角度
private float xAngle = 0;
//y座標旋轉角度
private float yAngle = 0;
public void setXAngle(float x)
{
xAngle = x;
}
public float getXAngle()
{
return xAngle;
}
public void setYAngle(float y)
{
yAngle = y;
}
public float getYAngle()
{
return yAngle;
}
}
四、效果預覽