OpenGL ES案例一:平面三角形绘制

说明:Android中1.x已经过时了,且2.0并不兼容1.x。本文以2.0版本为准!

1. 创建GLSurfaceView和Renderer

详细解释在代码中用注释标明了,就不另作介绍了。
GLSurfaceView负责界面展示,GLSurfaceView.Renderer负责界面的渲染逻辑控制。

public class TriangleGLView extends GLSurfaceView {

    public TriangleGLView(Context context) {
        this(context,null);
    }

    public TriangleGLView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    private void init() {
        //设置OpenGLES版本号2.0
        setEGLContextClientVersion(2);
        //设置渲染器
        setRenderer(new TriangleRenderer(this));
        //设置渲染模式
        //①RENDERMODE_CONTINUOUSLY:主动绘制,一定的时间间隔自动调用onDrawFrame方法,进行绘制
        //②RENDERMODE_WHEN_DIRTY:被动绘制,只有当调用requestRender()方法是,才会进行绘制。
        setRenderMode(RENDERMODE_WHEN_DIRTY);
    }

    private class TriangleRenderer implements Renderer {

        private GLSurfaceView mView;
        private Triangle mTriangle;

        public TriangleRenderer(GLSurfaceView view){
            this.mView = view;
        }

        @Override
        public void onSurfaceCreated(GL10 gl, EGLConfig config) {
            //设置背景色
            GLES20.glClearColor(05f,0.5f,0.5f,0.5f);
            //开启OpenGLES的某些功能。GL_DEPTH_TEST:深度测试,当绘制3D图形的时候需要开启,我们现在绘制的是平面图形不要开启。
//            GLES20.glEnable(GLES20.GL_DEPTH_TEST);
            mTriangle = new Triangle(mView);
        }

        @Override
        public void onSurfaceChanged(GL10 gl, int width, int height) {
            //设置OpenGLES视口。
            GLES20.glViewport(0,0,width,height);

            float r = (float)width/height;
            //设置平截头体,并给投影矩阵赋值。(frustumM方法赋值的投影矩阵为透视投影,见图透视投影)
            Matrix.frustumM(Triangle.sProjMatirx,0,
                    -r,r,-1,1,1,10);
            //设置平截头体,并给投影矩阵赋值。(orthoM方法赋值的投影矩阵为正交投影,见图:正交投影)
            //Matrix.orthoM(Triangle.sProjMatirx,0,
            //         -r,r,-1,1,1,10);
            //设置摄像机位置(观察点位置),
            Matrix.setLookAtM(Triangle.sVMatrix,0,
                    0,0,3,
                    0,0,0,
                    0,1,0);
        }

        @Override
        public void onDrawFrame(GL10 gl) {
            //清除颜色缓存,注释部分为深度缓存。
            GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT/*|GLES20.GL_DEPTH_BUFFER_BIT*/);
            mTriangle.draw();
        }
    }
}
透视投影.png
正交投影.png
2. 创建顶点着色器(triangle_ver.glsl)
uniform mat4 u_ProjMatirx;//投影矩阵,通过程序传进来。
uniform mat4 u_VMatirx;//摄像机(观察点)矩阵,通过程序传进来。
uniform mat4 u_MMatirx;//变换矩阵,通过程序传进来。

attribute vec4 a_position;//顶点数据,通过程序传进来。
attribute vec4 a_color;//顶点颜色数据,通过程序传进来。

varying vec4 v_color;//传递给片元着色器的顶点颜色数据。

void main() {
    //这里解释下总变换矩阵的作用:a_position是物体坐标,
    //而展示到屏幕上需要把物体坐标转换为世界坐标(下图会解释变换过程)。
    //注意矩阵顺序不能错,矩阵叉乘的特性。
    gl_Position = u_ProjMatirx * u_VMatirx * u_MMatirx * a_position;
    //将顶点颜色数据通过共享变量,传递给片元着色器。
    v_color = a_color;
}

矩阵变换过程.png

上图摘自OpenGL坐标系统

创建片元着色器
//片元着色器中需要制定精度
precision mediump float;

varying vec4 v_color;

void main() {
    //将顶点颜色数据赋给片元着色器内部,进行栅格化。
    gl_FragColor = v_color;
}

3.创建三角形类(Triangle)

内容比较多,简单说一下步骤:

  1. 初始化顶点数据
  2. 初始化着色器(包含-获取着色器属性(可选))
  3. 使用OpenGL ES 2.0绘制三角形(包含-变换矩阵初始化,着色器属性赋值)
package com.linuxpara.gles20example.shader;

import android.opengl.GLES20;
import android.opengl.GLSurfaceView;
import android.opengl.Matrix;
import android.util.Log;

import com.linuxpara.gles20example.util.ShaderUtils;

import java.nio.FloatBuffer;

/**
 * Date: 2017/12/11
 * *************************************************************
 * Auther: 陈占洋
 * *************************************************************
 * Email: [email protected]
 * *************************************************************
 * Description:
 */

public class Triangle {

    private static final String TAG = "Triangle";

    //投影矩阵
    public static float[] sProjMatirx = new float[16];
    //摄像机矩阵
    public static float[] sVMatrix = new float[16];
    //变化矩阵
    public static float[] sMMatrix = new float[16];

    private FloatBuffer mVerBuffer;
    private int mVerSize;
    private FloatBuffer mColorBuffer;

    private int mProgram;
    private int a_position;
    private int a_color;
    private int u_mMatirx;
    private int u_projMatrix;
    private int u_vMatirx;

    public Triangle(GLSurfaceView view) {
        initVerData();
        initShader(view);
    }

    /**
     * 初始化顶点数据
     */
    private void initVerData() {
        float[] ver = {
                //三角形一共三个顶点
                -0.5f, -1f, -3f,//第一个顶点的xyz轴坐标。
                0.5f, -1f, -3f,//第二个顶点的xyz轴坐标。
                0f, 1f, -3f,//第三个顶点的xyz轴坐标。
        };
        //顶点个数
        mVerSize = ver.length / 3;
        //顶点缓存
        mVerBuffer = ShaderUtils.getFloatBuffer(ver);

        float[] color = {
                //顶点的颜色
                1, 0, 0, 1,//第一个顶点的颜色RGBA
                0, 1, 0, 1,//第二个顶点的颜色RGBA
                0, 0, 1, 1,//第三个顶点的颜色RGBA
        };
        //顶点颜色缓存
        mColorBuffer = ShaderUtils.getFloatBuffer(color);
    }

    /**
     * 初始化着色器
     * @param view
     */
    private void initShader(GLSurfaceView view) {
        String verSource = ShaderUtils.getSourceFromAsset("triangle_ver.glsl", view.getResources());
        String fragSource = ShaderUtils.getSourceFromAsset("triangle_frag.glsl", view.getResources());

        mProgram = createProgram(verSource, fragSource);
        if (mProgram == 0){
            Log.i(TAG, "initShader: 创建着色器程序失败!!!");
        }
        //获取shader中的投影矩阵变量。
        u_projMatrix = GLES20.glGetUniformLocation(mProgram, "u_ProjMatirx");
        //获取shader中的摄像机矩阵变量
        u_vMatirx = GLES20.glGetUniformLocation(mProgram, "u_VMatirx");
        //获取shader中的变换矩阵变量
        u_mMatirx = GLES20.glGetUniformLocation(mProgram, "u_MMatirx");
        //获取shader中的顶点数据变量
        a_position = GLES20.glGetAttribLocation(mProgram, "a_position");
        //获取shader中的顶点颜色变量
        a_color = GLES20.glGetAttribLocation(mProgram, "a_color");
    }

    /**
     * 创建着色器程序。
     * @param verSource
     * @param fragSource
     * @return
     */
    protected int createProgram(String verSource,String fragSource){

        int verShader = loadShader(GLES20.GL_VERTEX_SHADER, verSource);
        if (verShader == 0){
            return 0;
        }

        int fragShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragSource);
        if (fragShader == 0){
            return 0;
        }

        int program = GLES20.glCreateProgram();
        if (program != 0){
            //依赖
            GLES20.glAttachShader(program,verShader);
            checkGLError("glAttachShader");
            GLES20.glAttachShader(program,fragShader);
            checkGLError("glAttachShader");
            //链接
            GLES20.glLinkProgram(program);

            int[] linked  = new int[1];
            GLES20.glGetProgramiv(program,GLES20.GL_LINK_STATUS,linked,0);
            if (linked[0] == 0){
                Log.e("ES20_ERROR", "Could not link program: ");
                Log.e("ES20_ERROR", GLES20.glGetProgramInfoLog(program));
                GLES20.glDeleteProgram(program);
                program = 0;
            }
        }

        return program;
    }

    /**
     * 加载着色器
     * @param shaderType
     * @param shaderSource
     * @return
     */
    private int loadShader(int shaderType,String shaderSource){
        //创建shader
        int shader = GLES20.glCreateShader(shaderType);
        if (shader != 0){
            //加载shader源码
            GLES20.glShaderSource(shader,shaderSource);
            //编译源码
            GLES20.glCompileShader(shader);
            int[] complied = new int[1];
            //获取shader信息
            GLES20.glGetShaderiv(shader,GLES20.GL_COMPILE_STATUS,complied,0);
            if (complied[0] == 0){
                Log.e("ES20_ERROR", "Could not compile shader " + shaderType + ":");
                Log.e("ES20_ERROR", GLES20.glGetShaderInfoLog(shader));
                //删除shader
                GLES20.glDeleteShader(shader);
                shader = 0;
            }
        }
        return shader;

    }

    public void checkGLError(String op) {

        int error = 0;
        if ((error = GLES20.glGetError()) != GLES20.GL_NO_ERROR){
            Log.e("ES20_ERROR", op + ": glError " + error);
            throw new RuntimeException(op + ": glError " + error);
        }

    }

    public void draw() {
        //指定使用着色器程序。
        GLES20.glUseProgram(mProgram);
        //初始化变换矩阵
        Matrix.setRotateM(sMMatrix,0,
                0,
                0,0,-1f);
        //给shader中u_ProjMatrix变量赋值
        GLES20.glUniformMatrix4fv(u_projMatrix,1,
                false,sProjMatirx,0);
        //给shader中u_VMatirx变量赋值
        GLES20.glUniformMatrix4fv(u_vMatirx,1,
                false,sVMatrix,0);
        //给shader中u_MMatirx变量赋值
        GLES20.glUniformMatrix4fv(u_mMatirx,1,
                false,sMMatrix,0);
        //给shader中a_position变量赋值
        GLES20.glVertexAttribPointer(a_position,3,GLES20.GL_FLOAT,
                false,0,mVerBuffer);
        //给shader中a_color变量赋值
        GLES20.glVertexAttribPointer(a_color,4,GLES20.GL_FLOAT,
                false,0,mColorBuffer);
        //开启顶点数据
        GLES20.glEnableVertexAttribArray(a_position);
        GLES20.glEnableVertexAttribArray(a_color);
        //绘制顶点数据
        GLES20.glDrawArrays(GLES20.GL_TRIANGLES,0,mVerSize);
        //关闭顶点数据
        GLES20.glDisableVertexAttribArray(a_position);
        GLES20.glDisableVertexAttribArray(a_color);

    }
}
用到的ShaderUtils工具类

代码比较简单,不做过多说明

package com.linuxpara.gles20example.util;

import android.content.res.Resources;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;

/**
 * Date: 2017/12/11
 * *************************************************************
 * Auther: 陈占洋
 * *************************************************************
 * Email: [email protected]
 * *************************************************************
 * Description:
 */

public class ShaderUtils {

    /**
     * 获取浮点类型缓存
     *
     * @param buffer
     * @return
     */
    public static FloatBuffer getFloatBuffer(float[] buffer) {

        FloatBuffer floatBuffer = ByteBuffer.allocateDirect(buffer.length * 4)
                .order(ByteOrder.nativeOrder())
                .asFloatBuffer()
                .put(buffer);
        floatBuffer.position(0);

        return floatBuffer;
    }

    /**
     * 获取int类型缓存
     *
     * @param buffer
     * @return
     */
    public static IntBuffer getIntBuffer(int[] buffer) {

        IntBuffer intBuffer = ByteBuffer.allocateDirect(buffer.length * 4)
                .order(ByteOrder.nativeOrder())
                .asIntBuffer()
                .put(buffer);
        intBuffer.position(0);

        return intBuffer;
    }

    /**
     * 获取字节类型缓存
     *
     * @param buffer
     * @return
     */
    public static ByteBuffer getByteBuffer(byte[] buffer) {

        ByteBuffer byteBuffer = ByteBuffer.allocateDirect(buffer.length)
                .order(ByteOrder.nativeOrder())
                .put(buffer);
        byteBuffer.position(0);

        return byteBuffer;
    }

    /**
     * 从资产目录中获取shader代码
     *
     * @param fileName
     * @return
     */
    public static String getSourceFromAsset(String fileName, Resources resources) {

        InputStream in = null;
        ByteArrayOutputStream baos = null;
        String result = "";

        try {
            in = resources.getAssets().open(fileName);

            baos = new ByteArrayOutputStream();

            byte[] buf = new byte[1024];
            int len = 0;
            while ((len = in.read(buf)) != -1) {
                baos.write(buf, 0, len);
            }
            String source = baos.toString("UTF-8");
            result = source.replaceAll("\\r\\n", "\n");
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (in != null) {
                try {
                    in.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (baos != null) {
                try {
                    baos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            return result;
        }
    }

    /**
     * 打印数组类型的矩阵
     * @param 
     * @param matrix
     * @param column
     * @return
     */
    public static  String printFloatMatrixArray(float[] matrix, int column) {
        StringBuilder result = new StringBuilder();
        for (int i = 0; i < matrix.length; i++) {
            if (i%column == 0){
                result.append("\n");
            }else {
                result.append(",");
            }
            result.append(matrix[i]);
        }
        return result.toString();
    }
}

补充:以下图片摘自极客学院。
GLES20.glDrawArrays(GLES20.GL_TRIANGLES,0,mVerSize);中绘制类型解释。
GL_POINTS:绘制独立的点。

gl-points.png

GL_LINES:顶点两两连接,为多条线段构成。
gl_lines.png

GL_LINE_STRIP:绘制一系列线段。
gl_line_strip.png

GL_LINE_LOOP:类同上,但是首尾相连,构成一个封闭曲线。
gl_line_loop.png

GL_TRIANGLES:每隔三个顶点构成一个三角形,为多个三角形组成。
gl_triangles.png

GL_TRIANGLE_STRIP:每相邻三个顶点组成一个三角形,为一系列相接三角形构成。
gl_triangle_strip.png

GL_TRIANGLE_FAN:以一个点为三角形公共顶点,组成一系列相邻的三角形。
gl_triangle_fan.png

你可能感兴趣的:(OpenGL ES案例一:平面三角形绘制)