Opengl ES系列學習--頂點着色器

     本節我們繼續來看一下《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,着色器程序就結束了。

     片段着色器和之前的基本一樣,我們就不展開了,大家可以自己閱讀。

     好了,繼續開車吧!!!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章