Opengl ES系列學習--glViewport API使用

     去年有分析了一些Opengl ES的實例,但是後面在實際的工作中,發現根基不牢,工作中使用的一些複雜場景還是理解的不夠透徹,所以回過心來,必須把基礎把紮實。從這節開始,我們後面對一些非常基礎普通的Opengl ES API進行實際使用介紹,萬丈高樓平地起,必須把基礎搞紮實,我們才能一步步向上。

     所有實例均有提供源碼,下載地址:Opengl ES Source Code

     網上找到一份非常好的資料,收集了很多API的英文翻譯,供大家參考:OpenGL ES 2.0 中文API

     好,本節我們來看一下glViewport API的作用。實現的實例如下圖:

     很簡單,我們就是不斷的變化視口,然後每個視口畫一個三角形。本節的源碼是在OpenGL\learn\src\main\java\com\opengl\learn\GlViewportRender.java文件中,該類所有源碼如下:

package com.opengl.learn;

import android.content.Context;
import android.opengl.GLSurfaceView;
import android.util.Log;

import com.lime.common.ESShader;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;

import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;

import static android.opengl.GLES20.GL_COLOR_BUFFER_BIT;
import static android.opengl.GLES20.GL_COMPILE_STATUS;
import static android.opengl.GLES20.GL_FLOAT;
import static android.opengl.GLES20.GL_FRAGMENT_SHADER;
import static android.opengl.GLES20.GL_LINK_STATUS;
import static android.opengl.GLES20.GL_TRIANGLES;
import static android.opengl.GLES20.GL_VERTEX_SHADER;
import static android.opengl.GLES20.glAttachShader;
import static android.opengl.GLES20.glClear;
import static android.opengl.GLES20.glClearColor;
import static android.opengl.GLES20.glCompileShader;
import static android.opengl.GLES20.glCreateProgram;
import static android.opengl.GLES20.glCreateShader;
import static android.opengl.GLES20.glDeleteProgram;
import static android.opengl.GLES20.glDeleteShader;
import static android.opengl.GLES20.glDrawArrays;
import static android.opengl.GLES20.glEnableVertexAttribArray;
import static android.opengl.GLES20.glGetAttribLocation;
import static android.opengl.GLES20.glGetError;
import static android.opengl.GLES20.glGetProgramInfoLog;
import static android.opengl.GLES20.glGetProgramiv;
import static android.opengl.GLES20.glGetShaderInfoLog;
import static android.opengl.GLES20.glGetShaderiv;
import static android.opengl.GLES20.glLinkProgram;
import static android.opengl.GLES20.glShaderSource;
import static android.opengl.GLES20.glUseProgram;
import static android.opengl.GLES20.glVertexAttribPointer;
import static android.opengl.GLES20.glViewport;

public class GlViewportRender implements GLSurfaceView.Renderer {
    private static String TAG = GlViewportRender.class.getSimpleName();
    private final float[] mVerticesData =
            {0.0f, 0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f,
                    -0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f,
                    0.5f, -0.5f, 0.0f, 0.0f, 0.0f, 1.0f, 1.0f};

    private final float[] mColorData =
            {
                    1.0f, 0.0f, 0.0f, 1.0f,   // c0
                    0.0f, 1.0f, 0.0f, 1.0f,   // c1
                    0.0f, 0.0f, 1.0f, 1.0f    // c2
            };

    private static final int BYTES_PER_FLOAT = 4;
    private static final int POSITION_COMPONENT_SIZE = 3;
    private static final int COLOR_COMPONENT_SIZE = 4;

    private Context mContext;
    private int mProgramObject;
    private int mWidth;
    private int mHeight;
    private FloatBuffer mVertices, mColorVertices;
    private int vPosition, vColor;

    private static final int STRIDE =
            (POSITION_COMPONENT_SIZE + COLOR_COMPONENT_SIZE) * BYTES_PER_FLOAT;

    public GlViewportRender(Context context) {
        mContext = context;
        mVertices = ByteBuffer.allocateDirect(mVerticesData.length * 4)
                .order(ByteOrder.nativeOrder()).asFloatBuffer();
        mVertices.put(mVerticesData).position(0);
        mColorVertices = ByteBuffer.allocateDirect(mColorData.length * 4)
                .order(ByteOrder.nativeOrder()).asFloatBuffer();
        mColorVertices.put(mColorData).position(0);
    }

    @Override
    public void onSurfaceCreated(GL10 gl, EGLConfig config) {
        loadProgram();
    }

    @Override
    public void onSurfaceChanged(GL10 gl, int width, int height) {
        mWidth = width;
        mHeight = height;
    }

    @Override
    public void onDrawFrame(GL10 gl) {
        drawShape();
    }

    private void drawShape() {
        // Clear the color buffer
        glClear(GL_COLOR_BUFFER_BIT);
        for (int i = 0; i < 4; i++) {
            if (i == 0) {
                glViewport(0, mHeight / 2, mWidth / 2, mHeight / 2);
            } else if (i == 1) {
                glViewport(mWidth / 2, mHeight / 2, mWidth / 2, mHeight / 2);
            } else if (i == 2) {
                glViewport(0, 0, mWidth / 2, mHeight / 2);
            } else if (i == 3) {
                glViewport(mWidth / 2, 0, mWidth / 2, mHeight / 2);
            }

            // Use the program object
            glUseProgram(mProgramObject);

            mVertices.position(0);
            // Load the vertex data
            glVertexAttribPointer(vPosition, 3, GL_FLOAT, false, STRIDE, mVertices);
            glEnableVertexAttribArray(vPosition);

            mVertices.position(POSITION_COMPONENT_SIZE);
            glVertexAttribPointer(vColor, 4, GL_FLOAT, false, STRIDE, mVertices);
            glEnableVertexAttribArray(vColor);

            glDrawArrays(GL_TRIANGLES, 0, 3);
        }
    }

    private void loadProgram() {
        String vShaderStr = ESShader.readShader(mContext, "viewport_vertexShader.glsl");
        String fShaderStr = ESShader.readShader(mContext, "viewport_fragmentShader.glsl");
        int vertexShader;
        int fragmentShader;
        int programObject;
        int[] linked = new int[1];

        // Load the vertex/fragment shaders
        vertexShader = loadShader(GL_VERTEX_SHADER, vShaderStr);
        fragmentShader = loadShader(GL_FRAGMENT_SHADER, fShaderStr);

        // Create the program object
        programObject = glCreateProgram();

        if (programObject == 0) {
            return;
        }

        glAttachShader(programObject, vertexShader);
        glAttachShader(programObject, fragmentShader);

        // Link the program
        glLinkProgram(programObject);

        // Check the link status
        glGetProgramiv(programObject, GL_LINK_STATUS, linked, 0);

        if (linked[0] == 0) {
            Log.e(TAG, "Error linking program:");
            Log.e(TAG, glGetProgramInfoLog(programObject));
            glDeleteProgram(programObject);
            return;
        }
        // Store the program object
        mProgramObject = programObject;
        glClearColor(1.0f, 1.0f, 1.0f, 0.0f);
        vPosition = glGetAttribLocation(mProgramObject, "vPosition");
        vColor = glGetAttribLocation(mProgramObject, "vColor");

        Log.i(TAG, "vPosition: " + vPosition + ", vColor: " + vColor);
    }

    private int loadShader(int type, String shaderSrc) {
        int shader;
        int[] compiled = new int[1];

        // Create the shader object
        shader = glCreateShader(type);

        if (shader == 0) {
            return 0;
        }

        // Load the shader source
        glShaderSource(shader, shaderSrc);

        // Compile the shader
        glCompileShader(shader);

        // Check the compile status
        glGetShaderiv(shader, GL_COMPILE_STATUS, compiled, 0);

        if (compiled[0] == 0) {
            Log.e(TAG, "compile shader error: " + glGetShaderInfoLog(shader));
            glGetError();
            glDeleteShader(shader);
            return 0;
        }
        Log.i(TAG, "load " + type + " shader result: " + shader);
        return shader;
    }
}

     我們主要是演練一下glViewport API的功能,其他邏輯就簡單略過了。glViewport接口的介紹請點擊:GLES2.0中文API-glViewport。可以看到該接口一共有四個參數,前兩個參數x、y定位了當前視口的左下角的點,以像素爲單位,後面兩個參數width、height定義了當前視口的大小,程序員思維應該很容易理解,一個接口就確定好了我們視口的位置和大小。那想像一下,我們平時繪製時,直接調用glViewport(0, 0, mWidth, mHeight),表示視口左下角和屏幕左下角重合,視口大小就是屏幕大小,所以視口的中心點也就是屏幕的中心點,繪製時就是按照Opengl座標,把整個屏幕投影在(-1,1)範圍內,X/Y方向都是。

     明白了最基礎的使用,我們再考慮一下,那如果我要添加一個水印,要指定水印的位置,要怎麼辦呢?對了,我們就可以修改視口的參數來達到這樣的目的了,我們修改前兩個參數,就可以變換視口的左下角;修改後兩個參數就可以控制視口的大小。

     那要實現本例的效果,想必大家感覺也很簡單了,四個三角形大小相同,且寬高歸一化後都是0.5f,我們要畫第一個左上角的三角形,它的左下角就是屏幕左側的中間點的座標(0,mHeight / 2);第二個右上角的三角形,它的左下角的座標就是屏幕的中心點(mWidth / 2, mHeight / 2);第三個左下角的三角形,它的左下角座標也就是屏幕的左下角(0,0);第四個三角形的左下角座標就是屏幕最底線的中間點(mWidth / 2, 0)。最後兩個參數寬高都一樣,這樣我們就畫出來四個不同位置的三角形了。

     這裏順便說一下glClear(GL_COLOR_BUFFER_BIT)清屏的調用,不能在for循環中調用,否則就會出問題。比如我們把drawShape方法修改成如下:

    private void drawShape() {
        // Clear the color buffer
        for (int i = 0; i < 4; i++) {
            glClear(GL_COLOR_BUFFER_BIT);
            if (i == 0) {
                glViewport(0, mHeight / 2, mWidth / 2, mHeight / 2);
            } else if (i == 1) {
                glViewport(mWidth / 2, mHeight / 2, mWidth / 2, mHeight / 2);
            } else if (i == 2) {
                glViewport(0, 0, mWidth / 2, mHeight / 2);
            } else if (i == 3) {
                glViewport(mWidth / 2, 0, mWidth / 2, mHeight / 2);
            }

            // Use the program object
            glUseProgram(mProgramObject);

            mVertices.position(0);
            // Load the vertex data
            glVertexAttribPointer(vPosition, 3, GL_FLOAT, false, STRIDE, mVertices);
            glEnableVertexAttribArray(vPosition);

            mVertices.position(POSITION_COMPONENT_SIZE);
            glVertexAttribPointer(vColor, 4, GL_FLOAT, false, STRIDE, mVertices);
            glEnableVertexAttribArray(vColor);

            glDrawArrays(GL_TRIANGLES, 0, 3);
        }
    }

     我們把glClear(GL_COLOR_BUFFER_BIT)邏輯放在for循環裏面了,那麼想像一下,前三個三角形畫完,都會執行glClear被清除,只有第四個三角形畫完後退出for循環,所以也就只會顯示最後一個三角形了。

     還有一個需要注意的,我們該實例中頂點的位置和顏色屬性都是從mVertices中取值的,所以在給顏色賦值時,必須調用mVertices.position(POSITION_COMPONENT_SIZE)移動Buffer的起始位置,否則顏色取值就會出錯,出來的結果跟我們意想的就會不一樣,大家可以試一下。

     好了,glViewport API我們就介紹到這裏吧,希望大家也能紮實的打好基礎,否則實際工作中碰到的那複雜場景我們真的是束手無策。

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