专栏首页Android知识点总结Android多媒体之GLES2战记第四集--移形换影

Android多媒体之GLES2战记第四集--移形换影

视野限制了人对这个宇宙的认知,但没有视野人,将会一无所知

上集说到勇者坠入黑暗之渊,凭借对世界的认知构建出了世界系 到此为止,OpenGL的世界观已经映入脑海,新手十二副本已经通过 接下来等待他们的将是什么,普通副本开启....


普通副本一:斗转星移

第一关卡:绘制矩形

这关简单,回头看看,是不是感觉清晰了许多,下面列一下关键点,其他不说了 这个矩形可不是一开始摸黑瞎画的,而是轴系下可感的矩形,地址:matrix.MatrixRectangle 视角移回正方向0f, 0f, -6,这时候画出的是后面

轴系下的矩形.png

//顶点坐标
private float mVertex[] = {   //以逆时针顺序
        -1f, 1f, 1.0f, // p0
        -1f, -1f, 1.0f, // p1
        1f, -1f, 1.0f, // p2
        1f, 1f, 1.0f, //p3
};

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

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

2.第二关卡:封装矩阵变换

视图的矩阵变换和投影矩阵感觉在WorldRenderer里也有点麻烦 封装一下吧,还是那四个矩阵

/**
 * 作者:张风捷特烈<br/>
 * 时间:2019/1/14/014:11:29<br/>
 * 邮箱:1981462002@qq.com<br/>
 * 说明:矩阵变化栈
 */

public class MatrixStack {
    private static float[] mProjectionMatrix = new float[16];//投影矩阵
    private static float[] mViewMatrix = new float[16];//相机矩阵
    private static float[] mOpMatrix;//变换矩阵
    //获取具体物体的总变换矩阵
    private static float[] mMVPMatrix = new float[16];

    /**
     * 获取不变换初始矩阵
     */
    public static void reset() {
        //mOpMatrix转化为单位阵
        mOpMatrix = new float[]{
                1, 0, 0, 0,
                0, 1, 0, 0,
                0, 0, 1, 0,
                0, 0, 0, 1,
        };
    }
    /**
     * 设置沿xyz轴移动
     *
     * @param x 移动的 x 分量
     * @param y 移动的 y 分量
     * @param z 移动的 z 分量
     */
    public static void translate(float x, float y, float z) {
        Matrix.translateM(mOpMatrix, 0, x, y, z);
    }

    /**
     * 设置沿(x,y,z)点旋转
     *
     * @param deg 角度
     * @param x   旋转点的 x 分量
     * @param y   旋转点的 y 分量
     * @param z   旋转点的 z 分量
     */
    public static void rotate(float deg, float x, float y, float z) {
        Matrix.rotateM(mOpMatrix, 0, deg, x, y, z);
    }

    /**
     * 设置缩放
     * @param x   缩放的 x 分量
     * @param y   缩放的 y 分量
     * @param z   缩放的 z 分量
     */
    public static void scale(float x, float y, float z) {
        Matrix.scaleM(mOpMatrix, 0, x, y, z);
    }

    /**
     * 相机的位置
     * @param cx  摄像机位置x
     * @param cy  摄像机位置y
     * @param cz  摄像机位置z
     * @param tx  摄像机目标点x
     * @param ty  摄像机目标点y
     * @param tz  摄像机目标点z
     * @param upx 摄像机UP向量X分量
     * @param upy 摄像机UP向量Y分量
     * @param upz 摄像机UP向量Z分量
     */
    public static void lookAt(float cx, float cy, float cz,
                              float tx, float ty, float tz,
                              float upx, float upy, float upz) {
        Matrix.setLookAtM(mViewMatrix, 0,
                cx, cy, cz,
                tx, ty, tz,
                upx, upy, upz);
    }

    /**
     *  设置透视投影参数
     * @param left   near面的left
     * @param right  near面的right
     * @param bottom near面的bottom
     * @param top    near面的top
     * @param near   near面距顶点长
     * @param far    far面距顶点长
     */
    public static void frustum(
            float left, float right, float bottom,
            float top, float near, float far) {
        Matrix.frustumM(mProjectionMatrix, 0,
                left, right, bottom,
                top, near, far);
    }
    
    /**
     * 查看栈顶的变换矩阵
     *
     * @return mMVPMatrix
     */
    public static float[] peek() {
        submit();
        return mMVPMatrix;
    }
    
    /**
     * 提交变换
     */
    private static void submit() {
        Matrix.multiplyMM(
                mMVPMatrix, 0,
                mViewMatrix, 0,
                mOpMatrix, 0);

        Matrix.multiplyMM(
                mMVPMatrix, 0,
                mProjectionMatrix, 0,
                mMVPMatrix, 0);
    }

    //获取具体物体的变换矩阵
    public static float[] getOpMatrix() {
        return mOpMatrix;
    }
}

MatrixStack使用

---->[WorldRenderer#onSurfaceChanged]------------
MatrixStack.frustum(
        -ratio, ratio, -1, 1,
        3, 9);
MatrixStack.lookAt(2, 2, -6,
        0f, 0f, 0f,
        0f, 1.0f, 0.0f);
MatrixStack.initStack();

---->[WorldRenderer#onDrawFrame]------------
MatrixStack.rotate(currDeg, 0, 1, 0);
mWorldShape.draw(MatrixStack.peek());

加面旋转.gif


3.第三关卡:操作矩阵的状态栈

想一下,如果我平移画一个立方,mOpMatrix会变化, 我再平移画一个立方时mOpMatrix会在上一个mOpMatrix的基础上进行变换 这种情况下我们是希望mOpMatrix在画完后回到之前状态的,这就涉及栈的概念 此处没用Java的Stack类,因为元素是操作矩阵,即float[],不好放

3.1:没有恢复状态时

一个x方向-1.5,另一个x方向+1.5

没有恢复状态.png

MatrixStack.translate(-1.5f, 0, 0);
mWorldShape.draw(MatrixStack.peek());
MatrixStack.translate(1.5f, 0, 0);
mWorldShape.draw(MatrixStack.peek());

3.2:MatrixStack添加恢复机制
//默认栈深为10,栈中元素为float[16]--即变换矩阵
private static float[][] mStack = new float[10][16];
private static int stackTop = -1;//栈顶无元素时为-1

/**
 * 矩阵操作准备入栈---保存mOpMatrix状态
 */
public static void save() {
    stackTop++;//栈顶+1
    //op矩阵入栈顶
    System.arraycopy(mOpMatrix, 0, mStack[stackTop], 0, 16);
}

/**
 * 栈顶出栈--恢复mOpMatrix
 */
public static void restore() {
    System.arraycopy(mStack[stackTop], 0, mOpMatrix, 0, 16);
    stackTop--;//栈顶下移
}

3.3:使用状态恢复机制

一个x方向-1.5,另一个x方向+1.5,这才是我们想要的效果

有恢复状态.png

MatrixStack.save();
MatrixStack.translate(-1.5f, 0, 0);
mWorldShape.draw(MatrixStack.peek());
MatrixStack.restore();

MatrixStack.save();
MatrixStack.translate(1.5f, 0, 0);
mWorldShape.draw(MatrixStack.peek());
MatrixStack.restore();

4.第四关卡:总结移动旋转缩放

综合变换.gif

4.1:注意setRotateMrotateM的区别

一开始写成setRotateM了,效果叠加不上,然后debug一下,发现: 源码中setRotateM会将一些之置零或1,也就是重置之前的变换,所以

public static void setRotateM(float[] rm, int rmOffset,
        float a, float x, float y, float z) {
    rm[rmOffset + 3] = 0;
    rm[rmOffset + 7] = 0;
    rm[rmOffset + 11]= 0;
    rm[rmOffset + 12]= 0;
    rm[rmOffset + 13]= 0;
    rm[rmOffset + 14]= 0;
    rm[rmOffset + 15]= 1;

4.2.使用:
 MatrixStack.save();
 MatrixStack.translate(-1.5f, 0, 0);
 MatrixStack.rotate(currDeg, -1.5f, 0, 0);
 mWorldShape.draw(MatrixStack.peek());
 MatrixStack.restore();
 
 MatrixStack.save();
 MatrixStack.translate(1.5f, 0, 0);
 MatrixStack.rotate(currDeg, 0, 1, 0);
 MatrixStack.scale(0.5f,1,0.5f);
 mWorldShape.draw(MatrixStack.peek());
 MatrixStack.restore();

普通副本二:变心之阵

刚才我们用的是Matrix自带的变换方法,自带的灵活性肯定欠佳 下面我们来看一下这16个数字是怎么让图形变换的

1.第一关卡:Matrix.translateM分析

怎么分析呢?废话,当然是看源码了

/**
 * Translates matrix m by x, y, and z in place.
 *
 * @param m matrix
 * @param mOffset index into m where the matrix starts
 * @param x translation factor x
 * @param y translation factor y
 * @param z translation factor z
 */
public static void translateM(
        float[] m, int mOffset,
        float x, float y, float z) {
    for (int i=0 ; i<4 ; i++) {
        int mi = mOffset + i;
        m[12 + mi] += m[mi] * x + m[4 + mi] * y + m[8 + mi] * z;
    }
}

mOffset可以看出是索引的偏移量,正常情况下都是0,所以不管他 核心的就是四个for内语句,i从0~3,然后就是这句迷之代码

m[12 + mi] += m[mi] * x + m[4 + mi] * y + m[8 + mi] * z;

mOffset为0时,简化一下:
m[12 + i] += m[i] * x + m[4 + i] * y + m[8 + i] * z;

感觉还是迷之代码的,没关系,再分析一下
i=0:  m[12] += m[0]*x + m[4]*y + m[8]*z
i=1:  m[13] += m[1]*x + m[5]*y + m[9]*z
i=2:  m[14] += m[2]*x + m[6]*y + m[10]*z
i=3:  m[15] += m[3]*x + m[7]*y + m[11]*z

Why ?

2.第二关卡:坐标变换的源头

所以的根源在于这句话,它代表了什么意思? 注意:OpenGL 中的向量是列主元,也就是竖着排

坐标变换.png

IMG20190114155647.jpg


然后算出gl_Position的结果会是一个四维向量

这也就是m12,m13,m14,m15为什么特别,m0,m1,m2,m3为什么和x息息相关

结果.png

再换个角度来看,gl_Position中的四个维度分别代表:x,y,z,w

gl_Position.x = m0*x + m4*y + m8*z + m12
gl_Position.y = m1*x + m5*y + m9*z + m13
gl_Position.z = m2*x + m6*y + m10*z + m14
 
这也说明 m12 m13 m14 三个数分别控制x,y,z的位移 

现在再看translateM,可以看出它的作用是改变第四行 我们传入的是行向量,传入渲染器中变成列向量,相当于转置, 也就是处理时uMVPMatrix的第四列,这样一来路就走通了, translateM通过改变第四行的向量来操作顶点的位置,yes!


3.第三关卡:Matrix.scaleM分析
public static void scaleM(float[] m, int mOffset,
        float x, float y, float z) {
    for (int i=0 ; i<4 ; i++) {
        int mi = mOffset + i;
        m[     mi] *= x;
        m[ 4 + mi] *= y;
        m[ 8 + mi] *= z;
    }
}

这个就简单了:
i=0:  m[0] = m[0]*x     m[4] = m[4]*y   m[8] = m[8]*z
i=1:  m[1] = m[1]*x     m[5] = m[5]*y   m[9] = m[9]*z
i=2:  m[2] = m[2]*x     m[6] = m[4]*y   m[10] = m[10]*z
i=3:  m[3] = m[3]*x     m[7] = m[7]*y   m[11] = m[11]*z

>这样一看是不是豁然开朗

旋转还是算了吧,没几张草稿纸还真算不清,源码看着也挺复杂 等以后哪天闲到怀疑人生的时候再来慢慢算算吧。


副本三:形之累变

在一个圆环上等分点,可以形成若干三角形,由此可以拼合出图形 这个副本是为了练习一下规律型的运算,发现规律,加以使用

环.gif


1.第一关卡:环
/**
 *  初始化顶点坐标与颜色
 * @param splitCount 分割点数
 * @param r 内圆半径
 * @param R 外圈半径
 */
public void initVertex(int splitCount, float r, float R) {
    //顶点坐标数据的初始化
    verticeCount = splitCount * 2 + 2;
    float[] vertices = new float[verticeCount * 3];//坐标数据
    float thta = 360.f / splitCount;
    for (int i = 0; i < vertices.length; i += 3) {
        int n = i / 3;
        if (n % 2 == 0) {//偶数点--内圈
            vertices[i] = (float) (r * Math.cos(Change.rad((n / 2) * thta)));//x
            vertices[i + 1] = (float) (r * Math.sin(Change.rad((n / 2) * thta)));//y
            vertices[i + 2] = 0;//z
        } else {//奇数点--外圈
            vertices[i] = (float) (R * Math.cos(Change.rad((n / 2) * thta)));//x
            vertices[i + 1] = (float) (R * Math.sin(Change.rad((n / 2) * thta)));//y
            vertices[i + 2] = 0;//z
        }
    }
    //i+8 表示 每次跨度两个点
    //橙色:0.972549f,0.5019608f,0.09411765f,1.0f
    float colors[] = new float[verticeCount * 4];
    for (int i = 0; i < colors.length; i += 8) {
        colors[i + 0] = 1;
        colors[i + 1] = 1;
        colors[i + 2] = 1;
        colors[i + 3] = 1;
        colors[i + 4] = 0.972549f;
        colors[i + 5] = 0.5019608f;
        colors[i + 6] = 0.09411765f;
        colors[i + 7] = 1.0f;
    }
    vertexBuffer = GLUtil.getFloatBuffer(vertices);
    mColorBuffer = GLUtil.getFloatBuffer(colors);
}

这种情况下,使用GL_TRIANGLE_STRIP时极好的,相邻三点组成三角形

GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, verticeCount);

八边环.png

调整参数.png


2.第二关卡:GLES20.GL_TRIANGLE_FAN三角形

fan 即扇子,一个中心,连接其他顶点,好处是比较节省顶点 这样可以绘制任意正多边形

fan.png

正八边形.png

/**
 * 初始化顶点坐标与颜色
 *
 * @param splitCount 分割点数
 * @param r          内圆半径
 */
public void initVertex(int splitCount, float r) {
    //顶点坐标数据的初始化
    verticeCount = splitCount + 2;
    float[] vertices = new float[verticeCount * 3];//坐标数据
    float thta = 360.f / splitCount;
    vertices[0] = 0;
    vertices[1] = 0;
    vertices[2] = 0;
    for (int n = 1; n <= verticeCount - 1; n++) {
        vertices[n * 3] = (float) (r * Math.cos(Change.rad((n - 1) * thta)));//x
        vertices[n * 3 + 1] = (float) (r * Math.sin(Change.rad((n - 1) * thta)));//y
        vertices[n * 3 + 2] = 0;//z
    }
    //顶点颜色
    //橙色:0.972549f,0.5019608f,0.09411765f,1.0f
    float colors[] = new float[verticeCount * 4];
    colors[0] = 1;
    colors[1] = 1;
    colors[2] = 1;
    colors[3] = 1;
    for (int i = 1; i <= verticeCount - 1; i++) {
        colors[4 * i] = 0.972549f;
        colors[4 * i + 1] = 0.5019608f;
        colors[4 * i + 2] = 0.09411765f;
        colors[4 * i + 3] = 1.0f;
    }
    vertexBuffer = GLUtil.getFloatBuffer(vertices);
    mColorBuffer = GLUtil.getFloatBuffer(colors);
}

绘制时使用:GLES20.GL_TRIANGLE_FAN

GLES20.glDrawArrays(GLES20.GL_TRIANGLE_FAN, 0, verticeCount);

3.第三关卡:总结一下几种绘制方式

[图片上传失败...(image-bece53-1548060318045)]

三角形系列

glDrawArrays:
---->[绘制点线]-------
GLES20.GL_POINTS            绘制点
GLES20.GL_LINES             两点一线
GLES20.GL_LINE_STRIP        相邻两点一线(不连首尾)
GLES20.GL_LINE_LOOP         相邻两点一线(连首尾)

---->[绘制三角形]-------
GLES20.GL_TRIANGLES         三点一个
GLES20.GL_TRIANGLE_STRIP    相邻三点一个
GLES20.GL_TRIANGLE_FAN      第一点中心,散射到其他点

glDrawElements:
---->[顶点索引绘制]------
GLES20.glDrawElements(
GLES20.GL_TRIANGLES, idx.length, 
GLES20.GL_UNSIGNED_SHORT, idxBuffer);

普通副本四:世界之幕

1.第一关卡:再看投影矩阵

视野

下面是视点:(2,2,-6) far 9 near 3的单位立方,结合上面的图来看看:

立方.png


near 和 far两个面决定了视野,即两面间的内容可见 near面的上下左右的长度也决定这物体的高矮胖瘦,比如左右减半后, 你应该能想了视野被"压扁"了,物体也会随之变扁

左右除以二.png

MatrixStack.frustum(
        -ratio/2, ratio/2, -1, 1,
        3f, 9);

2.第二关卡:near面与平移变换

near面越近,成像越小,你可以根据那个图,自己想想

near.png

当观察到的事物在near和far面组成的棱台之外,便不可见 所以说眼睛限制了你对这个宇宙的认知,但没有眼睛你会一无所知

平移视点.png


3.平行投影

这个比较简单,也就是没有透视,立方的对线都是平行的,参数和透视投影一致 3D一般都是透视投影,既然有这个API,应该有特定的用途吧

public static void orthoM(
        float left, float right, float bottom,
        float top, float near, float far) {
    Matrix.orthoM(mProjectionMatrix, 0,
            left, right, bottom,
            top, near, far);
}

平行投影0,0,-6.png

平行投影2,2,-6.png

好了,本集结束,下一集:宇宙之光

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Android动画Animator家族使用指南

    张风捷特烈
  • Android绘制函数图象及正弦函数的介绍

    张风捷特烈
  • Android多媒体之GLES2战记第六集--九层之台

    张风捷特烈
  • PCL中可用的PointT类型

    PointXYZ是使用最常见的一个点数据类型,因为他之包含三维XYZ坐标信息,这三个浮点数附加一个浮点数来满足存储对齐,可以通过points[i].d...

    点云PCL博主
  • Android 动画总结(6) - 估值器

    七适散人
  • 一个Sqrt函数引发的血案

    好吧,我承认我标题党了,不过既然你来了,就认真看下去吧,保证你有收获。 我们平时经常会有一些数据运算的操作,需要调用sqrt,exp,abs等函数,那么时候你...

    范蠡
  • Android 动画总结(4) - 插值器

    Interpolator 插值器,作用就是把 0 到 1 的浮点值变化映射到另一个浮点值变化,即根据时间流逝百分比计算出动画变化百分比。

    七适散人
  • OpenGL ES(四) 变换

    用户2215591
  • [PYTHON] 核心编程笔记之十-Py

    错误时语法或是逻辑上的,语法错误指示软件的结构上有错误,导致不能被解释器解释或编译器无法编译

    py3study
  • 自定义View:Padding与绘制内容

    有些时候,扩展Android框架提供的view并不能很好地解决问题。很多情况下,我们需要进行view绘制来实现想要的效果。本文我们将介绍如何使用Canvas绘制...

    技术小黑屋

扫码关注云+社区

领取腾讯云代金券