本節我們繼續來看一下《OPENGL ES 3.0編程指南 原書第2版(中文版)》書中第8章的內容,PDF下載地址:OPENGL ES 3.0編程指南 原書第2版(中文版),代碼下載地址:Opengl ES Source Code。該書中好像沒有對Demo的代碼進行講解,全書的重點全部都集中在Opengl ES本身,而實現出來的實例代碼講解的很少,作爲初學者,理解起來確實比較費勁,這也是我爲什麼推薦大家先學習2.0的原因,2.0的那本書對實現出來的Demo的代碼全部進行了講解,我們就知道要實現一個功能,需要什麼樣的步驟,每一步是什麼意思,這樣比較清晰。
還是來看一下本節最終實現的效果,效果圖如下,是一個純色的立方體,在不斷的旋轉:
我們對照代碼來看一下,先來看一下SimpleVertexShaderRenderer類,源碼如下:
public class SimpleVertexShaderRenderer implements GLSurfaceView.Renderer {
private Context mContext;
private final float[] projectionMatrix = new float[16];
private final float[] viewMatrix = new float[16];
private final float[] viewProjectionMatrix = new float[16];
private FloatBuffer mMatrixFloatBuffer;
///
// Constructor
//
public SimpleVertexShaderRenderer(Context context) {
mContext = context;
}
///
// Initialize the shader and program object
//
public void onSurfaceCreated(GL10 glUnused, EGLConfig config) {
String vShaderStr = ESShader.readShader(mContext, "chapter8/vertexShader.vert");
String fShaderStr = ESShader.readShader(mContext, "chapter8/fragmentShader.frag");
// Load the shaders and get a linked program object
mProgramObject = ESShader.loadProgram(vShaderStr, fShaderStr);
// Get the uniform locations
mMVPLoc = GLES30.glGetUniformLocation(mProgramObject, "u_mvpMatrix");
// Generate the vertex data
mCube.genCube(0.6f);
// Starting rotation angle for the cube
mAngle = 45.0f;
GLES30.glClearColor(1.0f, 1.0f, 1.0f, 0.0f);
mMatrixFloatBuffer = ByteBuffer.allocateDirect(16 * 4)
.order(ByteOrder.nativeOrder()).asFloatBuffer();
}
private void update() {
if (mLastTime == 0) {
mLastTime = SystemClock.uptimeMillis();
}
long curTime = SystemClock.uptimeMillis();
long elapsedTime = curTime - mLastTime;
float deltaTime = elapsedTime / 1000.0f;
mLastTime = curTime;
// ESTransform perspective = new ESTransform();
// ESTransform modelview = new ESTransform();
float aspect;
// Compute a rotation angle based on time to rotate the cube
mAngle += (deltaTime * 40.0f);
if (mAngle >= 360.0f) {
mAngle -= 360.0f;
}
// Compute the window aspect ratio
aspect = (float) mWidth / (float) mHeight;
// Generate a perspective matrix with a 60 degree FOV
// perspective.matrixLoadIdentity();
// Matrix.setIdentityM(viewMatrix, 0);
// perspective.perspective(60.0f, aspect, 1.0f, 20.0f);
Matrix.perspectiveM(projectionMatrix, 0, 60.0f, aspect, 1.0f, 20.0f);
// Generate a model view matrix to rotate/translate the cube
// modelview.matrixLoadIdentity();
Matrix.setIdentityM(viewMatrix, 0);
// Translate away from the viewer
// modelview.translate(0.0f, 0.0f, -2.0f);
Matrix.translateM(viewMatrix, 0, 0.0f, 0.0f, -2.0f);
// Rotate the cube
// modelview.rotate(mAngle, 1.0f, 0.0f, 1.0f);
Matrix.rotateM(viewMatrix, 0, mAngle, 1.0f, 0.0f, 1.0f);
// Compute the final MVP by multiplying the
// modevleiw and perspective matrices together
// mMVPMatrix.matrixMultiply(modelview.get(), perspective.get());
Matrix.multiplyMM(viewProjectionMatrix, 0, projectionMatrix, 0, viewMatrix, 0);
mMatrixFloatBuffer.clear();
mMatrixFloatBuffer.put(viewProjectionMatrix).position(0);
}
///
// Draw a triangle using the shader pair created in onSurfaceCreated()
//
public void onDrawFrame(GL10 glUnused) {
update();
// Set the viewport
GLES30.glViewport(0, 0, mWidth, mHeight);
// Clear the color buffer
GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT);
// Use the program object
GLES30.glUseProgram(mProgramObject);
// Load the vertex data
GLES30.glVertexAttribPointer(0, 3, GLES30.GL_FLOAT, false,
0, mCube.getVertices());
GLES30.glEnableVertexAttribArray(0);
// Set the vertex color to red
GLES30.glVertexAttrib4f(1, 1.0f, 0.0f, 0.0f, 1.0f);
// Load the MVP matrix
GLES30.glUniformMatrix4fv(mMVPLoc, 1, false,
mMatrixFloatBuffer);
// Draw the cube
GLES30.glDrawElements(GLES30.GL_TRIANGLES, mCube.getNumIndices(),
GLES30.GL_UNSIGNED_SHORT, mCube.getIndices());
}
///
// Handle surface changes
//
public void onSurfaceChanged(GL10 glUnused, int width, int height) {
mWidth = width;
mHeight = height;
}
// Handle to a program object
private int mProgramObject;
// Uniform locations
private int mMVPLoc;
// Vertex data
// private ESShapes mCube = new ESShapes();
private Cube mCube = new Cube();
// Rotation angle
private float mAngle;
// MVP matrix
private ESTransform mMVPMatrix = new ESTransform();
// Additional Member variables
private int mWidth;
private int mHeight;
private long mLastTime = 0;
}
該類的代碼我已經修改過了,修改的原因就是原來的實現過於複雜,理解起來不方便,而修改後的則非常清晰。修改點有兩處:1、去掉了ESShapes mCube成員變量,自己新增加了一個Cube類,對應的成員變量是Cube mCube,該類的作用就是用來存儲立方體的頂點和索引數據的,後面分析到時,我們就可以看到;2、去掉了update方法中所有矩陣的操作,代替使用系統Matrix類提供的API來進行矩陣計算。
先來看一下該類中的成員變量:mProgramObject表示編譯鏈接成功的program的ID;mMVPLoc表示着色器程序中定義的投影矩陣變量;mAngle表示立方體旋轉的角度;mLastTime表示最後一次的更新時間戳;mWidth、mHeight表示視窗口的寬度和高度;最後來看一下mCube和mMVPMatrix。原來的mCube是ESShapes類型,在當前的Render渲染類中最主要就是調用mCube.genCube(0.6f)生成Cube立方體的頂點數據,ESShapes類的源碼如下:
public class ESShapes {
public ESShapes() {
}
public int genSphere(int numSlices, float radius) {
int i;
int j;
int numParallels = numSlices;
int numVertices = (numParallels + 1) * (numSlices + 1);
int numIndices = numParallels * numSlices * 6;
float angleStep = ((2.0f * (float) Math.PI) / numSlices);
// Allocate memory for buffers
mVertices = ByteBuffer.allocateDirect(numVertices * 3 * 4)
.order(ByteOrder.nativeOrder()).asFloatBuffer();
mNormals = ByteBuffer.allocateDirect(numVertices * 3 * 4)
.order(ByteOrder.nativeOrder()).asFloatBuffer();
mTexCoords = ByteBuffer.allocateDirect(numVertices * 2 * 4)
.order(ByteOrder.nativeOrder()).asFloatBuffer();
mIndices = ByteBuffer.allocateDirect(numIndices * 2)
.order(ByteOrder.nativeOrder()).asShortBuffer();
for (i = 0; i < numParallels + 1; i++) {
for (j = 0; j < numSlices + 1; j++) {
int vertex = (i * (numSlices + 1) + j) * 3;
mVertices
.put(vertex + 0,
(float) (radius
* Math.sin(angleStep * (float) i) * Math
.sin(angleStep * (float) j)));
mVertices.put(vertex + 1,
(float) (radius * Math.cos(angleStep * (float) i)));
mVertices
.put(vertex + 2,
(float) (radius
* Math.sin(angleStep * (float) i) * Math
.cos(angleStep * (float) j)));
mNormals.put(vertex + 0, mVertices.get(vertex + 0) / radius);
mNormals.put(vertex + 1, mVertices.get(vertex + 1) / radius);
mNormals.put(vertex + 2, mVertices.get(vertex + 2) / radius);
int texIndex = (i * (numSlices + 1) + j) * 2;
mTexCoords.put(texIndex + 0, (float) j / (float) numSlices);
mTexCoords.put(texIndex + 1, (1.0f - (float) i)
/ (float) (numParallels - 1));
}
}
int index = 0;
for (i = 0; i < numParallels; i++) {
for (j = 0; j < numSlices; j++) {
mIndices.put(index++, (short) (i * (numSlices + 1) + j));
mIndices.put(index++, (short) ((i + 1) * (numSlices + 1) + j));
mIndices.put(index++,
(short) ((i + 1) * (numSlices + 1) + (j + 1)));
mIndices.put(index++, (short) (i * (numSlices + 1) + j));
mIndices.put(index++,
(short) ((i + 1) * (numSlices + 1) + (j + 1)));
mIndices.put(index++, (short) (i * (numSlices + 1) + (j + 1)));
}
}
mNumIndices = numIndices;
return numIndices;
}
public int genCube(float scale) {
int i;
int numVertices = 24;
int numIndices = 36;
float[] cubeVerts = {
-0.5f, -0.5f, -0.5f,
-0.5f, -0.5f, 0.5f,
0.5f, -0.5f, 0.5f,
0.5f, -0.5f, -0.5f,
-0.5f, 0.5f, -0.5f,
-0.5f, 0.5f, 0.5f,
0.5f, 0.5f, 0.5f,
0.5f, 0.5f, -0.5f,
-0.5f, -0.5f, -0.5f,
-0.5f, 0.5f, -0.5f,
0.5f, 0.5f, -0.5f,
0.5f, -0.5f, -0.5f,
-0.5f, -0.5f, 0.5f,
-0.5f, 0.5f, 0.5f,
0.5f, 0.5f, 0.5f,
0.5f, -0.5f, 0.5f,
-0.5f, -0.5f, -0.5f,
-0.5f, -0.5f, 0.5f,
-0.5f, 0.5f, 0.5f,
-0.5f, 0.5f, -0.5f,
0.5f, -0.5f, -0.5f,
0.5f, -0.5f, 0.5f,
0.5f, 0.5f, 0.5f,
0.5f, 0.5f, -0.5f,
};
float[] cubeNormals = {0.0f, -1.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f,
-1.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f,
0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, -1.0f,
0.0f, 0.0f, -1.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, -1.0f, 0.0f,
0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f,
1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f,
-1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f,
0.0f, 0.0f, 1.0f, 0.0f, 0.0f,
};
// float[] cubeTex = {0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f, 0.0f,
// 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f,
// 0.0f, 1.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f,
// 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f,
// 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f, 0.0f,
// };
// Allocate memory for buffers
mVertices = ByteBuffer.allocateDirect(numVertices * 3 * 4)
.order(ByteOrder.nativeOrder()).asFloatBuffer();
// mNormals = ByteBuffer.allocateDirect(numVertices * 3 * 4)
// .order(ByteOrder.nativeOrder()).asFloatBuffer();
// mTexCoords = ByteBuffer.allocateDirect(numVertices * 2 * 4)
// .order(ByteOrder.nativeOrder()).asFloatBuffer();
mIndices = ByteBuffer.allocateDirect(numIndices * 2)
.order(ByteOrder.nativeOrder()).asShortBuffer();
mVertices.put(cubeVerts).position(0);
for (i = 0; i < numVertices * 3; i++) {
mVertices.put(i, mVertices.get(i) * scale);
}
// mNormals.put(cubeNormals).position(0);
// mTexCoords.put(cubeTex).position(0);
short[] cubeIndices = {0, 2, 1, 0, 3, 2, 4, 5, 6, 4, 6, 7, 8, 9, 10,
8, 10, 11, 12, 15, 14, 12, 14, 13, 16, 17, 18, 16, 18, 19, 20,
23, 22, 20, 22, 21
};
mIndices.put(cubeIndices).position(0);
mNumIndices = numIndices;
return numIndices;
}
public FloatBuffer getVertices() {
return mVertices;
}
public FloatBuffer getNormals() {
return mNormals;
}
public FloatBuffer getTexCoords() {
return mTexCoords;
}
public ShortBuffer getIndices() {
return mIndices;
}
public int getNumIndices() {
return mNumIndices;
}
// Member variables
private FloatBuffer mVertices;
private FloatBuffer mNormals;
private FloatBuffer mTexCoords;
private ShortBuffer mIndices;
private int mNumIndices;
}
構造方法是我自己加入的,來看一下它的成員變量:mVertices表示立方體的頂點數據;mNormals、mTexCoords在當前的立方體實例中沒有用到,先不管;mIndices表示索引數據;mNumIndices表示索引個數;我們主要來看一下genCube方法,cubeVerts局部變量指定了所有頂點的位置,負值都是0.5個單位,正值也是0.5個單位,所以立方體的邊長是1個單位,而對每個頂點位置和scale相乘,就可以把立方體縮放;該方法中和mNormals、mTexCoords相關的邏輯全部無用,我都註釋掉了,cubeIndices描述了所有索引數據的值,它和頂點數據配合一起,就可以表示出我們的立方體,但是大家看到,頂點數據太多了,我自己開始也想嘗試把這些頂點全部畫出來,以理解原來的程序如何用這麼多頂點表示一個立方體,但是大腦堆棧嚴重崩潰,根本無法想象這麼多頂點到底是怎麼樣分佈了,而理解清楚了這個類所有的作用,我們要實現一個立方體,根本不需要這麼多頂點,所以我自己寫了一個Cube類,源碼如下:
public class Cube {
private final float[] cubeVerts = {
-0.5f, 0.5f, 0.5f, // (0) Top-left near
0.5f, 0.5f, 0.5f, // (1) Top-right near
-0.5f, -0.5f, 0.5f, // (2) Bottom-left near
0.5f, -0.5f, 0.5f, // (3) Bottom-right near
-0.5f, 0.5f, -0.5f, // (4) Top-left far
0.5f, 0.5f, -0.5f, // (5) Top-right far
-0.5f, -0.5f, -0.5f, // (6) Bottom-left far
0.5f, -0.5f, -0.5f, // (7) Bottom-right far
};
private final short[] cubeIndices = new short[]{
// Front
0, 2, 1,
1, 2, 3,
// Back
4, 7, 5,
5, 7, 6,
// Left
0, 2, 4,
4, 2, 6,
// Right
5, 7, 1,
1, 7, 3,
// Top
5, 1, 4,
4, 1, 0,
// Bottom
6, 2, 7,
7, 2, 3
};
public Cube() {
}
public int genCube(float scale) {
// Allocate memory for buffers
mVertices = ByteBuffer.allocateDirect(cubeVerts.length * 4)
.order(ByteOrder.nativeOrder()).asFloatBuffer();
mIndices = ByteBuffer.allocateDirect(cubeIndices.length * 2)
.order(ByteOrder.nativeOrder()).asShortBuffer();
mVertices.put(cubeVerts).position(0);
for (int i = 0; i < cubeVerts.length; i++) {
mVertices.put(i, mVertices.get(i) * scale);
}
mIndices.put(cubeIndices).position(0);
mNumIndices = cubeIndices.length;
return mNumIndices;
}
public FloatBuffer getVertices() {
return mVertices;
}
public ShortBuffer getIndices() {
return mIndices;
}
public int getNumIndices() {
return mNumIndices;
}
// Member variables
private FloatBuffer mVertices;
private ShortBuffer mIndices;
private int mNumIndices;
}
很簡單,我們需要把一個立方體描述清楚,只需要八個頂點就可以了,然後按照捲曲三角形逆時針的順序指定它們的索引值,這樣就足以畫出來這個立方體了,所有索引數據和Opengl ES系列學習--增加天空盒一節中講的完全相同,如果大家對這些頂點索引值還有什麼疑問,請先回頭搞清楚。該類中的genCube方法也很清晰,我們就不分析了。
回到SimpleVertexShaderRenderer類當中,onSurfaceCreated方法中,調用mCube.genCube(0.6f)邏輯來生成一個縮放0.6倍的立方體,所有頂點位置數據都存儲在mVertices成員變量中,所有頂點索引數據存儲在mIndices成員變量中;最後給成員變量mMatrixFloatBuffer賦值並分配存儲空間,因爲它存儲我們對矩陣運算完的結果,矩陣都是4 * 4的,所以有16位分量,每個分量都是float類型,所以分配空間時需要乘以4。
再來看update方法,原來的邏輯全部都是通過perspective、modelview兩個ESTransform類型的局部變量進行運算來實現矩陣操作的,ESTransform源碼如下:
public class ESTransform {
public ESTransform() {
mMatrixFloatBuffer = ByteBuffer.allocateDirect(16 * 4)
.order(ByteOrder.nativeOrder()).asFloatBuffer();
}
public void scale(float sx, float sy, float sz) {
mMatrix[0 * 4 + 0] *= sx;
mMatrix[0 * 4 + 1] *= sx;
mMatrix[0 * 4 + 2] *= sx;
mMatrix[0 * 4 + 3] *= sx;
mMatrix[1 * 4 + 0] *= sy;
mMatrix[1 * 4 + 1] *= sy;
mMatrix[1 * 4 + 2] *= sy;
mMatrix[1 * 4 + 3] *= sy;
mMatrix[2 * 4 + 0] *= sz;
mMatrix[2 * 4 + 1] *= sz;
mMatrix[2 * 4 + 2] *= sz;
mMatrix[2 * 4 + 3] *= sz;
}
public void translate(float tx, float ty, float tz) {
mMatrix[3 * 4 + 0] += (mMatrix[0 * 4 + 0] * tx + mMatrix[1 * 4 + 0]
* ty + mMatrix[2 * 4 + 0] * tz);
mMatrix[3 * 4 + 1] += (mMatrix[0 * 4 + 1] * tx + mMatrix[1 * 4 + 1]
* ty + mMatrix[2 * 4 + 1] * tz);
mMatrix[3 * 4 + 2] += (mMatrix[0 * 4 + 2] * tx + mMatrix[1 * 4 + 2]
* ty + mMatrix[2 * 4 + 2] * tz);
mMatrix[3 * 4 + 3] += (mMatrix[0 * 4 + 3] * tx + mMatrix[1 * 4 + 3]
* ty + mMatrix[2 * 4 + 3] * tz);
}
public void rotate(float angle, float x, float y, float z) {
float sinAngle, cosAngle;
float mag = (float) Math.sqrt((double) (x * x + y * y + z * z));
sinAngle = (float) Math.sin((double) (angle * Math.PI / 180.0));
cosAngle = (float) Math.cos((double) (angle * Math.PI / 180.0));
if (mag > 0.0f) {
float xx, yy, zz, xy, yz, zx, xs, ys, zs;
float oneMinusCos;
float[] rotMat = new float[16];
x /= mag;
y /= mag;
z /= mag;
xx = x * x;
yy = y * y;
zz = z * z;
xy = x * y;
yz = y * z;
zx = z * x;
xs = x * sinAngle;
ys = y * sinAngle;
zs = z * sinAngle;
oneMinusCos = 1.0f - cosAngle;
rotMat[0 * 4 + 0] = (oneMinusCos * xx) + cosAngle;
rotMat[0 * 4 + 1] = (oneMinusCos * xy) - zs;
rotMat[0 * 4 + 2] = (oneMinusCos * zx) + ys;
rotMat[0 * 4 + 3] = 0.0F;
rotMat[1 * 4 + 0] = (oneMinusCos * xy) + zs;
rotMat[1 * 4 + 1] = (oneMinusCos * yy) + cosAngle;
rotMat[1 * 4 + 2] = (oneMinusCos * yz) - xs;
rotMat[1 * 4 + 3] = 0.0F;
rotMat[2 * 4 + 0] = (oneMinusCos * zx) - ys;
rotMat[2 * 4 + 1] = (oneMinusCos * yz) + xs;
rotMat[2 * 4 + 2] = (oneMinusCos * zz) + cosAngle;
rotMat[2 * 4 + 3] = 0.0F;
rotMat[3 * 4 + 0] = 0.0F;
rotMat[3 * 4 + 1] = 0.0F;
rotMat[3 * 4 + 2] = 0.0F;
rotMat[3 * 4 + 3] = 1.0F;
matrixMultiply(rotMat, mMatrix);
}
}
public void frustum(float left, float right, float bottom, float top,
float nearZ, float farZ) {
float deltaX = right - left;
float deltaY = top - bottom;
float deltaZ = farZ - nearZ;
float[] frust = new float[16];
if ((nearZ <= 0.0f) || (farZ <= 0.0f) || (deltaX <= 0.0f)
|| (deltaY <= 0.0f) || (deltaZ <= 0.0f)) {
return;
}
frust[0 * 4 + 0] = 2.0f * nearZ / deltaX;
frust[0 * 4 + 1] = frust[0 * 4 + 2] = frust[0 * 4 + 3] = 0.0f;
frust[1 * 4 + 1] = 2.0f * nearZ / deltaY;
frust[1 * 4 + 0] = frust[1 * 4 + 2] = frust[1 * 4 + 3] = 0.0f;
frust[2 * 4 + 0] = (right + left) / deltaX;
frust[2 * 4 + 1] = (top + bottom) / deltaY;
frust[2 * 4 + 2] = -(nearZ + farZ) / deltaZ;
frust[2 * 4 + 3] = -1.0f;
frust[3 * 4 + 2] = -2.0f * nearZ * farZ / deltaZ;
frust[3 * 4 + 0] = frust[3 * 4 + 1] = frust[3 * 4 + 3] = 0.0f;
matrixMultiply(frust, mMatrix);
}
public void perspective(float fovy, float aspect, float nearZ, float farZ) {
float frustumW, frustumH;
frustumH = (float) Math.tan(fovy / 360.0 * Math.PI) * nearZ;
frustumW = frustumH * aspect;
frustum(-frustumW, frustumW, -frustumH, frustumH, nearZ, farZ);
}
public void ortho(float left, float right, float bottom, float top,
float nearZ, float farZ) {
float deltaX = right - left;
float deltaY = top - bottom;
float deltaZ = farZ - nearZ;
float[] orthoMat = makeIdentityMatrix();
if ((deltaX == 0.0f) || (deltaY == 0.0f) || (deltaZ == 0.0f)) {
return;
}
orthoMat[0 * 4 + 0] = 2.0f / deltaX;
orthoMat[3 * 4 + 0] = -(right + left) / deltaX;
orthoMat[1 * 4 + 1] = 2.0f / deltaY;
orthoMat[3 * 4 + 1] = -(top + bottom) / deltaY;
orthoMat[2 * 4 + 2] = -2.0f / deltaZ;
orthoMat[3 * 4 + 2] = -(nearZ + farZ) / deltaZ;
matrixMultiply(orthoMat, mMatrix);
}
public void matrixMultiply(float[] srcA, float[] srcB) {
float[] tmp = new float[16];
int i;
for (i = 0; i < 4; i++) {
tmp[i * 4 + 0] = (srcA[i * 4 + 0] * srcB[0 * 4 + 0])
+ (srcA[i * 4 + 1] * srcB[1 * 4 + 0])
+ (srcA[i * 4 + 2] * srcB[2 * 4 + 0])
+ (srcA[i * 4 + 3] * srcB[3 * 4 + 0]);
tmp[i * 4 + 1] = (srcA[i * 4 + 0] * srcB[0 * 4 + 1])
+ (srcA[i * 4 + 1] * srcB[1 * 4 + 1])
+ (srcA[i * 4 + 2] * srcB[2 * 4 + 1])
+ (srcA[i * 4 + 3] * srcB[3 * 4 + 1]);
tmp[i * 4 + 2] = (srcA[i * 4 + 0] * srcB[0 * 4 + 2])
+ (srcA[i * 4 + 1] * srcB[1 * 4 + 2])
+ (srcA[i * 4 + 2] * srcB[2 * 4 + 2])
+ (srcA[i * 4 + 3] * srcB[3 * 4 + 2]);
tmp[i * 4 + 3] = (srcA[i * 4 + 0] * srcB[0 * 4 + 3])
+ (srcA[i * 4 + 1] * srcB[1 * 4 + 3])
+ (srcA[i * 4 + 2] * srcB[2 * 4 + 3])
+ (srcA[i * 4 + 3] * srcB[3 * 4 + 3]);
}
mMatrix = tmp;
}
public void matrixLoadIdentity() {
for (int i = 0; i < 16; i++) {
mMatrix[i] = 0.0f;
}
mMatrix[0 * 4 + 0] = 1.0f;
mMatrix[1 * 4 + 1] = 1.0f;
mMatrix[2 * 4 + 2] = 1.0f;
mMatrix[3 * 4 + 3] = 1.0f;
}
private float[] makeIdentityMatrix() {
float[] result = new float[16];
for (int i = 0; i < 16; i++) {
result[i] = 0.0f;
}
result[0 * 4 + 0] = 1.0f;
result[1 * 4 + 1] = 1.0f;
result[2 * 4 + 2] = 1.0f;
result[3 * 4 + 3] = 1.0f;
return result;
}
public FloatBuffer getAsFloatBuffer() {
mMatrixFloatBuffer.put(mMatrix).position(0);
return mMatrixFloatBuffer;
}
public float[] get() {
return mMatrix;
}
private float[] mMatrix = new float[16];
private FloatBuffer mMatrixFloatBuffer;
}
該類中的方法都是基本的矩陣運算操作,從這裏也可以看到,作者的功底還是比較厚的,一般我們都只是會使用這些而已,從來沒有關心底層如何實現,而作者能手動寫出這些運算邏輯,那真不是蓋的!!我對這些底層操作也不理解,也懶,所以就把這些操作全部註釋掉了,Matrix系統類中提供了所有矩陣操作的API,所以update方法中,我就直接使用系統API來進行矩陣運算,以達到我們的目的了。
先根據curTime - mLastTime的差值計算出耗時時間,再乘以40.0f增大旋轉角度。如果if (mAngle >= 360.0f) 角度大於360度,則將它減去-360度,相當於完整一圈。接着計算窗口縱橫比,這個值我們在之前也都已經講過了,使用投影矩陣時,需要使用,而且必須將mWidth、mHeight全部強轉爲float進行計算,否則得到的結果將會是0。接着調用Matrix.perspectiveM(projectionMatrix, 0, 60.0f, aspect, 1.0f, 20.0f)生成投影矩陣,然後分別調用Matrix.setIdentityM(viewMatrix, 0)生成單位矩陣、Matrix.translateM(viewMatrix, 0, 0.0f, 0.0f, -2.0f)對單位矩陣進行平移、Matrix.rotateM(viewMatrix, 0, mAngle, 1.0f, 0.0f, 1.0f)對平移後的結果進行旋轉,然後Matrix.multiplyMM(viewProjectionMatrix, 0, projectionMatrix, 0, viewMatrix, 0)把投影矩陣和viewMatrix結果相乘,得到的結果存儲在viewProjectionMatrix矩陣當中,最後將矩陣運算得到的數據存儲在mMatrixFloatBuffer當中,後面我們只需要通過API將矩陣數據賦值給着色器中的u_mvpMatrix變量就可以了。
再來看onDrawFrame方法,先調用update對旋轉角度進行計算,同時將矩陣運算結果更新到mMatrixFloatBuffer成員變量當中,然後調用GLES30.glViewport指定視窗口大小,調用GLES30.glVertexAttribPointer(0, 3, GLES30.GL_FLOAT, false, 0, mCube.getVertices())來給傳遞頂點數據,它在onSurfaceCreated方法當中通過調用genCube成員方法已經生成好頂點位置數據和索引數據了,這裏只需要獲取就可以了,GLES30.glVertexAttrib4f(1, 1.0f, 0.0f, 0.0f, 1.0f)給通過location指定的ID值爲1的變量賦值,也就是顏色,所以我們看到的是一個紅色的立方體;GLES30.glUniformMatrix4fv(mMVPLoc, 1, false, mMatrixFloatBuffer)給矩陣變量賦值,矩陣變量已經在onDrawFrame方法開始時計算好了,最後調用GLES30.glDrawElements系統API以三角形GLES30.GL_TRIANGLES的方式傳入mCube.getIndices()所有索引數據來繪製三角形,我們的立方體就顯示出來了。
最後來看一下app\src\main\assets\chapter8目錄下的vertexShader.vert頂點着色器,源碼如下:
#version 300 es
uniform mat4 u_mvpMatrix;
layout(location = 0) in vec4 a_position;
layout(location = 1) in vec4 a_color;
out vec4 v_color;
void main()
{
v_color = a_color;
gl_Position = u_mvpMatrix * a_position;
}
因爲這裏通過location關鍵字指定了0和1兩個變量,所以在onDrawFrame方法中我們通過API給着色器變量賦值時,纔可以傳入0和1的ID,矩陣變量u_mvpMatrix還是傳統的方式,先通過API獲取到它的ID,也就是mMVPLoc成員變量,然後再調用GLES30.glUniformMatrix4fv來給它賦值,最後在main函數中執行u_mvpMatrix * a_position將它運用在頂點屬性上,賦值給內建變量gl_Position,着色器程序就結束了。
片段着色器和之前的基本一樣,我們就不展開了,大家可以自己閱讀。
好了,繼續開車吧!!!