1. 程式人生 > >Android openGl開發詳解(一)——繪製簡單圖形

Android openGl開發詳解(一)——繪製簡單圖形

學習五部曲,弄清楚5個W一個H(when(什麼時候使用)、where(在哪個地方使用?)、who(對誰使用)、what(是個什麼東西)、why(為什麼要這麼用?).一個H即:how(到底該怎麼用?)),基本的概念篇主要圍繞這幾個方面進行分析

1. What? openGl是什麼?openGl ES又是什麼?

相信很多人從事開發的都或多或少聽到過有關OpenGl這個東西,但是平時用的少,只知道有這麼個東西,而且學起來不簡單,所以大多數人都不能講出個個所以然來。

官方對OpenGl的描述為:

OpenGL(Open Graphics Library開發圖形介面)是一個跨平臺的圖形API,用於指定3D圖形處理硬體中的標準軟體介面。

OpenGl的前身是SGI公司為其圖形工作站開發的IRIS GL,後來因為IRIS GL的移植性不好,所以在其基礎上,開發出了OpenGl。OpenGl一般用於在圖形工作站,PC端使用,由於效能各方面原因,在移動端使用OpenGl基本帶不動。為此,Khronos公司就為OpenGl提供了一個子集,OpenGl ES(OpenGl for Embedded System)

什麼是OpenGl ES呢?

OpenGl ES是免費的跨平臺的功能完善的2D/3D圖形庫介面的API,是OpenGL的一個子集。

移動端使用到的基本上都是OpenGl ES,當然Android開發下還專門為OpenGl提供了android.opengl包,並且提供了GlSurfaceView,GLU,GlUtils等工具類。

2. How? Android中的openGL 如何使用?

在瞭解OpenGl的使用之前,我們需要了解兩個基本類別的Android框架:GlSurfaceView和GlSurfaceView.Renderer

3. GlSurfaceView是什麼? GLSurfaceView的作用是什麼? GLSurfaceView如何使用?

GlSurfaceView從名字就可以看出,它是一個SurfaceView,看原始碼可知,GlSurfaceView繼承自SurfaceView。並增加了Renderer.它的作用就是專門為OpenGl顯示渲染使用的。

GLSurfaceView的使用方法:
可以通過建立的例項使用這個類,並增加你的Renderer.

@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        GLSurfaceView glSurfaceView = new GLSurfaceView(this);
        glSurfaceView.setRenderer(new GLSurfaceView.Renderer() {
            @Override
            public void onSurfaceCreated(GL10 gl, EGLConfig config) {

            }

            @Override
            public void onSurfaceChanged(GL10 gl, int width, int height) {

            }

            @Override
            public void onDrawFrame(GL10 gl) {

            }
        });
        setContentView(glSurfaceView);
    }

4. GlSurfaceView.Renderer是什麼?GLSurfaceView.Renderer的作用?GLSurfaceView.Renderer的用法?

該介面定義了用於繪製在圖形所需的方法GLSurfaceView。你必須提供這個介面作為一個單獨的類的實現,並將其連線到您的GLSurfaceView使用例項 GLSurfaceView.setRenderer()。如上面的程式碼所示。作用就是提供各種渲染方法,OpenGl的渲染操作均在此介面中實習。下面說下實現該介面的方法含義:

  • onSurfaceCreated():系統呼叫這個方法一次建立時GLSurfaceView。使用此方法來執行只需要發生一次的操作,比如設定OpenGL的環境引數或初始化的OpenGL圖形物件。
  • onDrawFrame():系統呼叫上的每個重繪此方法GLSurfaceView。使用此方法作為主要執行點用於繪製(和重新繪製)的圖形物件。
  • 系統呼叫此方法時的GLSurfaceView幾何形狀的變化,包括尺寸變化GLSurfaceView或裝置螢幕的取向。例如,當裝置從縱向變為橫向的系統呼叫這個方法。使用此方法可以在變化做出反應GLSurfaceView容器。

介紹完了GlSurfaceView和GlSurfaceView.renderer之後,接下來說下如何使用GlSurfaceView;
1. 建立一個GlSurfaceView
2. 為這個GlSurfaceView設定渲染
3. 在GlSurfaceView.renderer中繪製處理顯示資料

5. OpenGl的簡單使用例項(繪製一個三角形)

  1. 在使用OpenGl之前,需要在AndroidManifest.xml中設定OpenGl的版本:這裡我們使用的是OpenGl ES 2.0,所以需要新增如下說明:
<uses-feature android:glEsVersion="0x00020000" android:required="true" />
  1. 使用GLSufaceView(上面有介紹)
  2. 具體在GlSurfaceView.Renderer中的繪製步驟:
    • 設定檢視展示視窗(viewport) :在onSurfaceChanged中呼叫GLES20.glViewport(0, 0, width, height);
    • 建立圖形類,確定好頂點位置和圖形顏色,將頂點和顏色資料轉換為OpenGl使用的資料格式
    • 載入頂點找色器和片段著色器用來修改圖形的顏色,紋理,座標等屬性
    • 建立投影和相機檢視來顯示檢視的顯示狀態,並將投影和相機檢視的轉換傳遞給著色器。
    • 建立專案(Program),連線頂點著色器片段著色器。
    • 將座標資料傳入到OpenGl ES程式中:

使用OpenGl修改背景顏色

建立一個GlSurfaceView,併為其設定渲染OneGlRenderer;

public class OneGlSurfaceView extends GLSurfaceView {
    private final OneGlRenderer mRenderer;
    public OneGlSurfaceView(Context context) {
        super(context);
        // Create an OpenGL ES 2.0 context
        setEGLContextClientVersion(2);

        mRenderer = new OneGlRenderer();

        // Set the Renderer for drawing on the GLSurfaceView
        setRenderer(mRenderer);
    }
}

實現渲染介面

public class OneGlRenderer implements GLSurfaceView.Renderer {
    public void onSurfaceCreated(GL10 unused, EGLConfig config) {
        // Set the background frame color
        GLES20.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
    }

    public void onDrawFrame(GL10 unused) {
        // Redraw background color
        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
    }

    public void onSurfaceChanged(GL10 unused, int width, int height) {
        GLES20.glViewport(0, 0, width, height);
    }
}

展示渲染後的GlSurfaceView

public class OneOpenGlActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        OneGlSurfaceView glSurfaceView = new OneGlSurfaceView(this);
        setContentView(glSurfaceView);
    }
}

效果如下:就是簡單給GlSurfaceView渲染一層黑色。

這裡寫圖片描述

使用OpenGl繪製幾何圖形

一:圖形建立

建立一個幾何圖形(這裡主要列舉三角形和正方形),需要注意一點,我們設定圖形的頂點座標後,需要將頂點座標轉為ByteBuffer,這樣OpenGl才能進行圖形處理。

三角形圖形建立:

public class Triangle {

    private FloatBuffer vertexBuffer;

    // number of coordinates per vertex in this array
    static final int COORDS_PER_VERTEX = 3;
    static float triangleCoords[] = {   // in counterclockwise order:
             0.0f,  0.5f, 0.0f, // top
            -0.5f, -0.5f, 0.0f, // bottom left
             0.5f, -0.5f, 0.0f  // bottom right
    };

    // Set color with red, green, blue and alpha (opacity) values
    float color[] = { 255, 0, 0, 1.0f };

    public Triangle() {
       // 初始化ByteBuffer,長度為arr陣列的長度*4,因為一個float佔4個位元組
        ByteBuffer bb = ByteBuffer.allocateDirect(triangleCoords.length * 4);
        // 陣列排列用nativeOrder
        bb.order(ByteOrder.nativeOrder());
        // 從ByteBuffer建立一個浮點緩衝區
        vertexBuffer = bb.asFloatBuffer();
        // 將座標新增到FloatBuffer
        vertexBuffer.put(triangleCoords);
        // 設定緩衝區來讀取第一個座標
        vertexBuffer.position(0);
    }
}

正方型圖:

public class Square {

    private FloatBuffer vertexBuffer;
    private ShortBuffer drawListBuffer;

    // number of coordinates per vertex in this array
    static final int COORDS_PER_VERTEX = 3;
    static float squareCoords[] = {
            -0.5f,  0.5f, 0.0f,   // top left
            -0.5f, -0.5f, 0.0f,   // bottom left
             0.5f, -0.5f, 0.0f,   // bottom right
             0.5f,  0.5f, 0.0f }; // top right

    private short drawOrder[] = { 0, 1, 2, 0, 2, 3 }; // order to draw vertices

    public Square() {
        // 初始化ByteBuffer,長度為arr陣列的長度*4,因為一個float佔4個位元組
        ByteBuffer bb = ByteBuffer.allocateDirect(squareCoords.length * 4);
        bb.order(ByteOrder.nativeOrder());
        vertexBuffer = bb.asFloatBuffer();
        vertexBuffer.put(squareCoords);
        vertexBuffer.position(0);

        // 初始化ByteBuffer,長度為arr陣列的長度*2,因為一個short佔2個位元組
        ByteBuffer dlb = ByteBuffer.allocateDirect(drawOrder.length * 2);
        dlb.order(ByteOrder.nativeOrder());
        drawListBuffer = dlb.asShortBuffer();
        drawListBuffer.put(drawOrder);
        drawListBuffer.position(0);
    }
}

建立圖形基本沒什麼技巧可言,按部就班就行了,為什麼資料需要轉換格式呢?主要是因為Java的緩衝區資料儲存結構為大端位元組序(BigEdian),而OpenGl的資料為小端位元組序(LittleEdian),因為資料儲存結構的差異,所以,在Android中使用OpenGl的時候必須要進行下轉換。當然,一般我們在使用的時候都會做個簡單的工具類。這裡提供幾個簡單的封裝。(佔幾個位元組就初始化ByteBuffer長度的時候*幾)

  1. 將int[]轉成IntBuffer
private IntBuffer intBufferUtil(int[] arr)
    {
        IntBuffer mBuffer;
        // 初始化ByteBuffer,長度為arr陣列的長度*4,因為一個int佔4個位元組
        ByteBuffer qbb = ByteBuffer.allocateDirect(arr.length * 4);
        // 陣列排列用nativeOrder
        qbb.order(ByteOrder.nativeOrder());
        mBuffer = qbb.asIntBuffer();
        mBuffer.put(arr);
        mBuffer.position(0);
        return mBuffer;
    }
  1. 將float[]陣列轉為OpenGl 所需要的FloatBuffer
private FloatBuffer floatBufferUtil(float[] arr)
    {
        FloatBuffer mBuffer;
        // 初始化ByteBuffer,長度為arr陣列的長度*4,因為一個int佔4個位元組
        ByteBuffer qbb = ByteBuffer.allocateDirect(arr.length * 4);
        // 陣列排列用nativeOrder
        qbb.order(ByteOrder.nativeOrder());
        mBuffer = qbb.asFloatBuffer();
        mBuffer.put(arr);
        mBuffer.position(0);
        return mBuffer;
    }
  1. 當然,依葫蘆畫瓢,如何將short[]轉ShortBuffer這個就照著寫就ok了
 private ShortBuffer shortBufferUtil(short[] arr){
        ShortBuffer mBuffer;
        // 初始化ByteBuffer,長度為arr陣列的長度*2,因為一個short佔2個位元組
        ByteBuffer dlb = ByteBuffer.allocateDirect(
                // (# of coordinate values * 2 bytes per short)
                arr.length * 2);
        dlb.order(ByteOrder.nativeOrder());
        mBuffer = dlb.asShortBuffer();
        mBuffer.put(arr);
        mBuffer.position(0);
        return mBuffer;
    }

建立完形狀之後,我們就要進行我們的第二步了,將這些形狀渲染到GlSurfaceView中去。主要可分為下面幾步:
1. 首先我們需要在GlSurfaceView.Renderer中初始化需要渲染的幾何圖形

private Triangle mTriangle;
    private Square   mSquare;
    public void onSurfaceCreated(GL10 unused, EGLConfig config) {
        // 設定背景顏色
        GLES20.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
        // 初始化triangle
        mTriangle = new Triangle();
        // 初始化 square
        mSquare = new Square();
    }

二.:繪製圖形,因為需要提供很多細節的圖形渲染管線,所以繪製圖形前至少需要一個頂點著色器來繪製形狀和一個片段著色器的顏色,形狀。這些著色器必須被編譯,然後加入到一個OpenGL ES程式,然後將其用於繪製形狀。簡單介紹下這幾個概念:
- 頂點著色器(Vertex Shader)頂點著色器是GPU上執行的小程式,由名字可以知道,通過它來處理頂點,他用於渲染圖形頂點的OpenGL ES圖形程式碼。頂點著色器可用來修改圖形的位置,顏色,紋理座標,不過不能用來建立新的頂點座標。
- 片段著色器(Fragment Shader ) 用於呈現與顏色或紋理的形狀的面的OpenGL ES程式碼。
- 專案(Program) -包含要用於繪製一個或多個形狀著色器的OpenGL ES的物件。

下面給Triangle類定義一個基本的著色器程式碼:

public class Triangle {

    private final String vertexShaderCode =
        "attribute vec4 vPosition;" +
        "void main() {" +
        "  gl_Position = vPosition;" +
        "}";

    private final String fragmentShaderCode =
        "precision mediump float;" +
        "uniform vec4 vColor;" +
        "void main() {" +
        "  gl_FragColor = vColor;" +
        "}";

    ...
}

當然,上面我們建立了著色器的編譯程式碼,程式碼編寫完成,需要寫個方法來執行這段程式碼,這裡我們在渲染器中寫一個如下方法來執行著色器程式碼:

public static int loadShader(int type, String shaderCode){

        // 創造頂點著色器型別(GLES20.GL_VERTEX_SHADER)
        // 或者是片段著色器型別 (GLES20.GL_FRAGMENT_SHADER)
        int shader = GLES20.glCreateShader(type);
        // 新增上面編寫的著色器程式碼並編譯它
        GLES20.glShaderSource(shader, shaderCode);
        GLES20.glCompileShader(shader);
        return shader;
    }

這裡有一點需要注意,因為著色器的程式碼執行是很昂貴滴,所以避免多次執行,需要我們一般將執行程式碼的邏輯寫帶圖形類的構造方法中。比如上面的Triangle,我們就這麼寫:

private final int mProgram;
public Triangle() {
        ... ...//資料轉換
        int vertexShader = OneGlRenderer.loadShader(GLES20.GL_VERTEX_SHADER,
                vertexShaderCode);
        int fragmentShader = OneGlRenderer.loadShader(GLES20.GL_FRAGMENT_SHADER,
                fragmentShaderCode);

        // 建立空的OpenGL ES程式
        mProgram = GLES20.glCreateProgram();

        // 新增頂點著色器到程式中
        GLES20.glAttachShader(mProgram, vertexShader);

        // 新增片段著色器到程式中
        GLES20.glAttachShader(mProgram, fragmentShader);

        // 建立OpenGL ES程式可執行檔案
        GLES20.glLinkProgram(mProgram);
    }

最後,所有繪製的所有基本配置都配置完成之後,我們來寫繪製圖形的方法,我們在圖形類(Triangle)中建立一個繪製的方法onDraw(),可以在onDraw()方法中設定繪製邏輯。

private int mPositionHandle;
    private int mColorHandle;

    private final int vertexCount = triangleCoords.length / COORDS_PER_VERTEX;
    private final int vertexStride = COORDS_PER_VERTEX * 4; // 4 bytes per vertex

    public void draw() {
        // 將程式新增到OpenGL ES環境
        GLES20.glUseProgram(mProgram);

        // 獲取頂點著色器的位置的控制代碼
        mPositionHandle = GLES20.glGetAttribLocation(mProgram, "vPosition");

        // 啟用三角形頂點位置的控制代碼
        GLES20.glEnableVertexAttribArray(mPositionHandle);

        //準備三角形座標資料
        GLES20.glVertexAttribPointer(mPositionHandle, COORDS_PER_VERTEX,
                GLES20.GL_FLOAT, false,
                vertexStride, vertexBuffer);

        // 獲取片段著色器的顏色的控制代碼
        mColorHandle = GLES20.glGetUniformLocation(mProgram, "vColor");

        // 設定繪製三角形的顏色
        GLES20.glUniform4fv(mColorHandle, 1, color, 0);

        // 繪製三角形
        GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, vertexCount);

        // 禁用頂點陣列
        GLES20.glDisableVertexAttribArray(mPositionHandle);
    }

完成上面所有步驟,只需要在GlSurfaceView.Renderer的onDrawFrame()方法中呼叫圖形類的繪製方法即可(上面的onDraw()):

 public void onDrawFrame(GL10 unused) {
        mTriangle.draw();
    }

最後的呈現效果如下圖所示:
這裡寫圖片描述

運用投影和相機檢視

通常情況下,OpenGl中展示的檢視和在Android上顯示的圖形會有偏差。借用官方圖片:

這裡寫圖片描述
當然我們可以通過矩陣轉換來解決這種問題,讓OpenGl上的檢視在任何android裝置上顯示的比例都是一樣的,這裡說下什麼是投影和相機檢視:

投影的定義

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

正交投影投影物體的帶下不會隨觀察點的遠近而發生變化,我們可以使用下面方法來執行正交投影:

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)          //相對觀察點遠面距離
相機檢視

什麼是相機檢視?簡單來說生活中我們拍照,你站的高度,拿相機的位置,姿勢不同,拍出來的照片也就不一樣,相機檢視就是來修改相機位置,觀察方式以及相機的傾斜角度等屬性。我們可以通過下面方法來修改相機檢視屬性:

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上的分量
轉換矩陣(變換矩陣)

轉換矩陣用來做什麼的呢?是否記得上面我們繪製的圖形座標需要轉換為OpenGl中能處理的小端位元組序(LittleEdian),沒錯,轉換矩陣就是用來將資料轉為OpenGl ES可用的資料位元組,我們將相機檢視和投影設定的資料相乘,便得到一個轉換矩陣,然後我們再講此矩陣傳給頂點著色器,具體使用方法及引數說明如下:

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

下面簡單講解下如何使用投影和相機檢視來實現矩陣變換並傳遞給頂點著色器;

  1. 定義一個投影:
 // 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];
    public void onSurfaceChanged(GL10 unused, int width, int height) {
        GLES20.glViewport(0, 0, width, height);

        float ratio = (float) width / height;

        // 這個投影矩陣被應用於物件座標在onDrawFrame()方法中
        Matrix.frustumM(mProjectionMatrix, 0, -ratio, ratio, -1, 1, 3, 7);
    }
  1. 定義一個相機檢視
@Override
public void onDrawFrame(GL10 unused) {
    ...
    // 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);
}
  1. 修改圖形類執行程式碼
public 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;" +
        "attribute vec4 vPosition;" +
        "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;" +
        "}";

    // Use to access and set the view transformation
    private int mMVPMatrixHandle;

    ...
}
  1. 投影和相機檢視程式碼到圖形類的繪製方法中去onDraw()
 public void draw(float[] mvpMatrix){
       ... ...
        // 得到形狀的變換矩陣的控制代碼
        mMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix");

        // 將投影和檢視轉換傳遞給著色器
        GLES20.glUniformMatrix4fv(mMVPMatrixHandle, 1, false, mvpMatrix, 0);

        // 畫三角形
        GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, vertexCount);

        // 禁用頂點陣列
        GLES20.glDisableVertexAttribArray(mPositionHandle);
    }

做完這些,我們就能得到如下圖:

這裡寫圖片描述

沒錯,這才沒有變形的檢視。到這裡,基本的通過OpenGl繪製簡單圖形就over了,下面我們講解下如何新增一些互動動作。

新增動作

前面都是簡單的動作介紹,使用OpenGl在螢幕上繪製物件是使用openGl的基本功。下面我來說下如何新增旋轉形狀。使用OpenGl的描繪物件是相對簡單的,首先需要在渲染器中建立一組旋轉矩陣,然後使用之前提到過的投影和相機檢視變換矩陣結合起來使用:

private float[] mRotationMatrix = new float[16];
public void onDrawFrame(GL10 gl) {
    float[] scratch = new float[16];

    ...

    // 建立一個旋轉矩陣
    long time = SystemClock.uptimeMillis() % 4000L;
    float angle = 0.090f * ((int) time);
    Matrix.setRotateM(mRotationMatrix, 0, angle, 0, 0, -1.0f);

    // 將旋轉矩陣與投影和相機檢視組合在一起
    // Note that the mMVPMatrix factor *must be first* in order
    // for the matrix multiplication product to be correct.
    Matrix.multiplyMM(scratch, 0, mMVPMatrix, 0, mRotationMatrix, 0);

    // Draw triangle
    mTriangle.draw(scratch);
}

執行效果圖如下:

這裡寫圖片描述

修改頂點顏色

一個顏色是不是太單調了?如何讓做成多彩的呢?接下來我們來做一個多彩三角形,如何來做一個多彩三角形?我們通過頂點著色器來做。基於上面的程式碼,我們只需要做一點點改動,下面是基本步驟:
1. 修改著色器程式碼
2. 將顏色值修改為float陣列並轉為floatBuffer
3. 將獲取的floatBuffer傳遞給頂點著色器。

修改著色器程式碼:

 private final String vertexShaderCode =
            "attribute vec4 vPosition;" +
                    "uniform mat4 uMVPMatrix;"+
                    "varying  vec4 vColor;"+
                    "attribute vec4 aColor;"+
                    "void main() {" +
                    "  gl_Position = uMVPMatrix*vPosition;" +
                    "  vColor=aColor;"+
                    "}";

    private final String fragmentShaderCode =
            "precision mediump float;" +
                    "varying vec4 vColor;" +
                    "void main() {" +
                    "  gl_FragColor = vColor;" +
                    "}";
shader的變數型別(uniform,attribute和varying)的區別

關於shader的變數型別(uniform,attribute和varying)的區別及使用,下面做下說明:
1. uniform:uniform變數在vertex和fragment兩者之間宣告方式完全一樣,則它可以在vertex和fragment共享使用。(相當於一個被vertex和fragment shader共享的全域性變數)uniform變數一般用來表示:變換矩陣,材質,光照引數和顏色等資訊。在程式碼中通過GLES20.glGetUniformLocation(int program, String name)來獲取屬性值。並通過 GLES20.glUniformMatrix4fv(int location, int count, boolean transpose, float[] value, int offset);方法將資料傳遞給著色器。
2. attribute:這個變數只能在頂點著色器中使用(vertex Shader),用來表示頂點的資料,比如頂點座標,頂點顏色,法線,紋理座標等。在繪製的時候通過GLES20.glGetAttribLocation(int program, String name)來獲取變數值,通過 GLES20.glEnableVertexAttribArray(int index)來啟動控制代碼,最後通過 GLES20.glVertexAttribPointer(int indx,int size,int type,boolean normalized,int stride,java.nio.Buffer ptr)來設定圖形資料。
3. varying變數:這個變數只能用來在vertex和fragment shader之間傳遞資料時使用,不可以通過程式碼獲取其變數值。

接來下我們進行資料轉換:

float color[] = {
            1.0f, 0f, 0f, 1.0f ,
            0f, 1.0f, 0f, 1.0f ,
            0f, 0f, 1.0f, 1.0f
    };
        public Triangle() {
           ... ...
          ByteBuffer dd = ByteBuffer.allocateDirect(
                    color.length * 4);
            dd.order(ByteOrder.nativeOrder());
            colorBuffer = dd.asFloatBuffer();
            colorBuffer.put(color);
            colorBuffer.position(0);
        }

最後我們需要獲取著色器的控制代碼並設定著色器的顏色:

public void draw(float[] mvpMatrix){
        ... ... 
         /* // 獲取片段著色器的vColor成員的控制代碼
        mColorHandle = GLES20.glGetUniformLocation(mProgram, "vColor");

        // 設定繪製三角形的顏色
        GLES20.glUniform4fv(mColorHandle, 1, colorBuffer, 0);*/

        //獲取片元著色器的vColor成員的控制代碼
        mColorHandle = GLES20.glGetAttribLocation(mProgram, "aColor");
        //設定繪製三角形的顏色
        GLES20.glEnableVertexAttribArray(mColorHandle);
        GLES20.glVertexAttribPointer(mColorHandle,4,
                GLES20.GL_FLOAT,false,
                0,colorBuffer);
                ... ...
                }

這裡寫圖片描述

6. 參考連結:

7. 專案地址:

AserbaosAndroid此專案為博主所有的系列學習的程式碼彙總專案,該文章的程式碼位於:opengl/OneOpenGl/OneOpenGlActivity

8. 打賞關注

If you find this repository helpful, you may make a donation to me via alipay or wechat.

堅持原創技術分享,您的支援將鼓勵我繼續創作!

微信打賞: 支付寶打賞: 知識星球: 微信公眾號:
微信支付 這裡寫圖片描述 這裡寫圖片描述 aserbao