OpenGL ES 運用投影與相機視角

文章中所有的代碼示例都已放在 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í)為圖形添加一些動作效果妆档。

>>>>Next>>>> : OpenGL ES 為視圖添加動作

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市借杰,隨后出現(xiàn)的幾起案子过吻,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,843評論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件纤虽,死亡現(xiàn)場離奇詭異乳绕,居然都是意外死亡,警方通過查閱死者的電腦和手機逼纸,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,538評論 3 392
  • 文/潘曉璐 我一進(jìn)店門洋措,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人杰刽,你說我怎么就攤上這事菠发。” “怎么了贺嫂?”我有些...
    開封第一講書人閱讀 163,187評論 0 353
  • 文/不壞的土叔 我叫張陵滓鸠,是天一觀的道長。 經(jīng)常有香客問我第喳,道長糜俗,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,264評論 1 292
  • 正文 為了忘掉前任曲饱,我火速辦了婚禮悠抹,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘扩淀。我一直安慰自己楔敌,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,289評論 6 390
  • 文/花漫 我一把揭開白布驻谆。 她就那樣靜靜地躺著卵凑,像睡著了一般。 火紅的嫁衣襯著肌膚如雪旺韭。 梳的紋絲不亂的頭發(fā)上氛谜,一...
    開封第一講書人閱讀 51,231評論 1 299
  • 那天掏觉,我揣著相機與錄音区端,去河邊找鬼。 笑死澳腹,一個胖子當(dāng)著我的面吹牛织盼,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播酱塔,決...
    沈念sama閱讀 40,116評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼沥邻,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了羊娃?” 一聲冷哼從身側(cè)響起唐全,我...
    開封第一講書人閱讀 38,945評論 0 275
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后邮利,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體弥雹,經(jīng)...
    沈念sama閱讀 45,367評論 1 313
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,581評論 2 333
  • 正文 我和宋清朗相戀三年延届,在試婚紗的時候發(fā)現(xiàn)自己被綠了剪勿。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,754評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡方庭,死狀恐怖厕吉,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情械念,我是刑警寧澤头朱,帶...
    沈念sama閱讀 35,458評論 5 344
  • 正文 年R本政府宣布,位于F島的核電站龄减,受9級特大地震影響髓窜,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜欺殿,卻給世界環(huán)境...
    茶點故事閱讀 41,068評論 3 327
  • 文/蒙蒙 一寄纵、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧脖苏,春花似錦程拭、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,692評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至亦歉,卻和暖如春恤浪,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背肴楷。 一陣腳步聲響...
    開封第一講書人閱讀 32,842評論 1 269
  • 我被黑心中介騙來泰國打工水由, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人赛蔫。 一個月前我還...
    沈念sama閱讀 47,797評論 2 369
  • 正文 我出身青樓砂客,卻偏偏與公主長得像,于是被迫代替她去往敵國和親呵恢。 傳聞我的和親對象是個殘疾皇子鞠值,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,654評論 2 354

推薦閱讀更多精彩內(nèi)容