OpenGL ES MVP/变换投影矩阵(8)

news2024/12/27 13:33:30

OpenGL ES MVP/变换投影矩阵(8)

简述

通过前面几节的学习,目前我们已经可以渲染自己想要的图像,也可以通过纹理加载图片进行渲染。接下来我们来学习一下MVP,这里的MVP不是Android应用开发里的框架MVP,而是Model,View,Projection。
首先我们先来解释一下这个是做什么的,这三者都是用来计算最终图像呈现的,举个例子,我们如果要开发一个游戏,游戏里的一个物体放在那里,物体放置的角度不同,我们看到的效果可能不一样,我们从不同的位置角度看物体,看到的也可能不一样,MVP就是在计算物体在不同的放置情况,不同的观察角度最终的成像,接下来我们一个一个介绍。

变换矩阵

在开始介绍MVP三个概念之前,我们先来介绍一下变换矩阵。
变换矩阵是MVP的基础原理,我们这里只介绍一下概念,不会深入它的原理,因为它的原理是一些数学运算,需要一些线性代数的基础。
我们对图像的变换计算,都是通过乘以一个矩阵来实现的,我们只需要知道我们将坐标乘以一个矩阵,将它映射到另一个坐标,这样就可以完成图形的变换,比如平移,缩放,翻转等等,所以我们各种变换都是通过乘以一个变换矩阵实现的,每一个模型都有一个对应的矩阵。

MVP

坐标变换:
最终坐标 = Mat(projection) * Mat(View) * Mat(model) * 原坐标

投影(Projection)

我们的图像可能是3D的,但是最终需要显示在我们的屏幕上,我们的屏幕是2D的,只能显示一个平面,所以我们需要通过计算将3D图像最终显示在2D到图像上,这便是一种投影。
投影矩阵一般有两种,一种是透视投影矩阵,另一种是正交投影矩阵。
透视投影矩阵是可以越远越小,类似现在的3D游戏,而正交投影有点类似2.5D游戏,大小不会受到远近的影响。

视口(View)

视口所处理的问题就是相机位置,比如相机向左移动,其实看到的图像就向右移动了,View的变换矩阵就是用于描述这个逻辑。

模型(Model)

这个变换矩阵描述的是物体自己的变化,比如物体自己放大,翻转,平移等等。

渲染正方体

我们以渲染一个旋转的正方体为例来介绍这几个模型。

顶点数据

我们需要渲染一个正方体,有8个顶点,正方体由6个面组成,每个面是2个三角形,就是12个三角形,我们索引缓冲区就是12个三角形,36个点。

private float[] vertexArray = new float[] {
        -0.5f, -0.5f, -0.5f,
        0.5f, -0.5f, -0.5f,
        -0.5f, 0.5f, -0.5f,
        0.5f, 0.5f, -0.5f,
        -0.5f, -0.5f, 0.5f,
        0.5f, -0.5f, 0.5f,
        -0.5f, 0.5f, 0.5f,
        0.5f, 0.5f, 0.5f,
};

private short[] indexArray = new short[] {
        0,1,2,
        1,2,3,

        0,1,4,
        1,4,5,

        2,3,7,
        2,6,7,

        4,5,6,
        5,6,7,

        0,2,4,
        2,6,4,

        1,3,5,
        3,5,7
};

着色器

顶点着色器有一个统一变量mvpMatrix,这个矩阵由model矩阵,view矩阵,projection矩阵相乘得到,我们这里只是演示就将乘法放在Cpu上处理,其实这种乘法操作放在GPU上效率会更高。
片段着色器中我们使用一个统一变量表示颜色,这里我们想要把每一个面渲染成不同颜色,因为如果是同颜色又没有边界,就看不出立体的效果。
这里我们会在每渲染两个三角形后修改统一变量来修改颜色,后面渲染的时候会看到,这个方法其实效率不是最高的,因为会多次调用DrawCall,多次DrawCall必然会增加开销,不过我们这里主要是为了演示变换矩阵的用法,所以就怎么方便怎么来了。

private final String vertexShaderCode =
        "attribute vec4 vPosition;" +
                "uniform mat4 mvpMatrix;" +
                "void main() {" +
                "  gl_Position = mvpMatrix * vPosition;" +
                "}";

private final String fragmentShaderCode =
        "precision mediump float;" +
                "uniform vec4 outputColor;" +
                "void main() {" +
                "  gl_FragColor = outputColor;" +
                "}";

顶点缓冲区/索引缓冲区数据填充

填充流程和之前基本一样,新增了一个GLES30.glEnable(GL_DEPTH_TEST)调用,这个表示需要OpenGL ES根据Z轴来判断深度,处理覆盖关系。

public void onSurfaceCreated(GL10 gl, EGLConfig config) {
    // 清除颜色
    GLES30.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
    GLES30.glEnable(GL_DEPTH_TEST);
    // 创建顶点缓冲区
    int[] idBuffer = new int[2];
    GLES30.glGenBuffers(2, idBuffer, 0);
    vertexBufferId = idBuffer[0];
    elementBufferId = idBuffer[1];

    // 顶点缓冲区数据填充
    FloatBuffer vertexBuffer = ByteBuffer.allocateDirect(vertexArray.length * 4).order(ByteOrder.nativeOrder()).asFloatBuffer();
    vertexBuffer.put(vertexArray);
    vertexBuffer.position(0);

    GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, vertexBufferId);
    GLES30.glBufferData(
            GLES30.GL_ARRAY_BUFFER,
            vertexArray.length * 4,
            vertexBuffer,
            GLES30.GL_STATIC_DRAW
    );
    GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, 0);

    ShortBuffer indexBuffer = ByteBuffer.allocateDirect(indexArray.length * 2).order(ByteOrder.nativeOrder()).asShortBuffer();
    indexBuffer.put(indexArray);
    indexBuffer.position(0);
    GLES30.glBindBuffer(GLES30.GL_ELEMENT_ARRAY_BUFFER, elementBufferId);
    GLES30.glBufferData(
            GLES30.GL_ELEMENT_ARRAY_BUFFER,
            indexArray.length * 2,
            indexBuffer,
            GLES30.GL_STATIC_DRAW
    );
    GLES30.glBindBuffer(GLES30.GL_ELEMENT_ARRAY_BUFFER, 0);

    // shader
    shaderProgramId = initShaderProgram(vertexShaderCode, fragmentShaderCode);
}

初始化projection和view矩阵

setIdentityM初始化矩阵,会将矩阵所有值初始化为0,斜角线值初始化为1。
使用setLookAtM获取视口矩阵,使用perspectiveM获取透视投影矩阵,参数下面介绍了。
将两个矩阵相乘存放在vpMatrix,这里之所以没有把model矩阵也放上,是因为我们需要通过model矩阵变化实现正方体旋转,所以需要将model矩阵放在渲染的时候。

public void onSurfaceChanged(GL10 gl, int width, int height) {
    GLES30.glViewport(0, 0, width, height);
    // setLookAtM用于获取view矩阵,参数为相机位置x,y,z。模型中心位置坐标x,y,z。向上向量x,y,z
    float[] viewMatrix = new float[16];
    Matrix.setIdentityM(viewMatrix, 0);
    Matrix.setLookAtM(viewMatrix, 0, 3,3,5,0,0f,0f,0f,1f, 0f);

    float[] projectionMatrix = new float[16];
    Matrix.setIdentityM(projectionMatrix, 0);
    // 获取透视投影矩阵,参数有:视角范围45度,宽高比,透视最近距离和最远距离。
    Matrix.perspectiveM(projectionMatrix,0,45f, ((float) getWidth()) / getHeight(),0.3f,50);
    Matrix.multiplyMM(vpMatrix, 0, projectionMatrix, 0, viewMatrix, 0);
}

渲染

我们这里用radiu表示渲染角度,每次渲染加2度,如果是60HZ则一秒钟会旋转120度。
通过model矩阵旋转radius度,每次渲染两个三角形换一个颜色,以实现不同面不同颜色。
乘出来最终的矩阵就是mvp矩阵,通过统一变量传递mvp矩阵。

public void onDrawFrame(GL10 gl) {
    // 清除屏幕
    GLES30.glClear(GL_DEPTH_BUFFER_BIT);
    GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT);
    // 使能着色器程序
    GLES30.glUseProgram(shaderProgramId);

    GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, vertexBufferId);
    int positionLocation = GLES30.glGetAttribLocation(shaderProgramId, "vPosition");
    GLES30.glEnableVertexAttribArray(positionLocation);
    GLES30.glVertexAttribPointer(positionLocation, 3, GLES30.GL_FLOAT, false, 3 * 4, 0);

    radiu += 2;
    if (radiu >= 360) {
        radiu = radiu % 360;
    }
    Matrix.setIdentityM(modelMatrix, 0);
    // model矩阵旋转。后三个参数x,y,z为变量轴
    Matrix.rotateM(modelMatrix, 0, radiu, 1, 1, 0);
    Matrix.multiplyMM(mvpMatrix, 0, vpMatrix, 0, modelMatrix, 0);

    int matrixLocation = GLES30.glGetUniformLocation(shaderProgramId, "mvpMatrix");
    FloatBuffer mvpMatrixBuffer = ByteBuffer.allocateDirect(vertexArray.length * 4).order(ByteOrder.nativeOrder()).asFloatBuffer();
    mvpMatrixBuffer.put(mvpMatrix);
    mvpMatrixBuffer.position(0);
    // 将mvp矩阵通过统一变量传给着色器
    GLES30.glUniformMatrix4fv(matrixLocation, 1, false, mvpMatrixBuffer);
    int outputColorLocation = GLES30.glGetUniformLocation(shaderProgramId, "outputColor");

    GLES30.glBindBuffer(GLES30.GL_ELEMENT_ARRAY_BUFFER, elementBufferId);
    // 调用DrawCall绘制三角形
    // 每次渲染6个顶点,两个三角形,然后就换颜色。  
    for (int i = 0; i < 6; i++) {
        GLES30.glUniform4f(outputColorLocation, colorList[i][0], colorList[i][1], colorList[i][2], colorList[i][3]);
        GLES30.glDrawElements(GLES30.GL_TRIANGLES, 6, GLES30.GL_UNSIGNED_SHORT, 6 * i * 2);
    }

    // 清除配置
    GLES30.glDisableVertexAttribArray(positionLocation);
    GLES30.glBindBuffer(GLES30.GL_ELEMENT_ARRAY_BUFFER, 0);
    GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, 0);
    GLES30.glUseProgram(0);
}

效果

在这里插入图片描述

小结

我们这节通过实现一个旋转的正方体来简单的介绍了一下MVP的矩阵变换模式。这也是我们OpenGL ES最后一节了,其实OpenGL ES还有很多高级的技术,但是几乎都是跟图像数学相关的,OpenGL ES自身的核心接口能力以及介绍的差不多了。
也许在遥远的未来,我们还会写一章Vulkan的教程,在这里埋个坑。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2185964.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

电场能量磁场能量相等,注意电场能量公式也没有复数形式(和坡印廷类似)

下面是电场能量密度和磁场能量密度的公式&#xff0c;注意这可不是坡印廷定理。且电场能量密度没有复数表达式&#xff0c;即不是把E和D换成复数形式就行的。注意&#xff0c;一个矢量可以转化为复数形式&#xff0c;两个矢量做运算不能转化为两个复数形式的矢量做运算&#xf…

什么是SPI机制

什么是SPI机制 SPI机制是Java平台提供的一种强大的动态扩展机制&#xff0c;能够让程序在运行时灵活地加载和使用服务提供者的实现类。我们这里带大家简单的了解一下SPI机制是如何工作的 SPI&#xff08;Service Provider Interface&#xff0c;服务提供者接口&#xff09;机制…

JavaScript while循环语句

While语句包括一个循环条件和一段代码块&#xff0c;只要条件为真&#xff0c;就不断循环执行代码块。 while(条件){语句;} var i0;while(i<100){console.log(i);i1;} 注意&#xff1a;所有的for循环都可以改写为while循环

Spring Boot 和 MyBatis-Plus凑一块儿了,这份教程你得看

一、引言 MyBatis-Plus 是 MyBatis 的增强版&#xff0c;提供了 CRUD 接口、分页插件、性能分析插件等特性&#xff0c;简化了开发过程。本文将详细介绍如何在 Spring Boot 项目中集成 MyBatis-Plus。 支持的数据看也越来越多&#xff0c;值得去搞一下&#xff0c;写了一个小例…

《PMI-PBA认证与商业分析实战精析》第5章 需求启发与分析

第5章 需求启发与分析 本章主要内容&#xff1a; 需求启发 需求分析 模型化与优化需求 记录、确认、核实和批准需求 本章涵盖的考试重点&#xff1a; 需求启发的四项活动 需求启发的八项技术 启发提问的四种类型问题 启发原型的类型 访谈的四种分类 观察技术的四种类…

华三资源

华三华三官方网站华三公司官网新华三 - 融绘数字未来&#xff0c;共享美好生活华三华三文档中心华三文档资源文档中心-新华三集团-H3C华三华三 ICT百科华三 ICT知识百科H3C ICT知识百科-新华三集团-H3C华三华三文档高级查找高级文档搜索新华三 - 融绘数字未来&#xff0c;共享美…

已解决:Could not find artifact xxx

已解决&#xff1a;Could not find artifact xxx 文章目录 写在前面问题描述报错原因分析 解决思路解决办法1. 检查依赖声明的正确性2. 检查远程仓库配置3. 检查网络连接4. 清理本地缓存并强制更新5. 手动上传依赖到私有仓库6. 检查本地仓库是否已被损坏 总结 写在前面 在使用…

V2V迁移:vsphere至openstack

预检查 检测待迁移实例是否已安装并配置virtio相关的块设备驱动、网卡驱动&#xff0c;linux包括kernel、initramfs&#xff0c;windows包括磁盘控制器、网卡。 Linux 系统检查 Virtio 驱动 Windows 系统检查 Virtio 驱动 环境 1、下载安装ovftool 2、安装qemu-kvm&#x…

数据结构-二叉树介绍及其在Java中遍历算法实现

一、二叉树介绍 1、二叉树(Binary tree)的定义 二叉树(binary tree)是树形结构的一个重要类型,是指树中节点的度不大于2的有序树,它是一种最简单且最重要的树。二叉树的递归定义为:二叉树是一棵空树,或者是一棵由一个根节点和两棵互不相交的,分别称作根的左子树和右…

Angular基础学习(入门 --> 入坑)

目录 一、Angular 环境搭建 二、创建Angular新项目 三、数据绑定 四、ngFor循环、ngIf、ngSwitch、[ngClass]、[ngStyle]、管道、事件、双向数据绑定--MVVM 五、DOM 操作 &#xff08;ViewChild&#xff09; 六、组件通讯 七、生命周期 八、Rxjs 异步数据流 九、Http …

SuperMap iClient for MapLibreGL 根据SQL条件过滤显示动态图层

查阅发现iClient 有子图层控制类 LayerStatus 可实现&#xff1a;子图层显示参数类。此类存储了各个子图层的名称、是否可见的状态、SQL 过滤条件等参数。 API详情&#xff1a;http://support.supermap.com.cn:8090/iserver/iClient/forJavaScript/docs/maplibregl/LayerStatus…

java_整型

1.整数类型 2.整型细节 Java各整数类型有固定的范围和字段长度&#xff0c;不受具体OS【操作系统】的影响&#xff0c;以保证Java程序的可移植性 Java的整型常量&#xff08;具体值&#xff09;默认为int型&#xff0c;声明long型常量后需加"l"或"L" pu…

计算机网络:计算机网络体系结构 —— OSI 模型 与 TCP/IP 模型

文章目录 计算机网络体系结构OSI 参考模型TCP/IP 参考模型分层的必要性物理层的主要问题数据链路层的主要问题网络层的主要问题运输层的主要问题应用层的主要问题 分层思想的处理方法发送请求路由器转发接受请求发送响应接收响应 计算机网络体系结构 计算机网络体系结构是指将…

一文说完c++全部基础知识,IO流(二)

一、IO流 流、一连串连续不断的数据集合。 看下图&#xff0c;继承关系 using namespace 流类的构造函数 eg:ifstream::ifstream (const char* szFileName, int mode ios::in, int); #include <iostream> #include <fstream> using namespace std; int main()…

堆排序算法详解:原理与Python实现

&#x1f49d;&#x1f49d;&#x1f49d;欢迎莅临我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐&#xff1a;「storm…

注册安全分析报告:科研诚信查询平台无验证方式导致安全隐患

前言 由于网站注册入口容易被黑客攻击&#xff0c;存在如下安全问题&#xff1a; 1. 暴力破解密码&#xff0c;造成用户信息泄露 2. 短信盗刷的安全问题&#xff0c;影响业务及导致用户投诉 3. 带来经济损失&#xff0c;尤其是后付费客户&#xff0c;风险巨大&#xff0c;造…

《MoCo:Momentum Contrast for Unsupervised Visual Representation Learning》中文校对版

系列文章目录 文章目录 系列文章目录摘要1.简介2.相关工作3.方法3.1.作为字典查找的对比学习2、3、 五、1、2、3、 六、1、2、3、 七、1、2、3、 八、1、2、3、 摘要 我们提出了用于无监督视觉表示学习的动量对比&#xff08;MoCo&#xff09;。从对比学习[29]作为字典查找的角…

死锁的成因与解决方案

目录 死锁的概念与成因 栗子 死锁的情况 哲学家问题 如何避免死锁 必要条件 死锁的解决方案 总结 死锁的概念与成因 多个线程同时被阻塞,他们中的其中一个或者全部都在等待某个资源的释放,导致线程无限期被阻塞,程序无法停止 栗子 我和美女a出去吃饺子,吃饺子要醋和酱油…

【EXCEL数据处理】000011 案列 EXCEL带有三角形图标的单元格转换,和文本日期格式转换。

前言&#xff1a;哈喽&#xff0c;大家好&#xff0c;今天给大家分享一篇文章&#xff01;创作不易&#xff0c;如果能帮助到大家或者给大家一些灵感和启发&#xff0c;欢迎收藏关注哦 &#x1f495; 目录 【EXCEL数据处理】000011 案列 EXCEL带有三角形图标的单元格转换。使用…

Pikachu-csrf-CSRF(get)

登陆&#xff0c;修改个人信息&#xff1b;发现这是个get请求 把请求连接复制出来 ​http://192.168.3.224:8082/vul/csrf/csrfget/csrf_get_edit.php?sex1&phonenum1&add2&email3&submitsubmit就是 get请求的csrf 攻击payload