前言

说到矩阵变换,我们第一时间想到的就是大学时代的线性代数这些复杂的东西,突然有了一种令人从入门到放弃的念头,不慌,作为了一个应用层的CV工程师,

在实际应用中线性代数哪些复杂的计算根本不用我们自己去算,绝大部分情境下直接使用Matrix这个类或者glm这个库即可。

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

我们知道在Opengl中有很多中坐标系,在Opengl中矩阵的一大作用就是将坐标从一个坐标系转换到另一个坐标系下,同时还可以通过矩阵实现一些形变的效果,

今天我们就使用矩阵的方式搭配Opengl ES实现平移、缩放、旋转等一些形变变换的效果。

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

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

单位矩阵

所谓的单位矩阵就是左上角到右下角对角线值均为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!!!

Opengl ES之矩阵变换(上)的更多相关文章

  1. 在 OpenGL ES 2.0 上实现视差贴图(Parallax Mapping)

    在 OpenGL ES 2.0 上实现视差贴图(Parallax Mapping) 视差贴图 最近一直在研究如何在我的 iPad 2(只支持 OpenGL ES 2.0, 不支持 3.0) 上实现 视 ...

  2. 一步步实现windows版ijkplayer系列文章之六——SDL2源码分析之OpenGL ES在windows上的渲染过程

    一步步实现windows版ijkplayer系列文章之一--Windows10平台编译ffmpeg 4.0.2,生成ffplay 一步步实现windows版ijkplayer系列文章之二--Ijkpl ...

  3. OpenGL ES: (4) EGL API详解 (转)

    上一节我们初步学习了 OpenGL ES.EGL.GLSL 的相关概念,了解了它们的功能,以及它们之间的关联.我们知道了 EGL 是绘制 API(比如 OpenGL ES)与 底层平台窗口系统之间的接 ...

  4. [OpenGL ES 02]OpenGL ES渲染管线与着色器

    [OpenGL ES 02]OpenGL ES渲染管线与着色器 罗朝辉 (http://www.cnblogs.com/kesalin/) 本文遵循"署名-非商业用途-保持一致"创 ...

  5. Android OpenGL ES(十三)通用的矩阵变换指令 .

    Android OpenGL ES 对于不同坐标系下坐标变换,大都使用矩阵运算的方法来定义和实现的.这里介绍对应指定的坐标系(比如viewmodel, projection或是viewport) An ...

  6. OpenGL ES(一.概念)

    OpenGL ES是以手持和嵌入式设备为目标的高级3D图形应用程序编程接口,主要的支持平台是iOS,Android,Linux和Windows 1.顶点着色器 他可以用于通过矩阵变换位置,计算照明公式 ...

  7. [OpenGL ES 03]3D变换:模型,视图,投影与Viewport

    [OpenGL ES 03]3D变换:模型,视图,投影与Viewport 罗朝辉 (http://blog.csdn.net/kesalin) 本文遵循“署名-非商业用途-保持一致”创作公用协议 系列 ...

  8. OpenGL ES 2.0 渲染管线 学习笔记

    图中展示整个OpenGL ES 2.0可编程管线 图中Vertex Shader和Fragment Shader 是可编程管线: Vertex Array/Buffer objects 顶点数据来源, ...

  9. 【Android 应用开发】OpenGL ES 2.0 -- 制作 3D 彩色旋转三角形 - 顶点着色器 片元着色器 使用详解

    最近开始关注OpenGL ES 2.0 这是真正意义上的理解的第一个3D程序 , 从零开始学习 . 案例下载地址 : http://download.csdn.net/detail/han120201 ...

  10. OpenGL ES 3.0顶点着色器(一)

    OpenGL ES 3.0流程图 1.Vertex Shader(顶点着色器) 顶点着色实现了一种通用的可编程方法操作顶点. 顶点着色器的输入包括以下几个: • Shader program.程序的顶 ...

随机推荐

  1. 一种基于Modbus的工业通信网关设计

    近年来,随着工业自动化领域的发展,工业现场对网络的可靠性及成本有极高的要求.传统基于串口的工业网关可以满足工业现场的应用,但却要付出高额成本.一种基于 ModBus 设计的工业通信网关就走进人们的眼中 ...

  2. Linux C语言编程基础

    Linux C语言编程基础 选择教材第二章的一节进行编程基础练习 二叉树广度优先遍历(链队) 算法: "head.h" #ifndef _head_h_ #define _head ...

  3. oracle修改表中的列

    declare v_Count1 int := 0; v_Count2 int := 0; v_Count3 int := 0; v_Count4 int := 0; v_Count5 int := ...

  4. SAP ABAP 验证与替代

    1.校验与替代的作用 校验(Validation):在凭证保存前根据设置条件判断此凭证是否有效,其中可以按抬头.行项目或完全凭证来判断,然后再根据Validation设置的消息类型决定凭证是否允许保存 ...

  5. UI自动化之【maven+selenium环境搭建】

    一.下载maven包 官网: http://maven.apache.org/download.cgi 二.配置maven环境变量  配置完之后验证一下:(若出现以下信息可看到maven的版本号就表示 ...

  6. char *setlocale(int category, const char *locale)

    category -- 这是一个已命名的常量,指定了受区域设置影响的函数类别. LC_ALL 包括下面的所有选项. LC_COLLATE 字符串比较.参见 strcoll(). LC_CTYPE 字符 ...

  7. 小梅哥课程学习——串口发送应用之发送数据(可在vivado中仿真出现正确波形)

    //1.底层代码源代码发送10位数据 module uart_pr( clk, reset_n, send_go, data, baud_set, tx_done, uart_tx ); input ...

  8. CSS vw与vh动态设置元素的高度宽度

    做为一个前端开发者,总有一天我们要设置页面某一部分内容自适应浏览器窗口大小,下面分享下使用vw和vh的设置方式, 一波解释: v(即viewport):可视窗口,也就是浏览器窗口大小.vw Viewp ...

  9. 2html5

    多媒体标签 <audio> <audio src='../audio/bxb.mp3' controls="controls" autoplay="au ...

  10. NOIP2009普及组

    T3]细胞分裂 [算法]数论 [题解]均分的本质是A整除B,A整除B等价于A的质因数是B的子集. 1.将m1分解质因数,即m1=p1^a1*p2^a2*...*pk^ak 所以M=m1^m2=p1^( ...