首先我們需要知道opengles在屏幕上的座標,即中間座標點是x=0,y=0; x軸往右是正,往左是負,y軸往上是正,往下是負,且x, y的範圍都是-1到1;所以我們先定義座標數組,順序是逆時針方向:
private static final float[] TRIANGLE_VERTEX_COORD = {0, 0.5f,
-0.5f, -0.5f,
0.5f, -0.5f};
爲了最大限度地提高效率,我們可以將這些座標寫入FloatBuffer,然後將其傳遞給OpenGL ES圖形管道進行處理:
private FloatBuffer mTriangleCoordBuffer;
public Triangle() {
// 數組轉化爲buffer,提高opengles性能
mTriangleCoordBuffer = ByteBuffer.allocateDirect(TRIANGLE_VERTEX_COORD.length * 4)
.order(ByteOrder.nativeOrder()).asFloatBuffer();
mTriangleCoordBuffer.put(TRIANGLE_VERTEX_COORD);
// 設置從第一位開始讀
mTriangleCoordBuffer.position(0);
}
現在我們要把這些座標數據傳給opengles圖形管道,那麼我們怎麼傳呢,這裏需要用到shader,即是着色器,opengles裏分爲兩種shader,一種是vertex shader(我們的頂點座標就是傳到這裏), 另一種是fragment shader(我們的顏色數據傳到這),並且由opengles特有的語言把這兩種shader組織起來,下面我們來看看例子:
private static final String VERTEX_SHADER_SOURCE = "attribute vec4 vPosition;" +
"void main() {" +
" gl_Position = vPosition;" +
"}";
private static final String FRAGMENT_SHADER_SOURCE = "uniform vec4 vColor;" +
"void main() {" +
" gl_FragColor = vColor;" +
"}";
它們的寫法類似於C語言,叫做GLES語言,當有一些自己獨有的東西;
attribute僅在vertex shader中使用,常用於指出vertex shader的輸入數據;
vec4代表這個向量裏有四個float型的數據;
vPosition是我們自己起的一個變量名;
gl_Position是opengles的特有名詞,指的是頂點的座標;
uniform用於存儲shader需要的各種數據,例如轉換矩陣、光線參數和顏色等。可以被vertex shader 和 fragment shader 共享使用;
gl_FragColor也是opengles特有的名詞,指的是顏色;
好了,寫完GLES代碼,我們需要把代碼編譯後傳給opengles,下面看下該怎麼做:
我們先寫一個加載shader的方法
private int getShader(int type, String source) {
int shader = GLES20.glCreateShader(type);
GLES20.glShaderSource(shader, source);
GLES20.glCompileShader(shader);
// 獲取編譯的狀態
final int[] compileStatus = new int[1];
GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS,
compileStatus, 0);
Log.v(TAG, "代碼編譯結果:" + "\n" + source
+ "\n:" + GLES20.glGetShaderInfoLog(shader));
if (compileStatus[0] == 0) {
Log.w(TAG, "編譯失敗!.");
return 0;
}
return shader;
}
接着把得到的shader和program連接,這樣opengles就知道該怎麼處理我們的着色邏輯了;
private int mVertexShader, mFragmentShader;
public Triangle() {
// ...
// 得到編譯後的shader
mVertexShader = getShader(GLES20.GL_VERTEX_SHADER, VERTEX_SHADER_SOURCE);
mFragmentShader = getShader(GLES20.GL_FRAGMENT_SHADER, FRAGMENT_SHADER_SOURCE);
// 創建並連接program
int program = GLES20.glCreateProgram();
GLES20.glAttachShader(program, mVertexShader);
GLES20.glAttachShader(program, mFragmentShader);
GLES20.glLinkProgram(program);
GLES20.glUseProgram(program);
}
我們之前定義好的頂點buffer該怎麼傳進去給opengles呢,這裏我們就需要先找到對應的變量的locaction:
private static final String POSITION_NAME = "vPosition";
private static final String COLOR_NAME = "vColor";
public Triangle() {
// ...
// 找到對應變量的index
mPositionLocation = GLES20.glGetAttribLocation(program, POSITION_NAME);
mFragColorLocation = GLES20.glGetUniformLocation(program, COLOR_NAME);
}
傳入數據,就可以基本完成我們的繪圖了:
public void draw() {
// 由於性能方面的考慮,opengles默認讀取不到緩衝區裏的數據,需要先enable後再讀
GLES20.glEnableVertexAttribArray(mPositionLocation);
GLES20.glVertexAttribPointer(mPositionLocation, COORDS_PER_VERTEX,
GLES20.GL_FLOAT, false, 0, mTriangleCoordBuffer);
GLES20.glUniform4f(mFragColorLocation, 0, 0.5f, 0, 1.0f);
GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, VERTEX_SIZE);
// 調用完glDrawArrays後才能disable;
GLES20.glDisableVertexAttribArray(mPositionLocation);
}
這裏給出較完整的代碼:
public class MyRenderer implements GLSurfaceView.Renderer {
private Triangle mTriangle;
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
GLES20.glClearColor(0.5f, 0.5f, 0.5f, 1.0f);
mTriangle = new Triangle();
}
@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
GLES20.glViewport(0, 0, width, height);
}
@Override
public void onDrawFrame(GL10 gl) {
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
mTriangle.draw();
}
}
public class Triangle {
private static final String TAG = "Triangle";
// 三角形的三個頂點
private static final float[] TRIANGLE_VERTEX_COORD = {0, 0.5f,
-0.5f, -0.5f,
0.5f, -0.5f};
private static final int VERTEX_SIZE = 3;
private static final int COORDS_PER_VERTEX = TRIANGLE_VERTEX_COORD.length / VERTEX_SIZE;
private FloatBuffer mTriangleCoordBuffer;
private static final String VERTEX_SHADER_SOURCE = "attribute vec4 vPosition;" +
"void main() {" +
" gl_Position = vPosition;" +
"}";
private static final String FRAGMENT_SHADER_SOURCE = "uniform vec4 vColor;" +
"void main() {" +
" gl_FragColor = vColor;" +
"}";
private int mVertexShader, mFragmentShader;
private static final String POSITION_NAME = "vPosition";
private static final String COLOR_NAME = "vColor";
private int mPositionLocation, mFragColorLocation;
public Triangle() {
// 數組轉化爲buffer,提高opengles性能
mTriangleCoordBuffer = ByteBuffer.allocateDirect(TRIANGLE_VERTEX_COORD.length * 4)
.order(ByteOrder.nativeOrder()).asFloatBuffer();
mTriangleCoordBuffer.put(TRIANGLE_VERTEX_COORD);
// 設置從第一位開始讀
mTriangleCoordBuffer.position(0);
// 得到編譯後的shader
mVertexShader = getShader(GLES20.GL_VERTEX_SHADER, VERTEX_SHADER_SOURCE);
mFragmentShader = getShader(GLES20.GL_FRAGMENT_SHADER, FRAGMENT_SHADER_SOURCE);
// 創建並連接program
int program = GLES20.glCreateProgram();
GLES20.glAttachShader(program, mVertexShader);
GLES20.glAttachShader(program, mFragmentShader);
GLES20.glLinkProgram(program);
GLES20.glUseProgram(program);
// 找到對應變量的index
mPositionLocation = GLES20.glGetAttribLocation(program, POSITION_NAME);
mFragColorLocation = GLES20.glGetUniformLocation(program, COLOR_NAME);
}
private int getShader(int type, String source) {
int shader = GLES20.glCreateShader(type);
GLES20.glShaderSource(shader, source);
GLES20.glCompileShader(shader);
// 獲取編譯的狀態
final int[] compileStatus = new int[1];
GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS,
compileStatus, 0);
Log.v(TAG, "代碼編譯結果:" + "\n" + source
+ "\n:" + GLES20.glGetShaderInfoLog(shader));
if (compileStatus[0] == 0) {
Log.w(TAG, "編譯失敗!.");
return 0;
}
return shader;
}
public void draw() {
// 由於性能方面的考慮,opengles默認讀取不到緩衝區裏的數據,需要先enable後再讀
GLES20.glEnableVertexAttribArray(mPositionLocation);
GLES20.glVertexAttribPointer(mPositionLocation, COORDS_PER_VERTEX,
GLES20.GL_FLOAT, false, 0, mTriangleCoordBuffer);
GLES20.glUniform4f(mFragColorLocation, 0, 0.5f, 0, 1.0f);
GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, VERTEX_SIZE);
GLES20.glDisableVertexAttribArray(mPositionLocation);
}
}
運行後的結果:
不過我們的三角型有點變形,我們定義的明明是等邊三角型,但實際畫出來的不是,這裏是因爲opengles的座標和實際屏幕的座標是不對應的,opengles的長和寬都是一樣的,就是-1到1,實際屏幕的長和寬肯定不一樣,沒有正方形的手機屏吧,該如何解決呢?這裏我們需要用到矩陣,下節再說。