Android多媒体之GL-ES战记第二集--谜团立方

1,968 阅读9分钟

上集回顾

旁白:上集说到,为了获取黑龙宝藏,勇者集结,共闯黑龙洞穴
经过一路艰辛,终于过了第四副本,前面还有什么困难等待着他们?一起收看


第五副本:龙之图阵

1.第一关卡:画一个矩形

NPC:隐藏任务,解谜:GLES20.GL_TRIANGLE_STRIP绘制方式
发现逆时针画的点貌似连的方式有点问题,貌似是先绘制2->3->4,在绘制1->2->3
因为2->3->4的三角形被1->2->3的等三角形遮住了,后来居上是编程的共识

static float sCoo[] = {   //以逆时针顺序
        -0.5f, 0.5f, 0.0f, // 左上
        -0.5f, -0.5f, 0.0f, // 左下
        0.5f, -0.5f, 0.0f, // 右下
        0.5f, 0.5f, 0.0f, //右上
};

float colors[] = new float[]{
        1f, 1f, 0.0f, 1.0f,//黄
        0.05882353f, 0.09411765f, 0.9372549f, 1.0f,//蓝
        0.19607843f, 1.0f, 0.02745098f, 1.0f,//绿
        1.0f, 0.0f, 1.0f, 1.0f,//紫色
};

---->[Rectangle#draw]---------
//绘制三角形
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);

怎么能让1->2->32->3->4不会重叠?--点调下顺序就行了
1->2->4->3

正方形

static float sCoo[] = {   //以逆时针顺序
        -0.5f, 0.5f, 0.0f, // 1.
        -0.5f, -0.5f, 0.0f, // 2.
        0.5f, 0.5f, 0.0f, //4.
        0.5f, -0.5f, 0.0f, // 3.
};

2.第二关卡:画五边形

先按照p1->p2->p3->p4->p5的顺序,看有什么效果
意料之内,先把p1->p2->p3->p4四点画完,再画第五点

五点.jpg

分析图

怎么调点呢?恩...好吧,哥要开始吹牛了
我这么随便一想,在满足正方形的条件下,动一下p5点,就行了,然后让p5上漂

p5上漂.png

static float sCoo[] = {   //以逆时针顺序
        -0.5f, 0.5f, 0.0f, // p1
        0.0f, 0.8f, 0.0f, //p5
        -0.5f, -0.5f, 0.0f, // p2
        0.5f, 0.5f, 0.0f, //p4
        0.5f, -0.5f, 0.0f, // p3
};

再多边形也可以用三角形拼出来,不过感觉有点麻烦
解密:GL_TRIANGLE_STRIP:相邻三个顶点绘制一个三角形


3.第三关卡:索引五边形--glDrawElements

我就想来着,应该有控制三角形顶点的东西,不然调起来很麻烦

3.1.缓冲的简单封装

缓冲顶点数据的套路基本一致,封个方法在GLUtils里吧,
暂时float(4字节)和short(2字节),其他的遇到再说

/**
 * float数组缓冲数据
 * @param vertexs 顶点
 * @return 获取浮点形缓冲数据
 */
public static FloatBuffer getFloatBuffer(float[] vertexs) {
    FloatBuffer buffer;
    ///每个浮点数:坐标个数* 4字节
    ByteBuffer qbb = ByteBuffer.allocateDirect(vertexs.length * 4);
    //使用本机硬件设备的字节顺序
    qbb.order(ByteOrder.nativeOrder());
    // 从字节缓冲区创建浮点缓冲区
    buffer = qbb.asFloatBuffer();
    // 将坐标添加到FloatBuffer
    buffer.put(vertexs);
    //设置缓冲区以读取第一个坐标
    buffer.position(0);
    return buffer;
}

/**
 * short数组缓冲数据
 * @param vertexs short 数组
 * @return 获取short缓冲数据
 */
public static ShortBuffer getShortBuffer(short[] vertexs) {
    ShortBuffer buffer;
    ByteBuffer qbb = ByteBuffer.allocateDirect(vertexs.length * 2);
    qbb.order(ByteOrder.nativeOrder());
    buffer = qbb.asShortBuffer();
    buffer.put(vertexs);
    buffer.position(0);
    return buffer;
}

3.2:自己控制三角形的形成

咱们从0开始数数,怎么控制点,看图你应该能知道

static float sCoo[] = {   //以逆时针顺序
        -0.5f, 0.5f, 0.0f, // p0
        -0.5f, -0.5f, 0.0f, // p1
        0.5f, -0.5f, 0.0f, // p2
        0.5f, 0.5f, 0.0f, //p3
        0.0f, 0.8f, 0.0f, //p4
};

//索引数组
private short[] idx = {
        0, 4, 3,
        1, 3, 0,
        1, 2, 3
};

//索引缓冲
idxBuffer = GLUtils.getShortBuffer(idx);

//绘制
GLES20.glDrawElements(GLES20.GL_TRIANGLES, idx.length,
GLES20.GL_UNSIGNED_SHORT, idxBuffer);

五边形.png


4.第四关卡:六边形

先转六边形应该没有什么大问题了

六边形.png

static float sCoo[] = {   //以逆时针顺序
        -0.5f, 0.5f, 0.0f, // p0
        -1.0f, 0.0f, 0.0f, // p1
        -0.5f, -0.5f, 0.0f, // p2
        0.5f, -0.5f, 0.0f, //p3
        1.0f, 0.0f, 0.0f, //p4
        0.5f, 0.5f, 0.0f, //p5
};

//索引数组
private short[] idx = {
        0, 1, 5,
        1, 5, 2,
        2, 5, 4,
        2, 3, 4
};

第六副本:龙之空间

到这来,咱们几乎都是在平面,先转要变立体了
守关boss要发大招了,勇士们,hold住

1.第一关卡:沿着y轴旋转起来

关于变换第一集讲了一些,忘了的可以回去看看
下面是转一圈的效果,感觉少了一半

旋转.gif

//围绕y轴旋转
Matrix.setRotateM(mOpMatrix, 0, currDeg, 0, 1, 0);

我让她一边移动一边旋转,好像可以看出一点东西
不过是转起来了,等到转讲视野的时候再详细论述吧

Matrix.setRotateM(mOpMatrix, 0, currDeg, 0, 1, 0);
Matrix.translateM(mOpMatrix, 0, currDeg/360.f, 0,0);

旋转+平移


1.第二关卡:空间面

貌似一直z轴我们都是0,动一下呗,纸上和ps上还是各有优点的
纸上画起来有感觉(但误差很大),ps里复用强,先看p0,p1,p2,p3
先从p0点(-0.5,0.5,0.5)入手,然后根据对称关系,或目测确定其他点,

两个面


//点位数组
static float sCoo[] = {
        -0.5f, 0.5f, 0.5f,//p0
        -0.5f, -0.5f, 0.5f,//p1
        -0.5f, -0.5f, -0.5f,//p2
        -0.5f, 0.5f, -0.5f,//p3
        }
        
//索引数组
private short[] idx = {
        0, 1, 3,
        1, 2, 3,
        }
        
float colors[] = new float[]{
        1f, 1f, 0.0f, 1.0f,//黄
        0.05882353f, 0.09411765f, 0.9372549f, 1.0f,//蓝
        0.19607843f, 1.0f, 0.02745098f, 1.0f,//绿
        1.0f, 0.0f, 1.0f, 1.0f,//紫色
        }

p0-p1-p2-p3.png


2.第二关卡:第二面

一面完成了,第二面p4、p5、p6、p7就好办了,可以看出来p0,p1和p4,p5是一样的
索引稍微画画也能看出规律,颜色再来那四个,就不贴了
可以看出和我们画的有点出入,貌似是视口的问题,但不影响图形本身

p4-p5-p6-p7.png

static float sCoo[] = {
        -0.5f, 0.5f, 0.5f,//p0
        -0.5f, -0.5f, 0.5f,//p1
        -0.5f, -0.5f, -0.5f,//p2
        -0.5f, 0.5f, -0.5f,//p3
        
        -0.5f, 0.5f, 0.5f,//p4
        -0.5f, -0.5f, 0.5f,//p5
        0.5f, -0.5f, 0.5f,//p6
        0.5f, 0.5f, 0.5f,//p7

//索引数组
private short[] idx = {
        0, 1, 3,
        1, 2, 3,
        
        0+4, 1+4, 3+4,
        1+4, 2+4, 3+4,

3.第三关卡:其他面

为了方便表述,我给每个面取了名字,分别是:A、B、C、D、E、F

static float sCoo[] = {
        //A面
        -0.5f, 0.5f, 0.5f,//p0
        -0.5f, -0.5f, 0.5f,//p1
        -0.5f, -0.5f, -0.5f,//p2
        -0.5f, 0.5f, -0.5f,//p3
        //B面
        -0.5f, 0.5f, 0.5f,//p4
        -0.5f, -0.5f, 0.5f,//p5
        0.5f, -0.5f, 0.5f,//p6
        0.5f, 0.5f, 0.5f,//p7
        //C面
        0.5f, 0.5f, 0.5f,//p8
        0.5f, -0.5f, 0.5f,//p9
        0.5f, -0.5f, -0.5f,//p10
        0.5f, 0.5f, -0.5f,//p11
        //D面
        -0.5f, 0.5f, 0.5f,//p12
        0.5f, 0.5f, 0.5f,//p13
        0.5f, 0.5f, -0.5f,//p14
        -0.5f, 0.5f, -0.5f,//p15
        //E面
        -0.5f, -0.5f, 0.5f,//p16
        0.5f, -0.5f, 0.5f,//p17
        0.5f, -0.5f, -0.5f,//p18
        -0.5f, -0.5f, -0.5f,//p19
        //F面
        -0.5f, 0.5f, -0.5f,//p20
        -0.5f, -0.5f, -0.5f,//p21
        0.5f, -0.5f, -0.5f,//p22
        0.5f, 0.5f, -0.5f,//p23
};

//索引数组
private short[] idx = {
        0, 1, 3,//A
        1, 2, 3,
        0 + 4, 1 + 4, 3 + 4,//B
        1 + 4, 2 + 4, 3 + 4,
        0 + 4 * 2, 1 + 4 * 2, 3 + 4 * 2,//C
        1 + 4 * 2, 2 + 4 * 2, 3 + 4 * 2,
        0 + 4 * 3, 1 + 4 * 3, 3 + 4 * 3,//D
        1 + 4 * 3, 2 + 4 * 3, 3 + 4 * 3,
        0 + 4 * 4, 1 + 4 * 4, 3 + 4 * 4,//E
        1 + 4 * 4, 2 + 4 * 4, 3 + 4 * 4,
        0 + 4 * 5, 1 + 4 * 5, 3 + 4 * 5,//F
        1 + 4 * 5, 2 + 4 * 5, 3 + 4 * 5,
};

float colors[] = new float[]{
        //A
        1f, 1f, 0.0f, 1.0f,//黄
        0.05882353f, 0.09411765f, 0.9372549f, 1.0f,//蓝
        0.19607843f, 1.0f, 0.02745098f, 1.0f,//绿
        1.0f, 0.0f, 1.0f, 1.0f,//紫色
        //B
        1f, 1f, 0.0f, 1.0f,//黄
        0.05882353f, 0.09411765f, 0.9372549f, 1.0f,//蓝
        0.19607843f, 1.0f, 0.02745098f, 1.0f,//绿
        1.0f, 0.0f, 1.0f, 1.0f,//紫色
        //C
        1f, 1f, 0.0f, 1.0f,//黄
        0.05882353f, 0.09411765f, 0.9372549f, 1.0f,//蓝
        0.19607843f, 1.0f, 0.02745098f, 1.0f,//绿
        1.0f, 0.0f, 1.0f, 1.0f,//紫色
        //D
        1f, 1f, 0.0f, 1.0f,//黄
        0.05882353f, 0.09411765f, 0.9372549f, 1.0f,//蓝
        0.19607843f, 1.0f, 0.02745098f, 1.0f,//绿
        1.0f, 0.0f, 1.0f, 1.0f,//紫色
        //E
        1f, 1f, 0.0f, 1.0f,//黄
        0.05882353f, 0.09411765f, 0.9372549f, 1.0f,//蓝
        0.19607843f, 1.0f, 0.02745098f, 1.0f,//绿
        1.0f, 0.0f, 1.0f, 1.0f,//紫色
        //F
        1f, 1f, 0.0f, 1.0f,//黄
        0.05882353f, 0.09411765f, 0.9372549f, 1.0f,//蓝
        0.19607843f, 1.0f, 0.02745098f, 1.0f,//绿
        1.0f, 0.0f, 1.0f, 1.0f,//紫色
};

3,4,5.png


第七副本:龙之逆鳞

第六副本中的立方体并不怎么让人满意,视角不怎么好,
这一副本就来再看看相机和投影以及矩阵

旋转立方.gif


1.第一关卡:调整相机

一开始眼睛在(0, 0, -3),看下图你就知道为什么画的时候面有问题了
因为相机在后面,而且很正,所以看着就很正...

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

现在调到后上方(2,2,-5)

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

调整相机.png


2.第二关卡:旋转试图,按纸上的视口绘制

调整视口.png

//初始化变换矩阵
Matrix.setRotateM(mOpMatrix, 0, 130, 0, 1, 0);

然后再来看绘制的流程,就和纸上的一致了,所以视口很重要

立方.png


第八副本:龙之盛装 LEVEL1

颜色多没劲,咱们来贴图,经历了这么多,回头看看,感慨良多 这个副本将简单认识贴图,以后还会有高级的龙之盛装


1.第一关卡:三角形贴图

百度了一个小时,愣是没有把贴图整明白,叙述的逻辑性欠佳,都是贴个代码完事,
贴个完整的还好,但都是一段一段的代码...
现在我来捋一遍,最简单的三角形贴图的流程,先把视野移到(0,0,-3) 不然肯定变形

贴图资源.png


1.1:片元代码rect_texture.frag
precision mediump float;
varying vec2 aCoordinate;//贴图坐标系
uniform sampler2D vTexture;//贴图

void main() {
  gl_FragColor=texture2D(vTexture,aCoordinate);
}

1.2:顶点代码:rect_texture.vert
attribute vec3 vPosition;//顶点坐标
uniform mat4 uMVPMatrix; //总变换矩阵

attribute vec2 vCoordinate;//贴图顶点坐标
varying vec2 aCoordinate;//贴图顶点坐标--片元变量

void main() {
  gl_Position = uMVPMatrix*vec4(vPosition,1);
  aCoordinate=vCoordinate;
}

1.3:贴图三角形:TextureRectangle

就是把颜色句柄换成了贴图句柄 再绘制的时候接受一个贴图的id,GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, texId);两行代码贴图
其余的基本一致,下面最重要的是这个贴图id如何获取

/**
 * 作者:张风捷特烈<br/>
 * 时间:2019/1/9 0009:20:09<br/>
 * 邮箱:1981462002@qq.com<br/>
 * 说明:贴图测试
 */
public class TextureRectangle {
    private static final String TAG = "Triangle";
    private Context mContext;
    private int mProgram;//OpenGL ES 程序
    private int mPositionHandle;//位置句柄
    private int mColorHandle;//颜色句柄
    private int muMVPMatrixHandle;//顶点变换矩阵句柄
    private FloatBuffer vertexBuffer;//顶点缓冲
    private final int vertexStride = COORDS_PER_VERTEX * 4; // 3*4=12
    static final int COORDS_PER_VERTEX = 3;//数组中每个顶点的坐标数
    static final int c = 1;//数组中每个顶点的坐标数
    static float sCoo[] = {   //以逆时针顺序
            -c, c, 0.0f, // p0
            -c, -c, 0.0f, // p1
            c, -c, 0.0f, // p2
    };

    private final float[] textureCoo = {
            0.0f,0.0f,
            0.0f,1.0f,
            1.0f,0.0f,
    };

    static final int TEXTURE_PER_VERTEX = 2;//数组中每个顶点的坐标数
    private final int vertexTextureStride = TEXTURE_PER_VERTEX * 4; // 4*4=16

    private ShortBuffer idxBuffer;
    //索引数组
    private short[] idx = {
            1, 2, 3,
    };
    private FloatBuffer mTextureCooBuffer;

    public TextureRectangle(Context context) {
        mContext = context;
        //初始化顶点字节缓冲区
        bufferData();//缓冲顶点数据
        initProgram();//初始化OpenGL ES 程序
    }

    /**
     * 缓冲数据
     */
    private void bufferData() {
        vertexBuffer = GLUtil.getFloatBuffer(sCoo);
        mTextureCooBuffer = GLUtil.getFloatBuffer(textureCoo);
        idxBuffer = GLUtil.getShortBuffer(idx);
    }

    /**
     * 初始化OpenGL ES 程序
     */
    private void initProgram() {
        ////顶点着色
        int vertexShader = GLUtil.loadShaderAssets(mContext,
                GLES20.GL_VERTEX_SHADER, "rect_texture.vert");
        //片元着色
        int fragmentShader = GLUtil.loadShaderAssets(mContext,
                GLES20.GL_FRAGMENT_SHADER, "rect_texture.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, "vCoordinate");
        //获取程序中总变换矩阵uMVPMatrix成员的句柄
        muMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix");
    }

    public void draw(float[] mvpMatrix, int texId ) {
        // 将程序添加到OpenGL ES环境中
        GLES20.glUseProgram(mProgram);
        //启用三角形顶点的句柄
        GLES20.glEnableVertexAttribArray(mPositionHandle);
        //启用三角形顶点颜色的句柄
        GLES20.glEnableVertexAttribArray(mColorHandle);

        //顶点矩阵变换
        GLES20.glUniformMatrix4fv(muMVPMatrixHandle, 1, false, mvpMatrix, 0);

        //准备三角顶点坐标数据
        GLES20.glVertexAttribPointer(
                mPositionHandle,//int indx, 索引
                COORDS_PER_VERTEX,//int size,大小
                GLES20.GL_FLOAT,//int type,类型
                false,//boolean normalized,//是否标准化
                vertexStride,// int stride,//跨度
                vertexBuffer);// java.nio.Buffer ptr//缓冲

        //准备三角顶点颜色数据
        GLES20.glVertexAttribPointer(
                mColorHandle,
                TEXTURE_PER_VERTEX,
                GLES20.GL_FLOAT,
                false,
                vertexTextureStride,
                mTextureCooBuffer);
        //绑定纹理
        GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, texId);

        //绘制
        GLES20.glDrawElements(GLES20.GL_TRIANGLES, idx.length, GLES20.GL_UNSIGNED_SHORT, idxBuffer);
        //禁用顶点数组:
        //禁用index指定的通用顶点属性数组。
        // 默认情况下,禁用所有客户端功能,包括所有通用顶点属性数组。
        // 如果启用,将访问通用顶点属性数组中的值,
        // 并在调用顶点数组命令(如glDrawArrays或glDrawElements)时用于呈现
        GLES20.glDisableVertexAttribArray(mPositionHandle);
    }
}

1.4:加载纹理

我在GLUtil中封装了两个方法

/**
 * 资源id 加载纹理
 * @param ctx 上下文
 * @param resId 资源id
 * @return 纹理id
 */
public static int loadTexture(Context ctx, int resId) {
    Bitmap bitmap = BitmapFactory.decodeResource(ctx.getResources(), resId);
    return loadTexture(ctx, bitmap);
}
/**
 * bitmap 加载纹理
 * @param ctx 上下文
 * @param bitmap bitmap
 * @return 纹理id
 */
public static int loadTexture(Context ctx, Bitmap bitmap) {
    //生成纹理ID
    int[] textures = new int[1];
    //(产生的纹理id的数量,纹理id的数组,偏移量)
    GLES20.glGenTextures(1, textures, 0);
    int textureId = textures[0];
    GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureId);
    GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST);
    GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
    GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE);
    GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE);
    //实际加载纹理(纹理类型,纹理的层次,纹理图像,纹理边框尺寸)
    GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bitmap, 0);
    bitmap.recycle();          //纹理加载成功后释放图片
    return textureId;
}

1.5:使用纹理GLRenderer.java
private int textureId;
---->[GLRenderer#onSurfaceCreated]------
textureId = GLUtil.loadTexture(mContext, R.mipmap.mian_a);//初始化纹理

---->[GLRenderer#onDrawFrame]------
mTextureRectangle.draw(mMVPMatrix,textureId);//绘制时使用纹理

纹理三角形.png


2.第二关卡:矩形纹理

这是一道送分题,没别的,坐标改改就行了

矩形贴图.png

 static float sCoo[] = {   //以逆时针顺序
         -c, c, 0.0f, // p0
         -c, -c, 0.0f, // p1
         c, -c, 0.0f, // p2
         c, c, 0.0f, //p3
 };
 
 private final float[] textureCoo = {
        0.0f,0.0f,
        0.0f,1.0f,
        1.0f,0.0f,
        1.0f,1.0f,
};

//索引数组
private short[] idx = {
        1, 2, 3,
        0, 1, 3,
};

有兴趣的可以动动贴图坐标系的点看一下,自己了解一下贴图坐标系


(出现一道门)NPC:恭喜勇者攻克第八副本,请使用您的立方开启这道黑龙之门
自此,您将正式踏入黑龙领地,祝您征途顺利
我将立方嵌入门中,大门打开,眼前竟是...

OK,本集结束,下一集:"圣火之光",敬请期待

后记:捷文规范

1.本文成长记录及勘误表
项目源码日期备注
V0.1-github2018-1-11Android多媒体之GL-ES战记第二集--谜团立方
2.更多关于我
笔名QQ微信爱好
张风捷特烈1981462002zdl1994328语言
我的github我的简书我的掘金个人网站

icon_wx_200.png