【OpenGL ES】透视变换原理
1 前言
MVP矩阵变换 中主要介绍了模型变换(平移、旋转、对称、缩放)和观测变换基本原理,本文将介绍透视变换的基本原理。
如下图,近平面和远平面间棱台称为视锥体,表示可见区域范围,视锥体以外的空间将被裁剪丢弃,视锥体内的模型通过透视变换投影到近平面上,近平面上得到的平面图形就是屏幕上要显示的模型的图形。

近平面的高度为 2(区间为 [-1, 1],为方便计算,已归一化),宽度也为 2。当相机位置和模型位置已固定时,由于近平面的宽高已固定,因此可以通过平移近平面的位置控制模型显示的缩放大小。
2 透视变换原理
如下图,在视图坐标系下,已知近平面和远平面距离原点的距离分别为 N 和 F,近平面高度和宽度都为 2,假设视锥体内任意一点的坐标为 (x0, y0, z0),经透视投影后的坐标为 (x1, y1, z1)。

由于近平面的宽高都是 2,即宽高比为 1: 1,但是 ViewPort 一般不是 1: 1,为了避免投影成像变形,通常将投影后的 x 坐标除以 ViewPort 的宽高比(假设为 r),因此有如下公式:

由于 z0 是个变量,导致 (x1, y1) 与 (x0, y0) 之间不是线性关系。为了方便使用线性变换描述透视投影,将透视投影分为 2 个步骤:透视变换、透视分割。
1)透视变换

2)透视分割

其中,x0, y0 是模型坐标,xp, yp 是透视变换后的坐标,x1, y1 是透视投影(或透视分割)后的坐标。
透视投影划分 2 步后,透视变换可以使用如下方式表示:

经透视投影后,x 轴和 y 轴的坐标都被归一化到 [-1, 1] 区间内,z 轴坐标同样也需要归一化到 [-1, 1] 区间内。本来z1 与 z0 之间应该是线性关系,但是考虑到 (xp, yp, zp) 与 (x0, y0, z0) 之间需要使用矩阵表示,即 zp 与 z0 之间存在线性关系,因此,有如下函数关系:

进一步得到 z1 与 z0 的函数关系如下:

使用待定系数法将 (-N, -1), (-F, 1) 代入求得 k 和 b 的值如下:

因此,zp 与 z0 之间的函数关系如下:

经透视变换后,接着需要进行透视分割,即将 (xp, yp, zp) 除以 (-z0),为保证透视变换后 z0 的信息不被丢失,将 -z0 值保存到第四维空间(即 w 维)中。因此,透视变换可以进一步使用如下方式表示:

frustumM 方法源码如下,m 是透视变换返回的矩阵;offset 为索引偏移,表示 m 中 offset 之前的数不参与变换,通常取 0;(left, right, bottom, top) 为投影平面的边框,通常取 (-ratio, ratio, -1, 1)(ratio为 ViewPort 宽高比);near 为近平面到相机的距离;far 为远平面到相机的距离。
public static void frustumM(float[] m, int offset,
float left, float right, float bottom, float top,
float near, float far) {
... //输入合法性校验
final float r_width = 1.0f / (right - left);
final float r_height = 1.0f / (top - bottom);
final float r_depth = 1.0f / (near - far);
final float x = 2.0f * (near * r_width);
final float y = 2.0f * (near * r_height);
final float A = (right + left) * r_width;
final float B = (top + bottom) * r_height;
final float C = (far + near) * r_depth;
final float D = 2.0f * (far * near * r_depth);
m[offset + 0] = x;
m[offset + 5] = y;
m[offset + 8] = A;
m[offset + 9] = B;
m[offset + 10] = C;
m[offset + 14] = D;
m[offset + 11] = -1.0f;
m[offset + 1] = 0.0f;
m[offset + 2] = 0.0f;
m[offset + 3] = 0.0f;
m[offset + 4] = 0.0f;
m[offset + 6] = 0.0f;
m[offset + 7] = 0.0f;
m[offset + 12] = 0.0f;
m[offset + 13] = 0.0f;
m[offset + 15] = 0.0f;
}
透视变换除了 frustumM 方法,还可以使用 perspectiveM 方法,公式如下:

其中,r 为 ViewPort 宽高比,θ 为视野角的一半。与 frustumM 方法相比,perspectiveM 方法仅对 z 进行了归一化,未对 x, y 进行归一化。

perspectiveM 方法源码如下,m 是透视变换返回的矩阵;offset 为索引偏移,表示 m 中 offset 之前的数不参与变换,通常取 0;fovy 为视野角度(角度制);aspect 为 ViewPort 宽高比;zNear 为近平面到相机的距离;zFar 为远平面到相机的距离。
public static void perspectiveM(float[] m, int offset,
float fovy, float aspect, float zNear, float zFar) {
float f = 1.0f / (float) Math.tan(fovy * (Math.PI / 360.0));
float rangeReciprocal = 1.0f / (zNear - zFar);
m[offset + 0] = f / aspect;
m[offset + 1] = 0.0f;
m[offset + 2] = 0.0f;
m[offset + 3] = 0.0f;
m[offset + 4] = 0.0f;
m[offset + 5] = f;
m[offset + 6] = 0.0f;
m[offset + 7] = 0.0f;
m[offset + 8] = 0.0f;
m[offset + 9] = 0.0f;
m[offset + 10] = (zFar + zNear) * rangeReciprocal;
m[offset + 11] = -1.0f;
m[offset + 12] = 0.0f;
m[offset + 13] = 0.0f;
m[offset + 14] = 2.0f * zFar * zNear * rangeReciprocal;
m[offset + 15] = 0.0f;
}
声明:本文转自【OpenGL ES】透视变换原理
【OpenGL ES】透视变换原理的更多相关文章
- OpenGL ES
前言 OpenGL ES是Khronos Group创建的一系列API中的一种(官方组织是:http://www.khronos.org/).在桌面计算机上有两套标准的 3DAPI:Direct3D和 ...
- Android OpenGL ES 离屏渲染(offscreen render)
通常在Android上使用OpenGL ES,都是希望把渲染后的结果显示在屏幕上,例如图片处理.模型显示等.这种情况下,只需要使用Android API中提供的GLSurfaceView类和Rende ...
- OpenGL ES无法获取贴图数据原因
最近在做一个项目,要从贴图中获取图像数据,查了很多资料,也琢磨很久,获取到的数据都是0.终于在一次偶然的机会,发现了端倪,成功了. 不得不说这"一分灵感"真的很重要 以下是在获取贴 ...
- 在 OpenGL ES 2.0 上实现视差贴图(Parallax Mapping)
在 OpenGL ES 2.0 上实现视差贴图(Parallax Mapping) 视差贴图 最近一直在研究如何在我的 iPad 2(只支持 OpenGL ES 2.0, 不支持 3.0) 上实现 视 ...
- Android OpenGL ES(二)----平滑着色
直线或者三角形上的每个片段混合后的颜色可以用一个varying生成.我们不仅能混合颜色,还可以给varying传递任何值,OpenGL会选择属于那条直线的两个值,或者属于那个三角形的三个值,并平滑地在 ...
- Android OpenGL ES(十四)gl10方法解析
Android 支持 OpenGL 列表 1.GL 2.GL 10 3.GL 10 EXT 4.GL 11 5.GL 11 EXT 6.GL 11 ExtensionPack 我们将使用 GL10 这 ...
- Android OpenGL ES 开发(九): OpenGL ES 纹理贴图
一.概念 一般说来,纹理是表示物体表面的一幅或几幅二维图形,也称纹理贴图(texture).当把纹理按照特定的方式映射到物体表面上的时候,能使物体看上去更加真实.当前流行的图形系统中,纹理绘制已经成为 ...
- OpenGL ES平移矩阵和旋转矩阵的左乘与右乘效果
OpenGL ES平移矩阵和旋转矩阵的左乘与右乘 在OpenGL .OpenGL ES中矩阵起着举足轻重的作用,而矩阵之间的左乘与右乘在效果上是不同的. 一.先平移后旋转 场景效果:人绕树旋转. 原理 ...
- OpenGL ES学习资料总结
从今年春节后开始学习OpenGL ES,发现网上资料很有限,而且良莠不齐,所以整理了一下我学习时用到的资料和一些心得. 1. OpenGL ES1.x参考资料 把NEHE的教程移植到了Android上 ...
- 基于Cocos2d-x学习OpenGL ES 2.0之多纹理
没想到原文出了那么多错别字,实在对不起观众了.介绍opengl es 2.0的不多.相信介绍基于Cocos2d-x学习OpenGL ES 2.0之多纹理的,我是独此一家吧.~~ 子龙山人出了一个系列: ...
随机推荐
- JMS微服务开发示例(六)安全退出进程
默认情况下,如果在linux,需要关闭微服务进程,请务必使用 kill -15 进程id 命令,其他命令可能会直接关闭进程,造成数据丢失. 例如,有个后台任务,执行了一半,这时候进程突然关闭了,会形成 ...
- 02-MySQL基本操作
SQL 的一些简单语法规则 结束符 SQL 指令需要语句结束符,默认是英文分号;. 当然,还有另外两个结束符: \g 与英文分号;等效. \G:将查到的结构旋转90度变成纵向. 反引号`` SQL语句 ...
- AspNetCore在docker里访问Oracle数据库的坑:ORA-01882: timezone region not found
哦吼 之前刚说了尝试了使用docker来部署AspNetCore应用,结果这才刚上班就遇到问题了= =- 我这项目用的数据库是Oracle,之前直接运行没啥问题,但放在docker里运行就报了这个错误 ...
- [转帖]什么是内存屏障? Why Memory Barriers ?
要了解如何使用memory barrier,最好的方法是明白它为什么存在.CPU硬件设计为了提高指令的执行速度,增设了两个缓冲区(store buffer, invalidate queue).这个两 ...
- [转帖]tidb的分区表
https://docs.pingcap.com/zh/tidb/v6.5/partitioned-table 分区类型 本节介绍 TiDB 中的分区类型.当前支持的类型包括 Range 分区.Ran ...
- [转帖]docker-compose完全清除
https://www.cnblogs.com/gelandesprung/p/12112420.html#:~:text=docker-compose%E5%AE%8C%E5%85%A8%E6%B8 ...
- [转帖]一文读懂容器存储接口 CSI
https://zhuanlan.zhihu.com/p/470093908 作者 | 惠志来源 | 阿里巴巴云原生公众号 导读:在<一文读懂 K8s 持久化存储流程>一文我们重点介绍了 ...
- 不同linux发行版 FIO测试结果总结
不同linux发行版 FIO测试结果总结 背景 机器来源 配置: 2路28核心Golden 6330 2.0Ghz 512G内存 硬盘 24块 960G SSD (22块 Raid5 + 2块 hot ...
- 【转帖】nginx变量使用方法详解-6
https://www.diewufeiyang.com/post/580.html Nginx 内建变量用在"子请求"的上下文中时,其行为也会变得有些微妙. 前面在 (三) 中我 ...
- [转帖]linux--Segfault详解
linux--Segfault详解 1 简介 1.1 段错误的定义 1.2 痛点 2 知识点 2.1 报错内容 2.2 error number 3 排除步骤(借助汇编) 3.1 日志确定错误类型 3 ...