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

96
张风捷特烈 Excellent
0.5 2019.01.16 12:30 字数 1541
前情回顾

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


副本九:黑暗之渊

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

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技术栈
Android技术栈
13.0万字 · 4.9万阅读 · 223人关注
决战安卓
Web note ad 1