文章中所有的代碼示例都已放在 Github 上拴竹,可以去項目 OpenGL-ES-Learning 中查看 流昏。
在前面的文章中介紹了如何繪制形狀,相信你對于使用 OpenGL ES 進(jìn)行繪制的流程有了大致的了解扫倡。其中包括一些基本概念:
- GLSurfaceView 作為繪制視圖內(nèi)容的容器載體豹缀;
- GLSurfaceView. Renderer 作為控制繪制內(nèi)容和過程的渲染器
- OpenGL 坐標(biāo)系的概念以及借助 ByteBuffer 定義形狀的坐標(biāo)數(shù)據(jù)
- 繪制形狀的三要素:頂點著色器、片元著色器鼠次、程式
如果你對這些概念還有些不熟悉更哄,可以回頭再看一下前面的文章,把這些簡單的概念過一遍可以強化理解腥寇。
在 OpenGL ES 環(huán)境中成翩,利用投影和相機視角可以讓顯示的繪圖對象更加酷似于我們用肉眼看到的真實物體。該物理視角的模擬是對繪制對象坐標(biāo)的進(jìn)行數(shù)學(xué)變換實現(xiàn)的:
投影(Projection):這個是基于調(diào)整繪圖對象在 GLSurfaceView 中的寬和高的坐標(biāo)來轉(zhuǎn)換的赦役。如果沒有該計算麻敌,那么用 OpenGL ES 繪制的對象會由于其長寬比例和 View 窗口比例的不一致而發(fā)生形變。一個投影變換一般僅當(dāng) OpenGL View 的比例在剛被建立或發(fā)生變化(在 onSurfaceChanged() 中回調(diào))時才進(jìn)行計算掂摔。
相機視角(Camera View):這個變換會基于一個虛擬相機位置改變來進(jìn)行术羔。注意到 OpenGL ES 并沒有定義一個沒有定義一個真實的 camera 對象赢赊,而是提供了一些輔助方法,通過對繪圖對象的變換來模擬相機視角级历。一個相機視角變換可能僅在GLSurfaceView 剛建立時計算一次释移,也可能根據(jù)用戶的行為或者 app 的功能進(jìn)行動態(tài)調(diào)整。
關(guān)于更多 OpenGL ES 投影和坐標(biāo)映射的知識鱼喉,可以閱讀 Mapping Coordinates for Drawn Objects秀鞭。
本篇文章主要闡述如何創(chuàng)建一個投影和一個相機視角,并應(yīng)用到 GLSurfaceView 中的繪制圖像上扛禽。
定義一個投影
投影變換的數(shù)據(jù)是在 GLSurfaceView.Renderer 類的 onSurfaceChanged() 方法中計算出來的锋边。
下面的代碼先獲取 GLSurfaceView 的高和寬,然后利用它并使用 Matrix.frustumM() 方法來填充一個投影變換矩陣(Projection Transformation Matrix):
public class MyGLRenderer3 implements GLSurfaceView.Renderer {
...
// mMVPMatrix is an abbreviation for "Model View Projection Matrix"
private final float[] mMVPMatrix = new float[16];
private final float[] mProjectionMatrix = new float[16];
private final float[] mViewMatrix = new float[16];
...
@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
GLES20.glViewport(0, 0, width, height);
// 獲取 GLSurfaceView 的寬和高的比例
float ratio = (float) width / height;
// this projection matrix is applied to object coordinates
// in the onDrawFrame() method
// 填充了一個投影矩陣:mProjectionMatrix
Matrix.frustumM(mProjectionMatrix, 0, -ratio, ratio, -1, 1, 3, 7);
}
}
Note: 在繪圖對象上只應(yīng)用一個投影變換時會導(dǎo)致顯示 empty display 编曼。所以我們在進(jìn)行 projection transformation(投影變換)時通常還要進(jìn)行一個相機視角轉(zhuǎn)化豆巨,使得顯示對象能全部出現(xiàn)在屏幕上。
定義一個相機視角
在渲染器中添加一個相機視角變換作為圖形繪制過程的一部分掐场,以此完成我們的繪圖對象所需變換的所有步驟往扔。下面的代碼在 onDrawFrame 回調(diào)返回中使用 Matrix.setLookAtM() 方法來計算相機視角變換,然后與之前計算的投影矩陣結(jié)合起來熊户,結(jié)合后的變換矩陣傳遞給繪制圖像:
@Override
public void onDrawFrame(GL10 gl) {
// Set the camera position (View matrix)
Matrix.setLookAtM(mViewMatrix, 0, 0, 0, -3, 0f, 0f, 0f, 0f, 1.0f, 0.0f);
// Calculate the projection and view transformation
Matrix.multiplyMM(mMVPMatrix, 0, mProjectionMatrix, 0, mViewMatrix, 0);
// Draw shape
mTriangle.draw(mMVPMatrix);
}
因此整個渲染器包含了投影變換和相機視變換的結(jié)合萍膛,該類的全部代碼如下所示:
public class MyGLRenderer3 implements GLSurfaceView.Renderer {
private Triangle mTriangle;
// mMVPMatrix is an abbreviation for "Model View Projection Matrix"
private final float[] mMVPMatrix = new float[16];
private final float[] mProjectionMatrix = new float[16];
private final float[] mViewMatrix = new float[16];
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
// initialize a triangle
mTriangle = new Triangle();
}
@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
GLES20.glViewport(0, 0, width, height);
float ratio = (float) width / height;
// this projection matrix is applied to object coordinates
// in the onDrawFrame() method
Matrix.frustumM(mProjectionMatrix, 0, -ratio, ratio, -1, 1, 3, 7);
}
@Override
public void onDrawFrame(GL10 gl) {
// Set the camera position (View matrix)
// 改變攝像頭在 z 軸上的位置(鏡頭拉伸),讓視圖調(diào)整到合適的大小嚷堡。
Matrix.setLookAtM(mViewMatrix, 0, 0, 0, -3, 0f, 0f, 0f, 0f, 1.0f, 0.0f);
// Calculate the projection and view transformation
Matrix.multiplyMM(mMVPMatrix, 0, mProjectionMatrix, 0, mViewMatrix, 0);
// Draw shape
mTriangle.draw(mMVPMatrix);
}
}
應(yīng)用投影和相機變換
為了使用在之前章節(jié)中結(jié)合了的相機視角變換和投影變換蝗罗,我們首先為之前在 Triangle 類中定義的頂點著色器添加一個 Matrix 變量:
class Triangle {
/**
* 頂點著色器代碼
*/
private final String vertexShaderCode =
// This matrix member variable provides a hook to manipulate
// the coordinates of the objects that use this vertex shader
"uniform mat4 uMVPMatrix;" + // 添加一個 Matrix 變量
"attribute vec4 vPosition;" + // 應(yīng)用程序傳入頂點著色器的頂點位置
"void main() {" +
// the matrix must be included as a modifier of gl_Position
// Note that the uMVPMatrix factor *must be first* in order
// for the matrix multiplication product to be correct.
" gl_Position = uMVPMatrix * vPosition;" + // 設(shè)置此次繪制此頂點位置,進(jìn)行矩陣變換
"}";
// Use to access and set the view transformation
private int mMVPMatrixHandle;
...
}
再修改原有圖形對象(Triangle)的 draw() 方法蝌戒,使得它接收組合后的變換矩陣串塑,并將它應(yīng)用到圖形上:
public void draw(float[] mvpMatrix) { // pass in the calculated transformation matrix
...
// get handle to shape's transformation matrix
mMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix");
// Pass the projection and view transformation to the shader
GLES20.glUniformMatrix4fv(mMVPMatrixHandle, 1, false, mvpMatrix, 0);
// Draw the triangle
GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, vertexCount);
// Disable vertex array
GLES20.glDisableVertexAttribArray(mPositionHandle);
}
下面給出圖形對象的完整代碼:
class Triangle {
// 繪制形狀的頂點數(shù)量
private static final int COORDS_PER_VERTEX = 3;
/**
* 頂點著色器代碼
*/
private final String vertexShaderCode =
// This matrix member variable provides a hook to manipulate
// the coordinates of the objects that use this vertex shader
"uniform mat4 uMVPMatrix;" +
"attribute vec4 vPosition;" + // 應(yīng)用程序傳入頂點著色器的頂點位置
"void main() {" +
// the matrix must be included as a modifier of gl_Position
// Note that the uMVPMatrix factor *must be first* in order
// for the matrix multiplication product to be correct.
" gl_Position = uMVPMatrix * vPosition;" + // 設(shè)置此次繪制此頂點位置
"}";
/**
* 片元著色器代碼
*/
private final String fragmentShaderCode =
"precision mediump float;" + // 設(shè)置工作精度
"uniform vec4 vColor;" + // 應(yīng)用程序傳入著色器的顏色變量
"void main() {" +
" gl_FragColor = vColor;" + // 顏色值傳給gl_FragColor內(nèi)建變量,完成片元的著色
"}";
/**
* 定義三角形頂點的坐標(biāo)數(shù)據(jù)的浮點型緩沖區(qū)
*/
private FloatBuffer vertexBuffer;
static float triangleCoords[] = { // 以逆時針順序;
0.0f, 0.622008459f, 0.0f, // top
-0.5f, -0.311004243f, 0.0f, // bottom left
0.5f, -0.311004243f, 0.0f // bottom right
};
// Set color with red, green, blue and alpha (opacity) values
float color[] = { 0.63671875f, 0.76953125f, 0.22265625f, 1.0f };
private final int mProgram;
private int mPositionHandle;
private int mColorHandle;
// Use to access and set the view transformation
private int mMVPMatrixHandle;
private final int vertexCount = triangleCoords.length / COORDS_PER_VERTEX;
private final int vertexStride = COORDS_PER_VERTEX * 4; // 4 bytes per vertex
public Triangle(){
// 初始化形狀中頂點坐標(biāo)數(shù)據(jù)的字節(jié)緩沖區(qū)
// 通過 allocateDirect 方法獲取到 DirectByteBuffer 實例
ByteBuffer byteBuffer = ByteBuffer.allocateDirect(
// 頂點坐標(biāo)個數(shù) * 坐標(biāo)數(shù)據(jù)類型 float 一個是 4 bytes
triangleCoords.length * 4
);
// 設(shè)置緩沖區(qū)使用設(shè)備硬件的原本字節(jié)順序進(jìn)行讀取;
byteBuffer.order(ByteOrder.nativeOrder());
// 因為 ByteBuffer 是將數(shù)據(jù)移進(jìn)移出通道的唯一方式使用北苟,這里使用 “as” 方法從 ByteBuffer 中獲得一個基本類型緩沖區(qū)(浮點緩沖區(qū))
vertexBuffer = byteBuffer.asFloatBuffer();
// 把頂點坐標(biāo)信息數(shù)組存儲到 FloatBuffer
vertexBuffer.put(triangleCoords);
// 設(shè)置從緩沖區(qū)的第一個位置開始讀取頂點坐標(biāo)信息
vertexBuffer.position(0);
// 加載編譯頂點渲染器
int vertexShader = MyGLRenderer2.loadShader(GLES20.GL_VERTEX_SHADER,
vertexShaderCode);
// 加載編譯片元渲染器
int fragmentShader = MyGLRenderer2.loadShader(GLES20.GL_FRAGMENT_SHADER,
fragmentShaderCode);
// create empty OpenGL ES Program
mProgram = GLES20.glCreateProgram();
// add the vertex shader to program
GLES20.glAttachShader(mProgram, vertexShader);
// add the fragment shader to program
GLES20.glAttachShader(mProgram, fragmentShader);
// creates OpenGL ES program executables
GLES20.glLinkProgram(mProgram);
}
public void draw(float[] mvpMatrix) { // pass in the calculated transformation matrix
// Add program to OpenGL ES environment
GLES20.glUseProgram(mProgram);
// get handle to vertex shader's vPosition member
mPositionHandle = GLES20.glGetAttribLocation(mProgram, "vPosition");
// Enable a handle to the triangle vertices
GLES20.glEnableVertexAttribArray(mPositionHandle);
// Prepare the triangle coordinate data
GLES20.glVertexAttribPointer(mPositionHandle, COORDS_PER_VERTEX,
GLES20.GL_FLOAT, false,
vertexStride, vertexBuffer);
// get handle to fragment shader's vColor member
mColorHandle = GLES20.glGetUniformLocation(mProgram, "vColor");
// Set color for drawing the triangle
GLES20.glUniform4fv(mColorHandle, 1, color, 0);
// Draw the triangle
GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, vertexCount);
// Disable vertex array
GLES20.glDisableVertexAttribArray(mPositionHandle);
// get handle to shape's transformation matrix
mMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix");
// Pass the projection and view transformation to the shader
GLES20.glUniformMatrix4fv(mMVPMatrixHandle, 1, false, mvpMatrix, 0);
// Draw the triangle
GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, vertexCount);
// Disable vertex array
GLES20.glDisableVertexAttribArray(mPositionHandle);
}
}
一旦我們正確地計算并應(yīng)用了投影變換和相機視角變換桩匪,我們的圖形就會以正確的比例繪制出來,它看上去會像是這樣:
文章中所有的代碼示例都已放在 Github 上友鼻,可以去項目 OpenGL-ES-Learning 中查看 傻昙。
現(xiàn)在,應(yīng)用已經(jīng)可以按正確的比例顯示圖形了彩扔,下面就來學(xué)習(xí)為圖形添加一些動作效果妆档。