美文网首页Android进阶之路Android技术知识手机移动程序开发
Android多媒体之GLES2战记第三集--圣火之光

Android多媒体之GLES2战记第三集--圣火之光

作者: e4e52c116681 | 来源:发表于2019-01-16 12:30 被阅读38次
    前情回顾

    旁边: 勇者们为求黑龙宝藏,集结起来共闯黑龙副本,经历重重艰辛,
    终于获得立方开启了黑龙之门,这也只是新征程的起点,后面将有更大的挑战等着他们
    张风捷特烈打开了门之后,看到了什么?让我们继续收看


    副本九:黑暗之渊

    在打开门后,光芒全部消失,眼中一团黑暗,张风捷特烈踏出一步
    便立刻下坠,仿佛是无尽的深渊,地面?地面在那里?我还要下坠多久?

    world-black.png
    1.第一关卡:创造世界

    NPC:This is the world without anything,you must create everything by yourself.
    我:好吧,总结一下流程吧,顺便该封的封一下

    简单的世界.png
    1.1.常量:
    public class Cons {
        //维度:独立参数的数目
        public static final int DIMENSION_2 = 2;//2维度
        public static final int DIMENSION_3 = 3;//3维度
        public static final int DIMENSION_4 = 4;//4维度
    }
    

    1.2.显示的世界:World.java
    /**
     * 作者:张风捷特烈<br/>
     * 时间:2019/1/13/013:10:46<br/>
     * 邮箱:1981462002@qq.com<br/>
     * 说明:GL的世界
     */
    public class World extends GLSurfaceView {
        private WorldRenderer mRenderer;
        public World(Context context) {
            this(context,null);
        }
        public World(Context context, AttributeSet attrs) {
            super(context, attrs);
            init();
        }
        private void init() {
            setEGLContextClientVersion(2);//设置OpenGL ES 2.0 context
            mRenderer = new WorldRenderer(getContext());
            setRenderer(mRenderer);//设置渲染器
            setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);
        }
    }
    

    1.3.世界的渲染器WorldRenderer
    /**
     * 作者:张风捷特烈<br/>
     * 时间:2019/1/9 0009:18:56<br/>
     * 邮箱:1981462002@qq.com<br/>
     * 说明:GL世界渲染类
     */
    public class WorldRenderer implements GLSurfaceView.Renderer {
        private static final String TAG = "GLRenderer";
        //Model View Projection Matrix--模型视图投影矩阵
        private static float[] mMVPMatrix = new float[16];
        //投影矩阵 mProjectionMatrix
        private static final float[] mProjectionMatrix = new float[16];
        //视图矩阵 mViewMatrix
        private static final float[] mViewMatrix = new float[16];
        //变换矩阵
        private float[] mOpMatrix = new float[16];
        private Context mContext;
        private RendererAble mWorldShape;
        public WorldRenderer(Context context) {
            mContext = context;
        }
        private int currDeg = 0;
        @Override
        public void onSurfaceCreated(GL10 gl, EGLConfig config) {
            GLES20.glClearColor(0.0f,0.0f,0.0f,1.0f);//rgba
            mWorldShape = new WorldShape(mContext);
        }
        @Override
        public void onSurfaceChanged(GL10 gl, int width, int height) {
            GLES20.glViewport(0, 0, width, height);//GL视口
            float ratio = (float) width / height;
            //透视投影矩阵--截锥
            Matrix.frustumM(mProjectionMatrix, 0,
                    -ratio, ratio, -1, 1,
                    3, 9);
            // 设置相机位置(视图矩阵)
            Matrix.setLookAtM(mViewMatrix, 0,
                    2f, 2f, -6.0f,
                    0f, 0f, 0f,
                    0f, 1.0f, 0.0f);
        }
        /**
         * 此方法会不断执行 {@link GLSurfaceView.RENDERMODE_CONTINUOUSLY}
         * 此方法执行一次 {@link GLSurfaceView.RENDERMODE_WHEN_DIRTY}
         *
         * @param gl
         */
        @Override
        public void onDrawFrame(GL10 gl) {
            //清除颜色缓存和深度缓存
            GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT);
            //初始化变换矩阵
            Matrix.setRotateM(mOpMatrix, 0, currDeg, 0, 1, 0);
            Matrix.multiplyMM(mMVPMatrix, 0,
                    mViewMatrix, 0,
                    mOpMatrix, 0);
            Matrix.multiplyMM(mMVPMatrix, 0,
                    mProjectionMatrix, 0,
                    mMVPMatrix, 0);
            mWorldShape.draw(mMVPMatrix);
            //打开深度检测
            GLES20.glEnable(GLES20.GL_DEPTH_TEST);
        }
    }
    

    2.第二关卡:打开圣火之光(画点)

    黑暗中应该先出现一个点,代表希望之光

    一点.png
    2.1--片元着色代码:world.frag
    precision mediump float;
     varying vec4 vColor;
     
     void main() {
       gl_FragColor = vColor;
     }
    

    2.2--顶点着色代码:world.frag

    注意这里要设置点的大小,否则默认为0

    attribute vec3 vPosition;//顶点坐标
    uniform mat4 uMVPMatrix; //总变换矩阵
    attribute vec4 aColor;//顶点颜色
    varying  vec4 vColor;//片元颜色
    
    void main() {
      gl_Position = uMVPMatrix*vec4(vPosition,1);
      vColor = aColor;//将顶点颜色传给片元
      gl_PointSize=10.0;//设置点的大小,默认为0
    }
    

    2.3--点形状绘制
    /**
     * 作者:张风捷特烈<br/>
     * 时间:2019/1/13/013:8:39<br/>
     * 邮箱:1981462002@qq.com<br/>
     * 说明:世界的形状
     */
    public class WorldShape extends RendererAble {
        private int mProgram;//OpenGL ES 程序
        private int mPositionHandle;//位置句柄
        private int mColorHandle;//颜色句柄
        private int muMVPMatrixHandle;//顶点变换矩阵句柄
        private FloatBuffer mColorBuffer;//颜色缓冲
        private final int vertexColorStride = Cons.DIMENSION_4 * 4; // 4*4=16
        private FloatBuffer mVertexBuffer;//顶点缓冲
        private final int vertexStride = Cons.DIMENSION_3 * 4; // 3*4=12
        private float[] mVertex = new float[]{
                0.0f,0.0f,0.0f
        };
    
        private float[] mColor = new float[]{
                1.0f, 1.0f, 1.0f, 1.0f,
        };
    
        public WorldShape(Context context) {
            super(context);
            mColorBuffer = GLUtil.getFloatBuffer(mColor);
            mVertexBuffer = GLUtil.getFloatBuffer(mVertex);
            initProgram();
        }
    
        private void initProgram() {
            //顶点着色
            int vertexShader = GLUtil.loadShaderAssets(mContext,
                    GLES20.GL_VERTEX_SHADER, "world.vert");
            //片元着色
            int fragmentShader = GLUtil.loadShaderAssets(mContext,
                    GLES20.GL_FRAGMENT_SHADER, "world.frag");
            mProgram = GLES20.glCreateProgram();//创建空的OpenGL ES 程序
            GLES20.glAttachShader(mProgram, vertexShader);//加入顶点着色器
            GLES20.glAttachShader(mProgram, fragmentShader);//加入片元着色器
            GLES20.glLinkProgram(mProgram);//创建可执行的OpenGL ES项目
            //获取顶点着色器的vPosition成员的句柄
            mPositionHandle = GLES20.glGetAttribLocation(mProgram, "vPosition");
            //获取片元着色器的vColor成员的句柄
            mColorHandle = GLES20.glGetAttribLocation(mProgram, "aColor");
            //获取程序中总变换矩阵uMVPMatrix成员的句柄
            muMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix");
        }
    
        @Override
        public void draw(float[] mvpMatrix) {
            // 将程序添加到OpenGL ES环境中
            GLES20.glUseProgram(mProgram);
            //启用顶点的句柄
            GLES20.glEnableVertexAttribArray(mPositionHandle);
            //启用顶点颜色的句柄
            GLES20.glEnableVertexAttribArray(mColorHandle);
            //顶点矩阵变换
            GLES20.glUniformMatrix4fv(muMVPMatrixHandle, 1, false, mvpMatrix, 0);
            //准备顶点坐标数据
            GLES20.glVertexAttribPointer(
                    mPositionHandle,//int indx, 索引
                    Cons.DIMENSION_3,//int size,大小
                    GLES20.GL_FLOAT,//int type,类型
                    false,//boolean normalized,//是否标准化
                    vertexStride,// int stride,//跨度
                    mVertexBuffer);// java.nio.Buffer ptr//缓冲
            //准备顶点颜色数据
            GLES20.glVertexAttribPointer(
                    mColorHandle,
                    Cons.DIMENSION_4,
                    GLES20.GL_FLOAT,
                    false,
                    vertexColorStride,
                    mColorBuffer);
            int count = mVertex.length / Cons.DIMENSION_3;
            GLES20.glDrawArrays(GLES20.GL_POINTS, 0, count);
        }
    }
    

    NPC:很好,获取技能GLES20.GL_POINTS,勇者,继续展现你的创造力吧!


    3.第三关卡:绘制四点
    四点.png
    private float[] mVertex = new float[]{
            -1.0f, 0.0f, -1.0f,
            -1.0f, 0.0f, 1.0f,
            1.0f, 0.0f, 1.0f,
            1.0f, 0.0f, -1.0f,
    };
    
    private float[] mColor = new float[]{
            1.0f, 1.0f, 1.0f, 1.0f,
            1.0f, 1.0f, 1.0f, 1.0f,
            1.0f, 1.0f, 1.0f, 1.0f,
            1.0f, 1.0f, 1.0f, 1.0f,
    };
    

    张风捷特烈黑暗之渊中踩在四个点上,停止了下落,经过测量,发现点的单位是px

    经过ps的精确测量10px.png

    副本十:萦龙之丝

    1.第一关卡:坐标系体系

    接下来我们将使用以下视角进行世界的构建

    World.png

    现在将D点变色:可见视角和坐标系不一致

    现在的视角.png
    private float[] mVertex = new float[]{
            -1.0f, 0.0f, -1.0f,//A
            -1.0f, 0.0f, 1.0f,//B
            1.0f, 0.0f, 1.0f,//C
            1.0f, 0.0f, -1.0f,//D
    };
    private float[] mColor = new float[]{
            1.0f, 1.0f, 1.0f, 1.0f,
            1.0f, 1.0f, 1.0f, 1.0f,
            1.0f, 1.0f, 1.0f, 1.0f,
            0.21960784f,0.56078434f,0.92156863f,1.0f,
    };
    

    2.第二关卡:调整视角,符合ps画的坐标系

    为了视觉上好些,也为了ps里画图方便,这里讲视角逆时针旋转130°

    旋转视角.png
    Matrix.setRotateM(mOpMatrix, 0, currDeg+130, 0, 1, 0);
    
    点的旋转.gif
    3.第三关卡:画线

    直接把画点改成画线就行了,看一下GLES20几个常量的区别

    画线
    GLES20.glLineWidth(10);//设置线的宽度
    int count = mVertex.length / Cons.DIMENSION_3;
    //GLES20.glDrawArrays(GLES20.GL_POINTS, 0, count);
    //GLES20.glDrawArrays(GLES20.GL_LINES, 0, count);
    //GLES20.glDrawArrays(GLES20.GL_LINE_STRIP, 0, count);
    GLES20.glDrawArrays(GLES20.GL_LINE_LOOP, 0, count);
    
    旋转线

    为了使用方便,封装一下绘制简单图形的代码,就是把变量抽取一下
    虽然只能画些简单的东西,但画画辅助线还是蛮方便的,一个SimpleShape

    /**
     * 作者:张风捷特烈<br/>
     * 时间:2019/1/13/013:17:37<br/>
     * 邮箱:1981462002@qq.com<br/>
     * 说明:形状类
     */
    public class Shape {
        private float[] mVertex;//顶点
        private float[] mColor;//颜色
        private int mDrawType;//绘制类型
    
    /**
     * 作者:张风捷特烈<br/>
     * 时间:2019/1/13/013:8:39<br/>
     * 邮箱:1981462002@qq.com<br/>
     * 说明:简单的形状
     */
    public class SimpleShape extends RendererAble {
        //略...
        private Shape mShape;
    
        public SimpleShape(Context context, Shape shape) {
            super(context);
            mShape = shape;
            mColorBuffer = GLUtil.getFloatBuffer(mShape.getColor());
            mVertexBuffer = GLUtil.getFloatBuffer(mShape.getVertex());
            initProgram();
        }
        //略...
    

    副本十一:The World

    目的,形象地认识这个世界


    1.第一关卡:坐标系的绘制
    1.1:确定坐标和颜色(由于不怎么变动,所以放在常量类Cons里了)

    记住三个轴的颜色(Z轴:蓝色,X轴:黄色,Y轴:绿色)

    世界坐标系.png
    public static final float[] VERTEX_COO = {//坐标轴
            0.0f, 0.0f, 0.0f,//Z轴
            0.0f, 0.0f, 1.0f,
            0.0f, 0.0f, 0.0f,//X轴
            1.0f, 0.0f, 0.0f,
            0.0f, 0.0f, 0.0f,//Y轴
            0.0f, 1.0f, 0.0f,
    };
    public static final float[] COLOR_COO = {//坐标轴颜色
            0.0f, 0.0f, 1.0f, 1.0f,//Z轴:蓝色
            0.0f, 0.0f, 1.0f, 1.0f,
            1.0f, 1.0f, 0.0f, 1.0f,//X轴:黄色
            1.0f, 1.0f, 0.0f, 1.0f,
            0.0f, 1.0f, 0.0f, 1.0f,//Y轴:绿色
            0.0f, 1.0f, 0.0f, 1.0f,
    };
    

    1.2:使用SimpleShape
    ---->[WorldRenderer#onSurfaceCreated]--------
    Shape shape = new Shape(Cons.VERTEX_COO, Cons.COLOR_COO, GLES20.GL_LINES);
    mCoo = new SimpleShape(mContext, shape);
    
    ---->[WorldRenderer#onDrawFrame]--------
    mCoo.draw(mMVPMatrix);
    
    world.gif
    2.第二关卡:简单封装

    如果图形创建在WorldRenderer中,感觉很不舒服,毕竟会有很多形状,
    WorldRenderer的本意只是为了渲染以及视角的控制,并不希望图形掺杂其中
    WorldShape可以专门绘制形状,由它统一向WorldRenderer输出形状
    既然WorldShape总管图形,那么操作图形,在所难免,建一个OP接口,目前只放两个方法

    简单封装.png
    2.1:操作接口
    /**
     * 作者:张风捷特烈<br/>
     * 时间:2019/1/13/013:19:27<br/>
     * 邮箱:1981462002@qq.com<br/>
     * 说明:操作接口
     */
    public interface OP<T> {
        /**
         * 添加
         * @param ts 若干对象
         */
        void add(T... ts);
    
        /**
         * 根据id移除元素
         * @param id 索引
         */
        void remove(int id);
    }
    
    

    2.2:世界的形状:WorldShape
    /**
     * 作者:张风捷特烈<br/>
     * 时间:2019/1/13/013:8:39<br/>
     * 邮箱:1981462002@qq.com<br/>
     * 说明:世界的形状
     */
    public class WorldShape extends RendererAble implements OP<RendererAble>{
        List<RendererAble> mRendererAbles;
        private float[] mVertex = new float[]{
                -1.0f, 0.0f, -1.0f,//A
                -1.0f, 0.0f, 1.0f,//B
                1.0f, 0.0f, 1.0f,//C
                1.0f, 0.0f, -1.0f,//D
        };
        private float[] mColor = new float[]{
                1.0f, 1.0f, 1.0f, 1.0f,
                1.0f, 1.0f, 1.0f, 1.0f,
                1.0f, 1.0f, 1.0f, 1.0f,
                0.21960784f, 0.56078434f, 0.92156863f, 1.0f,
        };
        public WorldShape(Context ctx) {
            super(ctx);
            mRendererAbles = new ArrayList<>();
    
            Shape coo = new Shape(Cons.VERTEX_COO, Cons.COLOR_COO, GLES20.GL_LINES);
            Shape ground = new Shape(mVertex, mColor, GLES20.GL_LINE_LOOP);
            add(
                    new SimpleShape(mContext,coo),
                    new SimpleShape(mContext,ground),
        }
        @Override
        public void draw(float[] mvpMatrix) {
            for (RendererAble rendererAble : mRendererAbles) {
                rendererAble.draw(mvpMatrix);
            }
        }
        @Override
        public void add(RendererAble... rendererAbles) {
            for (RendererAble rendererAble : rendererAbles) {
                mRendererAbles.add(rendererAble);
            }
        }
        @Override
        public void remove(int id) {
            if (id>=mRendererAbles.size()) {
                return;
            }
            mRendererAbles.remove(id);
        }
    }
    

    2.3:使用WorldShape

    现在工作重心移入WorldShape,避免对WorldRenderer造成负担

    ---->[WorldRenderer#onSurfaceCreated]--------
    mWorldShape = new WorldShape(mContext);
    
    ---->[WorldRenderer#onDrawFrame]--------
     mWorldShape.draw(mMVPMatrix);
    

    3.Shape的强化,移动与移动创建

    关于深拷贝和浅拷贝我就不废话了,移动创建中需要深拷贝(成员变量有引用数据类型)
    Shape implements Cloneable

    3.1:深拷贝
    /**
     * 深拷贝
     * @return 形状副本
     */
    public Shape clone() {
        Shape clone = null;
        try {
            clone = (Shape) super.clone();
            float[] vertex = new float[mVertex.length];
            float[] color = new float[mColor.length];
            System.arraycopy(mVertex, 0, vertex, 0, mVertex.length);
            System.arraycopy(mColor, 0, color, 0, mColor.length);
            clone.mVertex = vertex;
            clone.mColor = color;
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return clone;
    }
    

    3.2:移动与移动拷贝
    /**
     * 移动并创建新图形
     * @param x
     * @param y
     * @param z
     * @return
     */
    public Shape moveAndCreate(float x, float y, float z) {
        Shape clone = clone();
        clone.move(x, y, z);
        return clone;
    }
    
    /**
     * 仅移动图形
     * @param x
     * @param y
     * @param z
     */
    public void move(float x, float y, float z) {
        for (int i = 0; i < mVertex.length; i++) {
            if (i % 3 == 0) {//x
                mVertex[i] += x;
            }
            if (i % 3 == 1) {//y
                mVertex[i] += y;
            }
            if (i % 3 == 2) {//y
                mVertex[i] += z;
            }
        }
    }
    

    3.3:移动创建图形

    两行代码搞定,我都佩服我自己,感觉可以用矩阵变换,现在还不是进击矩阵的时候

    移动复制.gif
    ---->[WorldShape#WorldShape]------------
     Shape coo = new Shape(Cons.VERTEX_COO, Cons.COLOR_COO, GLES20.GL_LINES);
     Shape ground = new Shape(mVertex, mColor, GLES20.GL_LINE_LOOP);
     Shape top = ground.moveAndCreate(0, 1, 0);
     Shape bottom = ground.moveAndCreate(0, -1, 0);
     add(
             new SimpleShape(mContext,coo),
             new SimpleShape(mContext,top),
             new SimpleShape(mContext,bottom),
             new SimpleShape(mContext,ground));
    

    3.4:再加四根线(感觉有点low...)
    private float[] mVertex2 = new float[]{
            1.0f, 1.0f, 1.0f,
            1.0f, -1.0f, 1.0f,
    
            -1.0f, 1.0f, 1.0f,
            -1.0f, -1.0f, 1.0f,
    
            -1.0f, 1.0f, -1.0f,
            -1.0f, -1.0f, -1.0f,
    
            1.0f, 1.0f, -1.0f,
            1.0f, -1.0f, -1.0f,
    };
    private float[] mColor2 = new float[]{
            1.0f, 0.0f, 0.0f, 1.0f,
            1.0f, 0.0f, 0.0f, 1.0f,
            1.0f, 1.0f, 1.0f, 1.0f,
            1.0f, 1.0f, 1.0f, 1.0f,
            1.0f, 1.0f, 1.0f, 1.0f,
            1.0f, 1.0f, 1.0f, 1.0f,
            1.0f, 1.0f, 1.0f, 1.0f,
            1.0f, 1.0f, 1.0f, 1.0f,
    };
    
    Shape side = new Shape(mVertex2, mColor2, GLES20.GL_LINES);
    
    线立方.gif

    世界的坐标已经映入眼帘,yes!


    副本十二:黑龙之瞳LEVEL 2

    在明确世界坐标之后,现在可以再来看一下视线了
    相信你会觉得恍然大悟,原来如此,just so so
    在此之前再说一遍:Z轴:蓝色,X轴:黄色,Y轴:绿色,正对红线


    1.第一关卡:移动相机 Z轴

    注意:现在将视角转回(0,0,-6),旋转角度归0为了不遮挡视线,将ground四条线隐藏
    看红线在后面,说明我们是从后面开始看的,Z轴:蓝色无法看到,说明视点在Z轴
    即:现在视点在Z轴上,值为-6,绝对值的大小即离物体的远近,近大远小没毛病
    but,移到-8时,可见后面已经消失了,说明视野是有限制的

    视线点
    // 设置相机位置(视图矩阵)
        Matrix.setLookAtM(mViewMatrix, 0,
            0f, 0f, -6.0f,
            0f, 0f, 0f,
            0f, 1.0f, 0.0f);
    

    2.第二关卡:移动相机 X轴

    将X每次向x负方向移动0.3f,想一下你拿着相机站在后面,看你的X轴方向
    或者直接看黄线,黄线所指方向为X轴正方向,你应该可以感觉相机是怎么移动的吧!

    x轴转
    // 设置相机位置(视图矩阵)
    Matrix.setLookAtM(mViewMatrix, 0,
            -1.5f, 0f, -6,
            0f, 0f, 0f,
            0f, 1.0f, 0.0f);
    

    3.第三关卡:移动相机 Y轴

    将Y每次向Y负方向移动0.3f,想一下你拿着相机站在后面,看你的X轴方向
    或者直接看黄线,黄线所指方向为X轴正方向,你应该可以感觉相机是怎么移动的吧!

    y轴转.png
    // 设置相机位置(视图矩阵)
    Matrix.setLookAtM(mViewMatrix, 0,
            -1.5f, 1.5f, -6,
            0f, 0f, 0f,
            0f, 1.0f, 0.0f);
    

    GLSurfaceView再怎么牛,也是个View,我们便可以添加事件
    下面一个小练习,相信上面的理解了,对你来说不会太难

    操作.gif

    NPC:恭喜完成十二个新手副本,下面将进入普通副本,祝君顺利

    本集结束,下集--移形换影,敬请期待

    后记:捷文规范

    1.本文成长记录及勘误表
    项目源码 日期 备注
    V0.1-github 2018-1-14 Android多媒体之GL-ES战记第三集--圣火之光
    2.更多关于我
    笔名 QQ 微信 爱好
    张风捷特烈 1981462002 zdl1994328 语言
    我的github 我的简书 我的掘金 个人网站
    3.声明

    1----本文由张风捷特烈原创,转载请注明
    2----欢迎广大编程爱好者共同交流
    3----个人能力有限,如有不正之处欢迎大家批评指证,必定虚心改正
    4----看到这里,我在此感谢你的喜欢与支持


    icon_wx_200.png

    相关文章

      网友评论

        本文标题:Android多媒体之GLES2战记第三集--圣火之光

        本文链接:https://www.haomeiwen.com/subject/rexmdqtx.html