Android OpenGL ES 学习(九) – 坐标系统和实现3D效果

news2024/11/23 21:01:32

OpenGL 学习教程
Android OpenGL ES 学习(一) – 基本概念
Android OpenGL ES 学习(二) – 图形渲染管线和GLSL
Android OpenGL ES 学习(三) – 绘制平面图形
Android OpenGL ES 学习(四) – 正交投影
Android OpenGL ES 学习(五) – 渐变色
Android OpenGL ES 学习(六) – 使用 VBO、VAO 和 EBO/IBO 优化程序
Android OpenGL ES 学习(七) – 纹理
Android OpenGL ES 学习(八) –矩阵变换
Android OpenGL ES 学习(九) – 坐标系统和。实现3D效果
代码工程地址: https://github.com/LillteZheng/OpenGLDemo.git

上一章,我们已经学习了矩阵变换,实现了一些特殊的2D效果,这一章,我们来实现更酷的效果 – 3D。效果如下:
在这里插入图片描述

这一章可能会稍微难理解一点,我也是看官网看了几遍,再看懂了一些。所以,这一章说说我的理解,有不对的地方,欢迎大家指正。

前面说到,OpenGL 的坐标范围为 [-1,1] 之间,所以,要求我们在赋值或者矩阵运算的时候,都要进行转换,然后放进 [-1,1] 里面。
把一个物体的顶点坐标,转换成设备坐标,再转换成屏幕坐标,它是分步进行的,也就是类似于流水线那样子。在流水线中,物体的顶点在最终转化为屏幕坐标之前,还会被变换到多个坐标系统(Coordinate System)。
理解这个过程的好处在于,我们可以理清坐标转换的过程,并在其中加入一些效果,如 3D 效果。

总的来说,OpenGL 共有5个坐标系统。

  • 局部空间(Local Space,或者称为物体空间(Object Space))
  • 世界空间(World Space)
  • 观察空间(View Space,或者称为视觉空间(Eye Space))
  • 裁剪空间(Clip Space)
  • 屏幕空间(Screen Space)

这就是一个顶点在最终被转化为片段之前需要经历的所有不同状态。

一. 坐标概述

为了将坐标从一个坐标系变换到另一个坐标系,我们需要用到几个变换矩阵,最重要的几个分别是模型(Model)、观察(View)、投影(Projection)三个矩阵。我们的顶点坐标起始于局部空间(Local Space),在这里它称为局部坐标(Local Coordinate),它在之后会变为世界坐标(World Coordinate)观察坐标(View Coordinate),裁剪坐标(Clip Coordinate),并最后以屏幕坐标(Screen Coordinate)的形式结束。下面的这张图展示了整个流程以及各个变换过程做了什么:
在这里插入图片描述
意思就是,一个物体,从一个局域坐标到屏幕坐标,需要进过一系列的矩阵变换。

下面解释一下一些专有名词:

1.1 局部空间(Local Space)

是物体相对于自身的原点的坐标,是物体的起点。但是光有这个还不行,因为它没有参照物,放哪里的都可以,所以需要与 模型矩阵 相乘,得到世界坐标。

1.2 世界空间(World Space)

世界空间坐标是一个更大的坐标,你可以随意放在哪个位置,比如放广州,或者深圳,这一步,通常通过矩阵的平移,缩放和放大来实现,但这不能描述物体的物体位置。所以需要与 观察矩阵 相乘,得到一个以人为视角的方向。

1.3 观察空间(View Space)

观察空间也叫 camrea (摄像机)空间 或 用户空间,一个物体,需要有一个观察角度,才能直观地看到这个物体,产生一个以我们为角度的坐标,相当于把物体拉到我们面前。

1.4 裁减空间(Clip Space)

在顶点着色器运行的最后,我们希望把这些坐标都放在一个特定的范围内,超过这个范围的都被裁剪掉。在正交投影那章也讲到,我们实际的屏幕肯定不是 [-1,1] ,这个范围,所以我们需要一些特殊的投影矩阵,帮我们实现把实际物理坐标转换到 [-1,1] 中。

使用投影矩阵能将3D坐标投影(Project)到很容易映射到2D的标准化设备坐标系中。
然后就可以使用 glViewport 或其他渲染模式,把最终的坐标将会被映射到屏幕空间中,并转换成片段。

将观察坐标变换为裁剪坐标的投影矩阵可以为两种不同的形式,每种形式都定义了不同的平截头体。我们可以选择创建一个正射投影矩阵(Orthographic Projection Matrix)或一个透视投影矩阵(Perspective Projection Matrix)。

1.4.1 正交投影

正交投影,你可以理解成,在用户空间视角,太阳直射这个物体,那么这个物体的影子,跟物体的大小是相等,它的平截面,就是物体本身:
在这里插入图片描述
因此,当我们在画一些二维图形,发现写完坐标之后,长度不太一致,可以使用正交投影去修正 Android OpenGL ES 学习(四) – 正交投影。矩阵公式为:
在这里插入图片描述

1.4.2 透视投影

在实际的生活,我们看东西,离你越远的东西看起来更小。这个奇怪的效果称之为透视(Perspective)。透视的效果在我们看一条无限长的高速公路或铁路时尤其明显,正如下面图片显示的那样:
在这里插入图片描述
在我们认知中,就算铁路再远,它都是两条平行线,永远不可能相交的,但在投影中,它却是可以相交的,为了实现这个理论,引入了 w 分量,也就是齐次坐标(可点击查看齐次坐标)。
我们在坐标的基础上,处于 w 分量,就能得到一个透视的效果
在这里插入图片描述
它的视线方法为:

Matrix.perspectiveM(projectionMatrix,0,45f,aspectRatio,0.3f,100f)

看下图:
在这里插入图片描述
其中 fov 为视觉空间的观察角度,这样看起来比较真实,near 和 far 表示平截面的近平面和远平面,通常设置近距离为0.1f,而远距离设为100.0f,处于这个范围都会被渲染。

二. 进入3D

现在我们按照上面的步骤,实现3D 的效果,上面几个步骤组成一起是:

在这里插入图片描述
注意矩阵运算的顺序是相反的(记住我们需要从右往左阅读矩阵的乘法)。最后的顶点应该被赋值到顶点着色器中的gl_Position,OpenGL将会自动进行透视除法和裁剪。

顶点着色器保持不变,保留一个 matrix 即可,我们最后再把 model,view 和 projection 结合起来。

private const val VERTEX_SHADER = """#version 300 es
        uniform mat4 u_Matrix;
        layout(location = 0) in vec4 a_Position;
        layout(location = 1) in vec2 aTexture;
        out vec4 vTextColor;
        out vec2 vTexture;
        void main()
        {
            // 矩阵与向量相乘得到最终的位置
            gl_Position = u_Matrix * a_Position;
            vTexture = aTexture;
        
        }
"""

定义一个单位矩阵,并设置 model,view,projection 和最后矩阵结果 mvpMatrix:

private fun getIdentity() =  floatArrayOf(
    1f, 0f, 0f, 0f,
    0f, 1f, 0f, 0f,
    0f, 0f, 1f, 0f,
    0f, 0f, 0f, 1f
)
//获取矩阵
val modelMatrix = getIdentity()
val viewMatrix = getIdentity()
val projectionMatrix = getIdentity()
val mvpMatrix = getIdentity()

2.1 模型矩阵

首先,先使用模型矩阵,把局部空间变成世界空间:

//设置 M
Matrix.rotateM(modelMatrix,0,-55f,1f,0f,0f)

这里向x轴旋转 -55 °

2.2 视图矩阵

接着,再使用视图矩阵,将世界空间,转成视图空间:

//设置 V
Matrix.translateM(viewMatrix,0,0f,0f,-3f)

OpenGL 满足右手坐标系,所以如果要把物体往我们这边靠,就是负的,所以这里向 z 轴移动了 3f 。

2.3 投影矩阵

这里使用透视矩阵,用来模拟除以 w 分量,实现躺平效果:

 //设置 P
 Matrix.perspectiveM(projectionMatrix,0,45f,aspectRatio,0.1f,100f)

最后再把他们组合起来:

 //组合成 mvp,先 v x m
 Matrix.multiplyMM(mvpMatrix,0, viewMatrix,0, modelMatrix,0)
 //然后是 p x v x m
 Matrix.multiplyMM(mvpMatrix,0, projectionMatrix,0, mvpMatrix,0)

 val u_Matrix = getUniform("u_Matrix")
 GLES30.glUniformMatrix4fv(u_Matrix,1,false, mvpMatrix,0)

最后传入顶点着色器进行渲染。

我们的顶点坐标已经使用模型、观察和投影矩阵进行变换了,最终的物体应该会:

  • 稍微向后倾斜至地板方向。
  • 离我们有一些距离。
  • 有透视效果(顶点越远,变得越小)

在这里插入图片描述

三. 3D 立方体

终于到了这个环节,为了实现一个立方体,我们需要准备36个点,6个面 x 每个面有2个三角形组成 x 每个三角形有3个顶点),这36个点可以从 这里 获取。

然后我们需要改变 GlSurface 的渲染模式,改成持续绘制:

 renderMode = GLSurfaceView.RENDERMODE_CONTINUOUSLY

然后,为了不重新去计算 EBO 的三角形排列,所以,我们使用

GLES30.glDrawArrays(GLES30.GL_TRIANGLES, 0, 36)

来绘制,同理,你需要去掉 EBO 加载数据的赋值:

/*        //创建 ebo
        GLES30.glGenBuffers(1, ebo, 0)
        //绑定 ebo 到上下文
        GLES30.glBindBuffer(GLES30.GL_ELEMENT_ARRAY_BUFFER, ebo[0])
        //EBO 数值
        GLES30.glBufferData(
            GLES30.GL_ELEMENT_ARRAY_BUFFER,
            indexData.capacity() * 4,
            indexData,
            GLES30.GL_STATIC_DRAW
        )*/

为了更好的展示,我们也让渲染角度,不断的累加,这样效果更明显,完整的代码如下:

        GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT)

        GLES30.glBindVertexArray(vao[0])
        Matrix.setIdentityM(modelMatrix, 0)
        Matrix.setIdentityM(viewMatrix, 0)
        Matrix.setIdentityM(projectionMatrix, 0)
        Matrix.setIdentityM(mvpMatrix, 0)

        angle += 1
        angle %= 360
        //设置 M
        Matrix.rotateM(
            modelMatrix, 0,
            angle,
            0.5f,
            1.0f,
            0f
        )

        //设置 V
        Matrix.translateM(
            viewMatrix,
            0,
            0f,
            0f,
            -4f
        )

        //设置 P
        Matrix.perspectiveM(projectionMatrix, 0, 45f, aspectRatio, 0.3f, 100f)

        //组合成 mvp,先 v x m
        Matrix.multiplyMM(mvpMatrix, 0, viewMatrix, 0, modelMatrix, 0)
        //然后是 p x v x m
        Matrix.multiplyMM(mvpMatrix, 0, projectionMatrix, 0, mvpMatrix, 0)

        val u_Matrix = getUniform("u_Matrix")
        GLES30.glUniformMatrix4fv(u_Matrix, 1, false, mvpMatrix, 0)
        //useVaoVboAndEbo
        texture?.apply {
            GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, id)
        }

        //GLES30.glDrawElements(GLES30.GL_TRIANGLE_STRIP, 6, GLES30.GL_UNSIGNED_INT, 0)
        GLES30.glDrawArrays(GLES30.GL_TRIANGLES, 0, 36)

在这里插入图片描述
咦,看起来有点奇怪,怎么感觉所有面都有点穿插了,立方体的某些本应被遮挡住的面被绘制在了这个立方体其他面之上。
之所以会这样,是因为 OpenGL 绘制时,会覆盖之前的像素,所以有些三角形就覆盖在部分三角形上了。

处理这个也比较方便,就是开始 Z 缓冲。

3.1 Z缓冲

Z缓冲也叫深度缓冲(Depth Buffer),看官网怎么解释:

GLFW会自动为你生成这样一个缓冲(就像它也有一个颜色缓冲来存储输出图像的颜色)。深度值存储在每个片段里面(作为片段的z值),当片段想要输出它的颜色时,OpenGL会将它的深度值和z缓冲进行比较,如果当前的片段在其它片段之后,它将会被丢弃,否则将会覆盖。这个过程称为深度测试(Depth Testing),它是由OpenGL自动完成的。

什么意思呢,比如第一个已经绘制了一个矩形,OpenGL 也会有一个颜色缓冲,但第二个在画的时候,发现前面已经有缓存了,OK,那我就画没被缓存或者说遮挡的部分。

默认它是关闭,所以需要打开:

//开启z轴缓冲,深度测试
  GLES30.glEnable(GLES30.GL_DEPTH_TEST)

因为我们使用了深度测试,我们也想要在每次渲染迭代之前清除深度缓冲(否则前一帧的深度信息仍然保存在缓冲中)。就像清除颜色缓冲一样,我们可以通过在glClear函数中指定DEPTH_BUFFER_BIT位来清除深度缓冲:

GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT or GLES30.GL_DEPTH_BUFFER_BIT)

效果:
在这里插入图片描述

3.2 渲染多个矩形

现在我们想画更多的立方体,每个立方体看起来都是一样的,区别在于它们在世界的位置及旋转角度不同。
所以,我们只需要改变的它模型矩阵就可以了,但我们又像让每个立方体有大小之分,所以也改变它的视图矩阵,这样跟清晰点。
由于屏幕不大,我们设置4个立方体,它的位置如下:

var mulPosition = floatArrayOf(

    0.0f, 0.0f, 0.0f,
    1.2f, 1.2f, -1.0f,
    -1.5f, -1.3f, -2.5f,
    -1.3f, 1.3f, -1.5f
)

然后 for 循环中,去把每个分量的值拿出来即可。

        for (i in 0..boxCount) {
            Matrix.setIdentityM(modelMatrix, 0)
            Matrix.setIdentityM(viewMatrix, 0)
            Matrix.setIdentityM(projectionMatrix, 0)
            Matrix.setIdentityM(mvpMatrix, 0)

            angle += 1
            angle %= 360
            //设置 M
            Matrix.rotateM(
                modelMatrix, 0,
                angle,
                mulPosition[i * 3] + 0.5f,
                mulPosition[i * 3 + 1] + 1.0f,
                mulPosition[i * 3 + 2]
            )

            //设置 V
            Matrix.translateM(
                viewMatrix,
                0,
                mulPosition[i * 3],
                mulPosition[i * 3 + 1],
                mulPosition[i * 3 + 2] - 4f - boxCount
            )

            //设置 P
            Matrix.perspectiveM(projectionMatrix, 0, 45f, aspectRatio, 0.3f, 100f)

            //组合成 mvp,先 v x m
            Matrix.multiplyMM(mvpMatrix, 0, viewMatrix, 0, modelMatrix, 0)
            //然后是 p x v x m
            Matrix.multiplyMM(mvpMatrix, 0, projectionMatrix, 0, mvpMatrix, 0)

            val u_Matrix = getUniform("u_Matrix")
            GLES30.glUniformMatrix4fv(u_Matrix, 1, false, mvpMatrix, 0)
            //useVaoVboAndEbo
            texture?.apply {
                GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, id)
            }

            //GLES30.glDrawElements(GLES30.GL_TRIANGLE_STRIP, 6, GLES30.GL_UNSIGNED_INT, 0)
            GLES30.glDrawArrays(GLES30.GL_TRIANGLES, 0, 36)
        }

效果:
在这里插入图片描述

参考:
https://learnopengl-cn.github.io/01%20Getting%20started/08%20Coordinate%20Systems/

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

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

相关文章

高通Ride软件开发包使用指南(7)

高通Ride软件开发包使用指南(7)6.5 构建 x86 Ubuntu SDK6.6端到端可视化6.7 x86 Ubuntu上的功能验证6.7.1简单比特率验证6.7.2在x86笔记本上用 8xCams HEVC格式 录制 FPS6.8记录仪6.5 构建 x86 Ubuntu SDK 构建 x86 ubuntu ~/src/qride/stack-sdk$ ./ex…

十个精妙绝伦的SQL语句,说尽SQL精华

目录引子十大SQL1. 统计班级总分前十名2. 删除重复记录, 且保留一条3. 最大连续登陆天数的问题4. 计算除去部门最高工资,和最低工资的平均工资5. 计算占比和同比增长6. 算成绩7.算昨天每个城市top 10消费金额的用户,输出city_id,city_name,uid, 消费总金…

C语言刷题(3)

🐒博客名:平凡的小苏 📚学习格言:别人可以拷贝我的模式,但不能拷贝我不断往前的激情 目录 计算体重指数 计算三角形周长和面积 KiKi和酸奶 网购 变种水仙花 计算体重指数 描述 问题:计算BMI指数&#xff0…

安全可信 | 强墙出击!天翼云Web应用防火墙(原生版)硬核亮相!

12月9日,由中国信息通信研究院主办的“墙墙联合——云上防火墙技术沙龙”在线上顺利举行,天翼云科技有限公司研发专家吴雷分享了新标准、新需求下云Web应用防火墙(云WAF)的发展方向,并介绍了天翼云Web应用防火墙&#…

如何从 OVF 或 OVA 文件中部署虚拟机

从 OVF 或 OVA 文件中部署虚拟机 使用新建虚拟机向导从 OV F和 VMDK 文件或 OVA 文件部署虚拟机。由于 Web 浏览器的限制,OVA 部署仅限于1GB以下的文件。要部署大于1GB的 OVA 文件,请使用 tar 提取 OVA 文件并分别提供OVF和VMDK文件。 一、右键单击导航器中的‘‘虚拟机“,…

Android:远程调试WebView

目录 前言 配置WebView 手机设置 远程调试 前言 APP中使用WebView展示网页的结果可能不太理想,那么我们想调试的话就没有在浏览器当中调试方便,不过谷歌浏览器给我们提供了能远程调试的方案。 官方文档:https://developer.chrome.com…

Blazor也可以用MarkDown!Vditor-Blazor基础使用教程以及部分源码解析!Ant-Designer-Blazor

前言 Ant-Designer-Blazor组件库是一款非常好用的Blazor的UI框架库,使用很少的精力就可以搭建出一个漂亮的UI界面了。 但是Ant-Designer-Blazor中尚缺少对MarkDown组件的支持,所以只能下载Nuget包里面的一个Vditor(也是Ant-Designer的&#…

设计师常用网站,建议收藏

这几个设计师常用网站,你不知道就落后了 设计党赶紧收藏!1、菜鸟图库 https://www.sucai999.com/?vNTYwNDUx 免费设计素材。 站内平面海报、UI设计、电商淘宝、免抠、高清图片、样机模板等素材非常齐全。还有在线抠图、CDR版本转换功能,能有…

坦克大战③

1.防止敌人坦克重叠运动 八种情况 这辆敌方坦克和任何其它敌方坦克都不发生碰撞时且不超边界时才可以移动 2.记录玩家成绩 绘制版面信息 paint()方法中如果没有super.paint(g),那么绘制的敌方坦克数会出现重叠情况 调用方法 在Recorder类中定义一个方法…

Java 性能诊断工具简介-EJ Technologies JProfiler 12.0.5 中文激活版

目录 JProfiler 这篇文章讲解的比较详细: 可以辅助命令,应用jar的启动命令: 同时在开发工具里面添加一个JProfiler插件 JProfiler 这篇文章讲解的比较详细: JVM性能分析工具 Jprofiler - 百里浅暮 - 博客园 我在这里记录下。…

MATLB|电动汽车充放电的最优调度

目录 一、概述 二、电动汽车 2.1 电动汽车的类型 2.2电动汽车行程开始时间 2.3 电动汽车行驶里程 三、Matlab实现 3.1 代码前的准备 3.2 运行结果 四、往期回顾 五、Matlab代码实现 一、概述 电动汽车能够良好发展离不开精确的电动汽车充电负荷预测,目前,大多数充电负…

2022全年度烘干机十大热门品牌销量榜单

在“宅经济”时代,人们对大家电的需求持续高速增长。在当前的大环境下,人们的健康意识在不断提高,拥有除菌防菌功能的烘干机也更受欢迎。因此,烘干机作为快速进入普及阶段的家电新品,市场增长空间非常大。 根据鲸参谋数…

RCE绕过靶场练习

目录 CTF-01 CTF-02 CTF-03 CTF-04 CTF-05 CTF-01 测试回显 Array ([0] > PING 127.0.0.1 (127.0.0.1): 56 data bytes[1] > 64 bytes from 127.0.0.1: seq0 ttl42 time0.028 ms[2] > 64 bytes from 127.0.0.1: seq1 ttl42 time0.059 ms[3] > 64 bytes from …

Javascript 面向对象的缺陷,父类能调用被子类重写后的方法

问题背景 前些天做项目练手时,遇到一个需要写类的场景,各个类之间的交互我打算用事件的方式进行,就自然地在父类继承了EventEmitter类。然后在父类对一个具体事件注册了一个默认监听,子类通过注册自己专有的监听细化逻辑。代码逻…

win7、win10关闭驱动签名,进入驱动测试模式,以及常见初级问题的解决

win7关闭驱动签名,进入驱动测试模式win7、win10关闭驱动签名、进入驱动测试模式DebugView工具运行提示"Dbgv.sys: 拒绝访问"驱动项目配置属性常用设置驱动中KdPrint打印UNICODE_STRING字符串常用方法没使用的变量在编译时报警告:未引用的形参错…

文字转语音真人发声软件哪个好?这些实用软件快来收好

平时大家结束了一天的工作,会不会在空闲时间好好放松一下呢?如果是坐公交车或者地铁的小伙伴,想要在下班途中看一些电子书籍或者新闻,却因为下班高峰期人潮拥挤,导致无法腾出手来阅读,这时候你们会怎么解决…

红队基础知识

文章目录红队前置准备网络攻击链工程和操作攻击性思维渗透测试漏洞研究软件开发基础设施网络和系统逆向工程社会工程学物理安全威胁情报安全事件的检测和响应技术写作培训与汇报总结红队前置准备 红队通常指在对抗情况下需要仿真、模拟或以其他方式扮演某个、某组入侵者或理论…

【Docker】(一)基本概念与安装使用

1.概述 最近学习了Docker的使用,想通过一个系列的笔记来记录学习的过程与收获,并为以后的生产工作提供指导。 我一直认为学习一门技术时,需要先了解这门技术的基本概念,了解它能解决的问题,这样才能定位明确的学习目标…

frp内网穿透https

在公网服务器搭建frps(service),在内网本地机子搭建frpc(client),流量通过访问公网ip,经过frps服务端转发到fprc客户端,fprc再转发到本地web应用。 官方下载地址​ https://github.com/fatedier/frp/releases 官方文档地址https…

智能门锁“激战正酣”

近年来,智能化已经成为了高频词,越来越多的行业都在朝着智能化方向发展,家居行业也不例外。受技术升级、居民收入水平提高等多重因素影响,整个智能家居行业呈现出了蓬勃发展态势。据亿欧智库预测,2025年中国智能家居市…