Opengl ES系列学习--glViewport API使用

     去年有分析了一些Opengl ES的实例,但是后面在实际的工作中,发现根基不牢,工作中使用的一些复杂场景还是理解的不够透彻,所以回过心来,必须把基础把扎实。从这节开始,我们后面对一些非常基础普通的Opengl ES API进行实际使用介绍,万丈高楼平地起,必须把基础搞扎实,我们才能一步步向上。

     所有实例均有提供源码,下载地址:Opengl ES Source Code。

     网上找到一份非常好的资料,收集了很多API的英文翻译,供大家参考:OpenGL ES 2.0 中文API。

     好,本节我们来看一下glViewport API的作用。实现的实例如下图:

Opengl ES系列学习--glViewport API使用_第1张图片

     很简单,我们就是不断的变化视口,然后每个视口画一个三角形。本节的源码是在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循环,所以也就只会显示最后一个三角形了。

Opengl ES系列学习--glViewport API使用_第2张图片

     还有一个需要注意的,我们该实例中顶点的位置和颜色属性都是从mVertices中取值的,所以在给颜色赋值时,必须调用mVertices.position(POSITION_COMPONENT_SIZE)移动Buffer的起始位置,否则颜色取值就会出错,出来的结果跟我们意想的就会不一样,大家可以试一下。

     好了,glViewport API我们就介绍到这里吧,希望大家也能扎实的打好基础,否则实际工作中碰到的那复杂场景我们真的是束手无策。

你可能感兴趣的:(Android源码解析,Android,View视图,Opengl,ES)