Opengl ES之矩阵变换(上)

前言

说到矩阵变换,我们第一时间想到的就是大学时代的线性代数这些复杂的东西,突然有了一种令人从入门到放弃的念头,不慌,作为了一个应用层的CV工程师,
在实际应用中线性代数哪些复杂的计算根本不用我们自己去算,绝大部分情境下直接使用Matrix这个类或者glm这个库即可。

关于矩阵与向量的相关知识,矩阵的加减乘除等规则,这里就不展开细说,感兴趣的同学自行查阅线性代数即可,不过这些规则忘记了也没关系,反正有API可用。

我们知道在Opengl中有很多中坐标系,在Opengl中矩阵的一大作用就是将坐标从一个坐标系转换到另一个坐标系下,同时还可以通过矩阵实现一些形变的效果,
今天我们就使用矩阵的方式搭配Opengl ES实现平移、缩放、旋转等一些形变变换的效果。

通常来说在Opengl ES中的矩阵都是一个4X4的矩阵,也就是一个包含16个元素的一维数组。

下面以Matrix这个类介绍一下矩阵变换的一些常用方法。下面介绍的矩阵变换所参考的坐标系统都是一样的,均是下图这个:

Opengl ES之矩阵变换(上)

单位矩阵

所谓的单位矩阵就是左上角到右下角对角线值均为1的矩阵,又成为单元矩阵。使用Matrix.setIdentityM方法可以将一个矩阵变为单位矩阵。

矩阵平移

矩阵平移所使用的方法是Matrix.translateM

需要注意的是在Opengl在顶点坐标的值是在-1到1之间,因此translateX的范围可以为-2到2。为什么呢?因为-1到1的距离是2,因此往最多可以往左移动2,同理,最多可以往右移动2。

矩阵旋转

矩阵旋转所使用的方法是Matrix.rotateM,其中第三个参数是表示选旋转的角度,后面的三个参数xyz代表的是绕那个轴旋转,绕那个轴旋转就把那个轴的参数设置成1,其他轴设置成0即可。

矩阵缩放

矩阵缩放所使用的方法是Matrix.scaleM

组合矩阵的写法

假如有以下形变步骤,先绕Z轴旋转90度,再向X轴平移0.5,最后X轴缩放0.9倍,那么最终这个形变矩阵该如何计算呢?是以下这个写法吗?

Matrix.rotateM(mvpMatrix, 0, 90, 0, 0, 1);
Matrix.translateM(mvpMatrix, 0, 0.5, 0, 0);
Matrix.scaleM(mvpMatrix, 0, 0.9, 1f, 0f);

不是的,组合矩阵的写法有一个规则,这个规则大家一定要记住:

在组合矩阵时,先进行缩放操作,然后是旋转,最后才是位移,但是写法需要反正写,也就是先写translateM,然后rotateM,最后scaleM

如果不这样写会发生什么呢?例如顺着写,先写scaleM,然后是rotateM,最后写translateM,测试时就会出现问题,旋转超过180度之后再移动,就会出现移动方向相反的情况。

因此以上例子正确的写法应该是这样子的:

Matrix.translateM(mvpMatrix, 0, 0.5, 0, 0);
Matrix.rotateM(mvpMatrix, 0, 90, 0, 0, 1);
Matrix.scaleM(mvpMatrix, 0, 0.9, 1f, 0f);

show me code

在Opengl ES中可以使用mat4来表示一个4X4的矩阵,我们将总的变换矩阵在CPU中计算好之后以uniform的形式传递到着色器中去。
在顶点着色器中将矩阵与顶点坐标相乘的结果作为新的顶点输出坐标即可完成矩阵变换。

以下是MatrixTransformOpengl.cpp的详细代码:

// 顶点着色器
static const char *ver = "#version 300 es\n"
                         "in vec4 aPosition;\n"
                         "in vec2 aTexCoord;\n"
                         "out vec2 TexCoord;\n"
                         "uniform mat4 mvpMatrix;\n"
                         "void main() {\n"
                         "  TexCoord = aTexCoord;\n"
                         "  gl_Position = mvpMatrix * aPosition;\n"
                         "}";

// 片元着色器
static const char *fragment = "#version 300 es\n"
                              "precision mediump float;\n"
                              "out vec4 FragColor;\n"
                              "in vec2 TexCoord;\n"
                              "uniform sampler2D ourTexture;\n"
                              "void main()\n"
                              "{\n"
                              "    FragColor = texture(ourTexture, TexCoord);\n"
                              "}";


// 使用绘制两个三角形组成一个矩形的形式(三角形带)
// 第一第二第三个点组成一个三角形,第二第三第四个点组成一个三角形
const static GLfloat VERTICES[] = {
        1.0f,-1.0f, // 右下
        1.0f,1.0f, // 右上
        -1.0f,-1.0f, // 左下
        -1.0f,1.0f // 左上
};

// 贴图纹理坐标(参考手机屏幕坐标系统,原点在左上角)
//由于对一个OpenGL纹理来说,它没有内在的方向性,因此我们可以使用不同的坐标把它定向到任何我们喜欢的方向上,然而大多数计算机图像都有一个默认的方向,它们通常被规定为y轴向下,X轴向右
const static GLfloat TEXTURE_COORD[] = {
        1.0f,1.0f, // 右下
        1.0f,0.0f, // 右上
        0.0f,1.0f, // 左下
        0.0f,0.0f // 左上
};

MatrixTransformOpengl::MatrixTransformOpengl():BaseOpengl() {
    initGlProgram(ver,fragment);
    positionHandle = glGetAttribLocation(program,"aPosition");
    textureHandle = glGetAttribLocation(program,"aTexCoord");
    textureSampler = glGetUniformLocation(program,"ourTexture");
    matrixHandle = glGetUniformLocation(program,"mvpMatrix");
}

MatrixTransformOpengl::~MatrixTransformOpengl() noexcept {
    LOGD("MatrixTransformOpengl析构函数");
}

void MatrixTransformOpengl::setMvpMatrix(float *mvp) {
    for (int i = 0; i < 16; ++i) {
        mvpMatrix[i] = mvp[i];
    }
}

void MatrixTransformOpengl::setPixel(void *data, int width, int height, int length) {
    LOGD("texture setPixel");
    imageWidth = width;
    imageHeight = height;
    glGenTextures(1, &textureId);

    // 激活纹理,注意以下这个两句是搭配的,glActiveTexture激活的是那个纹理,就设置的sampler2D是那个
    // 默认是0,如果不是0的话,需要在onDraw的时候重新激活一下?
//    glActiveTexture(GL_TEXTURE0);
//    glUniform1i(textureSampler, 0);

// 例如,一样的
    glActiveTexture(GL_TEXTURE2);
    glUniform1i(textureSampler, 2);
    // 绑定纹理
    glBindTexture(GL_TEXTURE_2D, textureId);
    // 为当前绑定的纹理对象设置环绕、过滤方式
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
    // 生成mip贴图
    glGenerateMipmap(GL_TEXTURE_2D);

    glBindTexture(GL_TEXTURE_2D, textureId);

    // 解绑定
    glBindTexture(GL_TEXTURE_2D, 0);
}

void MatrixTransformOpengl::onDraw() {

//    glViewport(0,0,imageWidth,imageHeight);

    glClearColor(0.0f, 1.0f, 0.0f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT);
    glUseProgram(program);

    // 激活纹理
    glActiveTexture(GL_TEXTURE2);
    glUniform1i(textureSampler, 2);

    // 绑定纹理
    glBindTexture(GL_TEXTURE_2D, textureId);

    // 设置矩阵
    glUniformMatrix4fv(matrixHandle, 1, GL_FALSE,mvpMatrix);

    /**
     * size 几个数字表示一个点,显示是两个数字表示一个点
     * normalized 是否需要归一化,不用,这里已经归一化了
     * stride 步长,连续顶点之间的间隔,如果顶点直接是连续的,也可填0
     */
    // 启用顶点数据
    glEnableVertexAttribArray(positionHandle);
    glVertexAttribPointer(positionHandle,2,GL_FLOAT,GL_FALSE,0,VERTICES);

    // 纹理坐标
    glEnableVertexAttribArray(textureHandle);
    glVertexAttribPointer(textureHandle,2,GL_FLOAT,GL_FALSE,0,TEXTURE_COORD);

    // 4个顶点绘制两个三角形组成矩形
    glDrawArrays(GL_TRIANGLE_STRIP,0,4);

    glUseProgram(0);

    // 禁用顶点
    glDisableVertexAttribArray(positionHandle);
    if(nullptr != eglHelper){
        eglHelper->swapBuffers();
    }

    glBindTexture(GL_TEXTURE_2D, 0);
}

java层的MatrixActivity.java实例代码如下:

public class MatrixActivity extends BaseGlActivity {

    private MatrixTransformOpengl matrixTransformOpengl;
    // 遵守先缩放再旋转最后平移的顺序
    // 首先执行缩放,接着旋转,最后才是平移。这就是矩阵乘法的工作方式。
    private final float[] mvpMatrix = new float[16];
    // 因为在Opengl在顶点坐标的值是在-1到1之间,因此translateX的范围可以为-2到2。
    private float translateX = 0;
    private float scaleX = 1;
    private float rotationZ = 0;

    @Override
    public int getLayoutId() {
        return R.layout.activity_gl_matrix;
    }

    @Override
    public BaseOpengl createOpengl() {
        matrixTransformOpengl = new MatrixTransformOpengl();
        return matrixTransformOpengl;
    }

    @Override
    public Bitmap requestBitmap() {
        BitmapFactory.Options options = new BitmapFactory.Options();
        // 不缩放
        options.inScaled = false;
        Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.ic_boy, options);

        // 设置一下矩阵
        Matrix.setIdentityM(mvpMatrix, 0);
        matrixTransformOpengl.setMvpMatrix(mvpMatrix);

        return bitmap;
    }

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        findViewById(R.id.bt_translate).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                if (null != matrixTransformOpengl) {
                    translateX += 0.1;
                    if(translateX >=2 ){
                        translateX = 0f;
                    }
                    updateMatrix();
                }
            }
        });

        findViewById(R.id.bt_scale).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                if (null != matrixTransformOpengl) {
                    scaleX += 0.1;
                    updateMatrix();
                }
            }
        });

        findViewById(R.id.bt_rotate).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                if (null != matrixTransformOpengl) {
                    rotationZ += 10;
                    updateMatrix();
                }
            }
        });

        findViewById(R.id.bt_reset).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                if (null != matrixTransformOpengl) {
                    translateX = 0;
                    scaleX = 1;
                    rotationZ = 0;
                    updateMatrix();
                }
            }
        });

    }

    private void updateMatrix() {
        Matrix.setIdentityM(mvpMatrix, 0);
        // 重点注释
        // 在组合矩阵时,先进行缩放操作,然后是旋转,最后才是位移,但是写法需要反正写,也就是先写translateM,然后rotateM,最后scaleM
        // 如果不这样写会发生什么呢?例如顺这写,先写scaleM,然后是rotateM,最后写translateM,测试时就会出现问题,旋转超过180度之后再移动,就会出现移动方向相反的情况
        Matrix.translateM(mvpMatrix, 0, translateX, 0, 0);
        Matrix.rotateM(mvpMatrix, 0, rotationZ, 0, 0, 1);
        Matrix.scaleM(mvpMatrix, 0, scaleX, 1f, 0f);
        matrixTransformOpengl.setMvpMatrix(mvpMatrix);
        myGLSurfaceView.requestRender();
    }
}

运行结果

系列教程源码

https://github.com/feiflyer/NDK_OpenglES_Tutorial

后续demo如果有完善可能会更新。

Opengl ES系列入门介绍

Opengl ES之EGL环境搭建
Opengl ES之着色器
Opengl ES之三角形绘制
Opengl ES之四边形绘制
Opengl ES之纹理贴图
Opengl ES之VBO和VAO
Opengl ES之EBO
Opengl ES之FBO
Opengl ES之PBO
Opengl ES之YUV数据渲染
YUV转RGB的一些理论知识
Opengl ES之RGB转NV21
Opengl ES之踩坑记
Opengl ES之矩阵变换(上)
Opengl ES之矩阵变换(下)
Opengl ES之水印贴图

关注我,一起进步,人生不止coding!!!
微信扫码关注

原文链接:https://www.cnblogs.com/goFlyer/p/17239870.html

本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:Opengl ES之矩阵变换(上) - Python技术站

(0)
上一篇 2023年4月18日
下一篇 2023年4月18日

相关文章

  • 当我第一次通过Kotlin和Compose来实现一个Canvas时, 我收获了什么?

    当我第一次通过Kotlin和Compose来实现一个Canvas时, 我收获了什么? 自从2019年Google推荐Kotlin为Android开发的首选语言以来已经经历了将近四年的时间, Compose的1.0版本也发布了将近2年的时间, Kotlin+Compose在现阶段的Android开发过程中还远远达不到主流的程度. 我们是否应该开始尝试这个组合?…

    Android 2023年4月27日
    00
  • 【FAQ】统一扫码服务常见问题及解答

    1.隐私政策是怎么样的?收集哪些信息? 关于Scan Kit的隐私政策及收集的信息,请查看SDK隐私安全说明。 Android:SDK隐私安全说明 iOS:SDK隐私安全说明 2.如何使用多码识别?多码模式下如何实现指定条码?多码模式的坐标支持返回坐标么?多码模式下实现自动放大? 1)统一扫码服务支持返回多码 ①android项目建议您参考MultiProc…

    Android 2023年4月25日
    00
  • 免费广告效果监测服务,实现全链路营销效果跟踪

    广告主们都希望以低预算获得更高的广告投放收益,在投放广告后,想要了解高回报的渠道,往往需要收集并分析繁杂的数据,耗时耗力。通过广告监测,广告主可以准确的追溯用户渠道来源,看到不同流量的用户价值,分析广告投放效果,从而指导广告的出价和投放素材的优化,把预算花在刀刃上。 针对广告主们广告监测的需求,华为分析服务提供免费的广告监测能力,为有广告监测需求的广告主节省…

    Android 2023年4月17日
    00
  • Android Studio 学习-第三章 Activity 第一组

    事先申明:所有android 类型的学习记录全部基于《第一行代码 Android》第三版,在此感谢郭霖老师的书籍帮助。 1.手动创建Activity       在Project类型目录中寻找到 项目/app/src/main/java/com.example.activitytest 在 com.example.activitytest包右键新建Activ…

    Android 2023年4月17日
    00
  • 语言录制兼容长按跟点击录制

    录音需求中,往往有两种常规操作。 长按基本实现流程: 监听触摸事件,按下时录制,抬起时停止。 点击基本流程: 点击开始录制,在次点击停止录制 但是凡事有绝对,如果需要同时支持长按录制抬起结束跟点击录制在次点击结束呢?面对如此无理的需求,从技术层面上怎么如丝滑般去兼容呢。 需要两者兼容,只能从触摸事件入手了,这里的重点其实就在于怎么在触摸事件中去区分点击事件跟…

    Android 2023年4月22日
    00
  • uni-app在跨平台小程序中的实践

       1. 背景  随着移动互联网的高速发展,各家大厂的小程序百花齐放,在用户规模及商业化方面都取得了极大的成功。微信小程序的日活超过5亿,其他追赶者如:支付宝、百度、字节跳动小程序的月活也纷纷超过3亿。鉴于小程序相比App,无需下载等待,有更高的激活率,相比H5有更好的下单转化率,汽车报价业务线借助各平台的优势,大力发展小程序业务。 目前国内头部应用中,有…

    Android 2023年4月18日
    00
  • 关于RecycleView的子元素锁定吸顶效果

      最近有一关于列表元素锁定吸顶的需求。处理方案比较简单的,在外层布局添加一个相同的标题栏控件,根据控件在滚动过中的位置来判断显示隐藏,从而达到效果。使用的关键代码是: int[] loc = new int[2]; view.getLocationOnScreen(loc); int[] fitLoc = new int[2]; outView.getLo…

    Android 2023年4月18日
    00
  • 手机穿戴设备能力共享,提升丰富交互体验

    HUAWEI Wear Engine面向手机和穿戴设备的应用与服务开发者,提供华为穿戴设备开放能力。 开发者通过调用Wear Engine开放能力,可以实现手机上的生态应用与服务给华为穿戴设备发消息、发通知、传输数据,并获取穿戴设备状态、读取传感器数据等,也可以实现华为穿戴设备上的生态应用与服务给手机发消息、传输数据等。 Wear Engine将手机上的生态…

    Android 2023年4月20日
    00
合作推广
合作推广
分享本页
返回顶部