《OpenGL從入門到放棄03 》相機和視圖

之前文章:

《OpenGL從入門到放棄01 》一些基本概念
《OpenGL從入門到放棄02 》GLSurfaceView和Renderer


上一篇文章我們看到三角形形狀跟預期有點不一樣


這是OpenGL坐標系的問題,對坐標系不明白可以看第一節(jié),這一節(jié)我們通過投影和相機視圖來解決這個問題。

先來個概念,之后再上代碼

相機

生活中我們拍照,你站的高度,拿相機的位置,姿勢不同,拍出來的照片也就不一樣
在Android OpenGLES程序中,我們可以通過以下方法來進行相機設置:

Matrix.setLookAtM (float[] rm,      //接收相機變換矩陣
                int rmOffset,       //變換矩陣的起始位置(偏移量)
                float eyeX,float eyeY, float eyeZ,   //相機位置
                float centerX,float centerY,float centerZ,  //觀測點位置
                float upX,float upY,float upZ)  //up向量在xyz上的分量

相機位置:相機的位置是比較好理解的,就是相機在3D空間里面的坐標點,

相機觀察方向:相機的觀察方向,對應center向量,表示的是相機鏡頭的朝向,你可以朝前拍、朝后拍、也可以朝左朝右,或者其他的方向。

相機UP方向:相機的UP方向,可以理解為相機頂端指向的方向。比如你把相機斜著拿著,拍出來的照片就是斜著的,你倒著拿著,拍出來的就是倒著的。

如圖:center的X,Y,Z表示相機的正方向的方向向量,up的X,Y,Z表示相機的上方向的方向向量。因為空間中想要確定一個具體朝向,必須要有兩組向量,所以此函數(shù)需要一個點和兩組向量來確定相機的位置和具體朝向。

投影

使用OpenGl繪制的3D圖形,需要展示在移動端2D設備上,這就需要投影。
OpenGl ES中有兩種投影方式:一種是正交投影,一種是透視投影

正交投影

投影物體的大小不會隨觀察點的遠近而發(fā)生變化,我們可以使用下面方法來執(zhí)行正交投影

Matrix.orthoM (float[] m,           //接收正交投影的變換矩陣
            int mOffset,        //變換矩陣的起始位置(偏移量)
            float left,         //相對觀察點近面的左邊距
            float right,        //相對觀察點近面的右邊距
            float bottom,       //相對觀察點近面的下邊距
            float top,          //相對觀察點近面的上邊距
            float near,         //相對觀察點近面距離
            float far)          //相對觀察點遠面距離

透視投影

隨觀察點的距離變化而變化,觀察點越遠,視圖越小,反之越大,我們可以通過如下方法來設置透視投影:

Matrix.frustumM (float[] m,         //接收透視投影的變換矩陣
            int mOffset,        //變換矩陣的起始位置(偏移量)
            float left,         //相對觀察點近面的左邊距
            float right,        //相對觀察點近面的右邊距
            float bottom,       //相對觀察點近面的下邊距
            float top,          //相對觀察點近面的上邊距
            float near,         //相對觀察點近面距離
            float far)          //相對觀察點遠面距離

只有繪制在近平面和遠平面之間的內容,才能被用戶看到

轉換矩陣(變換矩陣)

轉換矩陣用來做什么的呢?
是否記得上面我們繪制的圖形坐標需要轉換為OpenGl中能處理的小端字節(jié)序(LittleEdian)
沒錯,轉換矩陣就是用來將數(shù)據(jù)轉為OpenGl ES可用的數(shù)據(jù)字節(jié),我們將相機視圖矩陣和投影矩陣相乘,便得到一個轉換矩陣,然后我們再將此矩陣傳給頂點著色器,具體使用方法及參數(shù)說明如下:

Matrix.multiplyMM (float[] result, //接收相乘結果
            int resultOffset,  //接收矩陣的起始位置(偏移量)
            float[] lhs,       //左矩陣
            int lhsOffset,     //左矩陣的起始位置(偏移量)
            float[] rhs,       //右矩陣
            int rhsOffset)     //右矩陣的起始位置(偏移量)

下面簡單講解下如何使用投影和相機視圖來實現(xiàn)矩陣變換并傳遞給頂點著色器

實戰(zhàn)

1、定義一個投影矩陣、相機矩陣、變換矩陣

private final float[] mMVPMatrix = new float[16];      //變換矩陣(投影矩陣*相機矩陣的結果,最終要傳遞給頂點著色器)
private final float[] mProjectionMatrix = new float[16]; //投影矩陣
private final float[] mViewMatrix = new float[16]; //相機位置矩陣

2、onSurfaceChanged 中進行設置

public void onSurfaceChanged(GL10 gl, int width, int height) {
        // 設置繪圖的窗口(可以理解成在畫布上劃出一塊區(qū)域來畫圖)
        GLES20.glViewport(0,0,width,height);
        /**投影和相機視圖相關**/
        float ratio = (float) width / height;
        //設置正交投影
//        Matrix.orthoM (mProjectionMatrix, 0, -ratio, ratio, -1, 2, 3, 7);
        Matrix.orthoM (mProjectionMatrix, 0,0,width, height, 0, -width, width);//這個投影會跟屏幕坐標關聯(lián)上
        //設置透視投影(觀察點越遠,視圖越小),這個投影矩陣被應用于對象坐標在onDrawFrame()方法中
//        Matrix.frustumM(mProjectionMatrix, 0, -ratio, ratio, -1, 2, 3, 7);

        //設置相機位置
        Matrix.setLookAtM(mViewMatrix, 0, 0, 0, 7f, 0f, 0f, 0f, 0f, 1.0f, 1.0f);
        //計算變換矩陣,最終mMVPMatrix要傳遞給頂點著色器
        Matrix.multiplyMM(mMVPMatrix,0,mProjectionMatrix,0,mViewMatrix,0);

    }

至此就得到了一個變換矩陣 mMVPMatrix,最終需要傳遞給頂點著色器

3、修改三角形代碼

在上一節(jié)代碼的基礎上修改

1、修改頂點著色器

// 頂點著色器的腳本
    String vertexShaderCode =
            "uniform mat4 uMVPMatrix;" +         //接收傳入的轉換矩陣
            " attribute vec4 vPosition;" +      //接收傳入的頂點
                    " void main() {" +
                        "  gl_Position = uMVPMatrix * vPosition;" +  //矩陣變換計算之后的位置
                    " }";

2、增加變量

    //變換矩陣句柄
    private int mMVPMatrixHandle;
    //變換矩陣,提供set方法
    private float[] mvpMatrix = new float[16];
    public void setMvpMatrix(float[] mvpMatrix) {
        this.mvpMatrix = mvpMatrix;
    }

3、修改draw方法

draw方法主要是將我們的數(shù)據(jù)傳遞給著色器,既然頂點著色器增加了矩陣變量,那么我們需要把變換矩陣傳遞給頂點著色器

        // 獲取變換矩陣的句柄
        mMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix");
        // 將投影和視圖轉換傳遞給著色器,可以理解為給頂點著色器中uMVPMatrix這個變量賦值為mvpMatrix
        GLES20.glUniformMatrix4fv(mMVPMatrixHandle, 1, false, mvpMatrix, 0);

4、在onDrawFrame中繪制

    mGlTriangle02.setMvpMatrix(mMVPMatrix);
    mGlTriangle02.draw();

mGlTriangle02在onSurfaceCreated中new出來,然后就可以看到效果了

image.png

看起來差不多了,可以看到頂點位置不變的情況下,使用投影跟相機視圖確實能改變2d效果。

最后貼下三角形的全部代碼

public class GLTriangle02{

    // 頂點著色器的腳本
    String vertexShaderCode =
            "uniform mat4 uMVPMatrix;" +         //接收傳入的轉換矩陣
            " attribute vec4 vPosition;" +      //接收傳入的頂點
                    " void main() {" +
                        "  gl_Position = uMVPMatrix * vPosition;" +  //矩陣變換計算之后的位置
                    " }";

    // 片元著色器的腳本
    String fragmentShaderCode =
            " precision mediump float;" +  // 聲明float類型的精度為中等(精度越高越耗資源)
                    " uniform vec4 vColor;" +       // 接收傳入的顏色
                    " void main() {" +
                    "     gl_FragColor = vColor;" +  // 給此片元的填充色
                    " }";

    private FloatBuffer vertexBuffer;  //頂點坐標數(shù)據(jù)要轉化成FloatBuffer格式


    // 數(shù)組中每3個值作為一個坐標點
    static final int COORDS_PER_VERTEX = 3;
    //三角形的坐標數(shù)組
    static float triangleCoords[] = {
            0.0f, 0.5f, 0.0f, // top
            -0.5f, -0.5f, 0.0f, // bottom left
            0.5f, -0.5f, 0.0f  // bottom right
    };

    //頂點個數(shù),計算得出
    private final int vertexCount = triangleCoords.length / COORDS_PER_VERTEX;
    //一個頂點有3個float,一個float是4個字節(jié),所以一個頂點要12字節(jié)
    private final int vertexStride = COORDS_PER_VERTEX * 4; // 4 bytes per mVertex

    //三角形的顏色數(shù)組,rgba
    private float[] mColor = {
            0.0f, 1.0f, 0.0f, 1.0f,
    };

    //當前繪制的頂點位置句柄
    private int vPositionHandle;
    //片元著色器顏色句柄
    private int vColorHandle;
    //變換矩陣句柄
    private int mMVPMatrixHandle;
    //這個可以理解為一個OpenGL程序句柄
    private final int mProgram;

    //變換矩陣,提供set方法
    private float[] mvpMatrix = new float[16];
    public void setMvpMatrix(float[] mvpMatrix) {
        this.mvpMatrix = mvpMatrix;
    }


    public GLTriangle02() {
        /** 1、數(shù)據(jù)轉換,頂點坐標數(shù)據(jù)float類型轉換成OpenGL格式FloatBuffer,int和short同理*/
        vertexBuffer = GLUtil.floatArray2FloatBuffer(triangleCoords);

        /** 2、加載編譯頂點著色器和片元著色器*/
        int vertexShader = GLUtil.loadShader(GLES20.GL_VERTEX_SHADER,
                vertexShaderCode);
        int fragmentShader = GLUtil.loadShader(GLES20.GL_FRAGMENT_SHADER,
                fragmentShaderCode);

        /** 3、創(chuàng)建空的OpenGL ES程序,并把著色器添加進去*/
        mProgram = GLES20.glCreateProgram();

        // 添加頂點著色器到程序中
        GLES20.glAttachShader(mProgram, vertexShader);

        // 添加片段著色器到程序中
        GLES20.glAttachShader(mProgram, fragmentShader);

        /** 4、鏈接程序*/
        GLES20.glLinkProgram(mProgram);

    }


    public void draw() {

        // 將程序添加到OpenGL ES環(huán)境
        GLES20.glUseProgram(mProgram);

        /***1.獲取句柄*/
        // 獲取頂點著色器的位置的句柄(這里可以理解為當前繪制的頂點位置)
        vPositionHandle = GLES20.glGetAttribLocation(mProgram, "vPosition");
        // 獲取片段著色器的vColor句柄
        vColorHandle = GLES20.glGetUniformLocation(mProgram, "vColor");
        // 獲取變換矩陣的句柄
        mMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix");

        /**2.設置數(shù)據(jù)*/
        // 啟用頂點屬性,最后對應禁用
        GLES20.glEnableVertexAttribArray(vPositionHandle);

        //準備三角形坐標數(shù)據(jù)
        GLES20.glVertexAttribPointer(vPositionHandle, COORDS_PER_VERTEX,
                GLES20.GL_FLOAT, false,
                vertexStride, vertexBuffer);
        // 設置繪制三角形的顏色,給vColor 這個變量賦值
        GLES20.glUniform4fv(vColorHandle, 1, mColor, 0);
        // 將投影和視圖轉換傳遞給著色器,可以理解為給uMVPMatrix這個變量賦值為mvpMatrix
        GLES20.glUniformMatrix4fv(mMVPMatrixHandle, 1, false, mvpMatrix, 0);

        /** 3.繪制三角形,三個頂點*/
        GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, vertexCount);

        // 禁用頂點數(shù)組(好像不禁用也沒啥問題)
        GLES20.glDisableVertexAttribArray(vPositionHandle);
    }
}

到這里我們對繪制三角形的步驟已經(jīng)有一個基本的了解,那么如何畫一個長方形呢?其實跟畫三角形差不多,下一節(jié)我們就學習一下如何繪制一個長方形,敬請期待。

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容