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: zhanyang.chen@gmail.com
 * *************************************************************
 * 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: zhanyang.chen@gmail.com
 * *************************************************************
 * 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 <T>
     * @param matrix
     * @param column
     * @return
     */
    public static <T> 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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,319评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,801评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,567评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,156评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,019评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,090评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,500评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,192评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,474评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,566评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,338评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,212评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,572评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,890评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,169评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,478评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,661评论 2 335

推荐阅读更多精彩内容