Android多媒體之GLES2戰記第三集--聖火之光

張風捷特烈發表於2019-01-14
前情回顧

旁邊: 勇者們為求黑龍寶藏,集結起來共闖黑龍副本,經歷重重艱辛,
終於獲得立方開啟了黑龍之門,這也只是新徵程的起點,後面將有更大的挑戰等著他們
張風捷特烈開啟了門之後,看到了什麼?讓我們繼續收看


副本九:黑暗之淵

在開啟門後,光芒全部消失,眼中一團黑暗,張風捷特烈踏出一步
便立刻下墜,彷彿是無盡的深淵,地面?地面在那裡?我還要下墜多久?

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

相關文章