惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Scott Helme
Scott Helme
N
Netflix TechBlog - Medium
AI
AI
Security Latest
Security Latest
GbyAI
GbyAI
P
Proofpoint News Feed
Y
Y Combinator Blog
A
Arctic Wolf
G
Google Developers Blog
U
Unit 42
爱范儿
爱范儿
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
V
Vulnerabilities – Threatpost
Know Your Adversary
Know Your Adversary
Cisco Talos Blog
Cisco Talos Blog
T
Tor Project blog
C
CXSECURITY Database RSS Feed - CXSecurity.com
T
Threatpost
L
Lohrmann on Cybersecurity
C
CERT Recently Published Vulnerability Notes
C
Check Point Blog
B
Blog RSS Feed
The GitHub Blog
The GitHub Blog
Microsoft Azure Blog
Microsoft Azure Blog
博客园 - 【当耐特】
博客园 - Franky
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
C
Cisco Blogs
云风的 BLOG
云风的 BLOG
NISL@THU
NISL@THU
D
Darknet – Hacking Tools, Hacker News & Cyber Security
Microsoft Security Blog
Microsoft Security Blog
T
The Blog of Author Tim Ferriss
阮一峰的网络日志
阮一峰的网络日志
Latest news
Latest news
L
LINUX DO - 最新话题
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
美团技术团队
WordPress大学
WordPress大学
L
LangChain Blog
Stack Overflow Blog
Stack Overflow Blog
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
酷 壳 – CoolShell
酷 壳 – CoolShell
大猫的无限游戏
大猫的无限游戏
The Hacker News
The Hacker News
Simon Willison's Weblog
Simon Willison's Weblog
V
V2EX
Project Zero
Project Zero
博客园_首页

音视频开发进阶

音视频教程-第三节 音视频教程-第二节 真的,AI 可能就是新时代的信息差 充值 Cursor 之后,工作有了哪些变化?🤔 个人'蒸馏'大模型能做哪些有意思的事情 DeepSeek 大模型在 Mac 上的部署和运行 音视频教程-第一节 【WebRTC 专栏】-- Android 开发集成 WebRTC 库的几种方式 【WebRTC 专栏】-- 在 Mac M1 等系列芯片编译和开发 WebRTC-Android 库 Meta Llama3 大模型在 Mac 上的部署和运行 iOS VideoToolBox 解码 HEVC Open-GOP 视频的问题排查 Flutter 状态管理之 InheritedWidget 使用和分析 用 ChatGPT 回答技术问题怎么样 ? 音视频开发系统入门大致路线 UE 4.27 添加自定义 ShadingModel 用 UE4 虚幻引擎做个捏脸小功能~~ UE4 材质练习 之 凹凸贴图偏移的使用 UE4 材质练习系列基础 OpenGL上下文创建以及共享机制 007 | 播放器系列专栏-解析 MP4 文件读取信息 006 | 播放器系列专栏-在 Mac 上查看 MP4 格式信息 干货 | 快速抽取缩略图是怎么练成的? 关于直播、WebRTC、FFmpeg 的那些事 005 | 播放器系列专栏-在 Windows 上查看 MP4 格式信息 将音视频中的花屏、绿屏、黑屏问题一网打尽 关于音视频里面的解码帧率和渲染帧率 004 | 播放器系列专栏-认识MP4视频(下) 003 | 播放器系列专栏-认识MP4视频(上) 入门或者转行音视频,应该要怎么做? H264视频文件如何缩放分辨率 002 | 播放器系列专栏-FFmpeg依赖库的配置 001 | 播放器系列专栏-关于播放器项目的一个小实践 Seek策略以及在有B帧情况下的处理 目前流媒体开发工程师工作内容主要是什么? 一个音视频领域专业问答的小圈子 干货收藏 || Vulkan Game Engine 视频教程 音视频春节假期内卷指南(实操) Vulkan 在 FFmpeg 中的支持 Windows 下 FFmpeg 和 LibX264 的编译和配置 Metal 开发 | 使用 C++ 进行接口调用 音视频开发工作经验分享 || 视频版 FFmpeg 调用 MediaCodec 硬解码到 Surface 上 代码吸猫 | 用 OpenGL 图像渲染的养猫计划 百倍变速--解码到底能不能丢 非参考帧 ?FFmpeg 有话说!!! 老生常谈-FFmpeg 的编译问题轻松搞定 FFmpeg 调用 Android MediaCodec 进行硬解码(附源码) 【WebRTC 专栏】--创建相机预览 Unity Shader 光照基础之 Half Lambert 光照模型 Unity Shader 光照基础之Lambert光照模型 Unity Shader 光照基础内容 Unity Shader 显示一张图片纹理 UnityShader 的基本概念 Unity 物体的基本操作 C++ 模板系列小结07-尾置返回类型 C++ 模板系列小结06-可变参数模板特性 C++ 中的多线程的使用和线程池建设 C++ 模板系列小结05-模板类型作为模板参数 C++ 模板系列小结04-类模板中的成员模板 C++ 模板系列小结03-在模板中指定变量类型 C++ 模板系列小结02-非类型模板参数 C++ 模板系列小结01-函数模板和类模板 从零打造渲染引擎系列01-什么是渲染引擎 iOS开发 - 在 Swift 中去调用 C/C++ 代码 2021 技术新番 - 从零打造渲染引擎系列 iOS 音视频开发的一些基础准备工作 音视频交流群又来啦~~~ 【WebRTC 专栏】WebRTC & Android 开发学习环境搭建~ 【喜大普奔】域名终于备案通过啦 Shader 优化 | OpenGL 绘制网格效果 【音视频连载-011】第二季 FFmpeg 一层一层获取文件信息 KodeLife | Shader 实时编辑预览的强大工具使用实践 推荐几个堪称教科书级别的 Android 音视频入门项目 【音视频连载-010】第二季 FFmpeg 日志打印 【音视频连载-008】基础学习篇-SDL 播放 PCM 音频文件(下) 【音视频连载-007】基础学习篇-SDL 播放 PCM 音频文件(上) 【音视频连载-006】基础学习篇-SDL 播放 YUV 视频文件 【音视频连载-005】基础学习篇-SDL 加载 YUV 文件并显示 【音视频连载-004】基础学习篇-SDL 加载图片并显示 【音视频连载-003】基础学习篇-SDL 消息循环和事件响应 【音视频连载-002】基础学习篇-SDL 创建窗口并显示颜色 【音视频连载-001】基础学习篇- SDL 介绍以及工程配置 LearnOpenGL 源码在 MAC 上的编译与调试 2019 年终总结与回顾 Android NDK 开发的免费技术视频来啦~~ OpenGL 实现视频编辑中的转场效果 OpenGL 实践之贝塞尔曲线绘制 图像库 libjpeg-turbo 编译与实践 图像库 libpng 编译与实践 rust 开发编译 Android 动态库实践 Android NDK 开发 —— 从 Assets 文件夹加载图片并上传纹理 简单易用的图像解码库介绍 —— stb_image 博客图床迁移记 进击的 Vulkan 移动开发之 SwapChain 进击的 Vulkan 移动开发之 Command Buffer 进击的 Vulkan 移动开发之 Instance & Device & Queue 进击的 Vulkan 移动开发(一)之今生前世 Java 显式锁 Lock 与条件队列 C++ 标准容器库小结 一文读懂 YUV 的采样与格式 《OpenGL ES 3.x 游戏开发》碰撞检测之 AABB 包围盒
OpenGL 学习系列---坐标系统
音视频开发进阶 · 2018-01-23 · via 音视频开发进阶

一个专注音视频领域的小圈子

避免图片丢失,建议阅读微信原文:

https://mp.weixin.qq.com/s/0HZgyZra90LQLFAq6dQJ9A 在前面绘制基本图形中,遇到了很明显的问题,圆形不像圆形,正多边形不像正多边形?就像下面图形一样: 好好的正五边形却东倒西歪的,这就是因为我们前面的绘制都是把它当成 二维 的绘制,而在 OpenGL 中却是绘制 三维的。在二维和三维之间还有个转换,而之前为了方便学习则忽略了这个转换,现在就要开始理解它了 —— 坐标系统!!

坐标系统

在立体几何的坐标系里面定义一个点的位置,需要 x、y、z 三个坐标轴的值,而在 OpenGL 中绘制 3D 物体也是需要的。

在绘制基本形状时,只是定义了 x、y 轴的坐标,这样 z 轴的坐标就默认为 0 了。

OpenGL 将定义好的坐标轴的值转换为实际绘制的坐标,需要经过五个坐标系统的转换。

如下图所示:

这里面涉及到了五个坐标空间和三个转换矩阵:

空间:

  • 局部空间(Local Space)
  • 世界空间(World Space)
  • 观察空间(View Space)
  • 裁剪空间(Clip Space)
  • 屏幕空间(Screen Space)

矩阵:

  • 模型矩阵(Model Matrix)
  • 视图矩阵(View Matrix)
  • 投影矩阵(Projection Matrix)

根据流程图,每个坐标空间的转换都需要一个转换矩阵来完成。

最后裁剪空间到屏幕空间的转换,就是将经过这一系列转换后的坐标映射到屏幕的坐标上,这一过程就不需要转换矩阵了。

在进入不同的坐标空间之前,需要先了解 OpenGL 的坐标系:

OpenGL 是一个右手坐标系,正 X 轴在右手边,正 Y 轴朝上,正 Z 轴穿过屏幕朝向你。

与之相对的就是左手坐标系,其正 Z 轴穿过屏幕朝向里面了。

局部空间

局部空间坐标是 OpenGL 绘制坐标的起点,接下来所有的转换操作都是在局部空间坐标基础上进行的。

局部空间坐标就是我们自己定义的起始坐标点,是相对于原点 $ (0,0,0) $ 的。

此时所在的空间就是局部空间,也就是说我们在局部空间里面定义物体的起始坐标。

世界空间

我们定义每一个坐标点都是在局部空间,相对于 $ (0,0,0) $ 的。这样一来,当多个物体同时绘制时,就会扎堆了。

而世界空间就是当所有物体一起绘制、仍然相对于原点的、更大的一个坐标系。

局部空间和世界空间有点相像,可以在局部空间定义坐标系时就考虑到世界坐标系,避免多个物体绘制时出现扎堆现象。

当然还有更好的方法,就是使用模型矩阵(Model Matrix)。

使用模型矩阵,可以对物体进行位移、缩放、旋转。

这样的话就可以将物体从坐标原点移开,并且还能够进行一些相关操作,不用去考虑在局部空间来定义世界空间的坐标了。

观察空间

横看成岭侧成峰 远近高低各不同

当物体在世界空间中就位了,接下来就是要考虑从哪个方向和角度来观察物体了。

观察空间,又是 OpenGL 的摄像机,是将世界空间的坐标转化为摄像机的视角所观察到的空间坐标。

也就是说,在观察空间里,坐标原点不再是世界空间的坐标原点了,而是以摄像机的视角作为场景原点,这就不再是简单地进行平移、旋转了,而是切换到另一种坐标系里。

OpenGL 本身是没有摄像机的概念的,不过可以通过把场景中的所有物体往相反的方向移动来模拟出摄像机。这样就场景没动,而摄像机在移动。

要定义一个摄像机,或者说要定义一个摄像机视角为坐标原点的坐标系,需要:

  • 摄像机在世界空间中的位置
  • 摄像机观察的方向
  • 指向摄像机右测的向量
  • 指向摄像机上方的向量

如图,最终建立了一个以摄像机位置为原点的坐标系。

其中,蓝色箭头为摄像机坐标系中的 Z 轴,绿色箭头为摄像机坐标系中的 Y 轴,红色箭头为摄像机坐标系中的 X 轴。

而接下来要做的就是将物体在世界空间中的坐标转换到以摄像机视角为原点的观察空间坐标中。

这其中也需要用到一个转换矩阵:视图矩阵(View Matrix)。通过视图矩阵来切换坐标系。

裁剪空间

当物体坐标都位于观察空间后,接下来要做的就是裁剪。根据我们的需要来裁剪一定范围内的物体,而在这个范围之外的坐标就会被忽略掉。

裁剪空间实质上还是进行坐标的操作。

从观察空间到裁剪空间,需要用到:投影矩阵(Projection Matrix)。

投影矩阵会指定一个坐标范围,这个范围内的坐标将变换为归一化设备坐标 ,不在这个范围内的坐标就会被裁剪掉。

观察空间中的坐标经过投影矩阵的变换之后称为投影坐标,又叫做裁剪坐标

说是裁剪坐标,其实是待裁剪,接下来的裁剪过程将由 OpenGL 来完成的。投影矩阵的变换,只是筛选出那些不需要被裁剪的坐标。

由投影矩阵创建的范围,是一个封闭的空间几何体,被称为视景体

投影矩阵有两种不同的形式,创建的视景体也有两种样式。

正交投影

正交投影会创建一个类似立方体的视景体。它由左、上、右、下 四个方向距离和近平面距离、远平面距离组成。四个方向距离定义了近平面和远平面的大小。而在近平面和远平面之外的坐标点就会被裁剪掉了。

在场景中处于视景体内的物体会被投影到近平面上,然后再将近平面上投影出的内容映射到屏幕上。

它所用到的矩阵是正交投影矩阵。

由于正交投影是平行投影的一种,其投影线是平行的,所以投影到近平面上的图形不会产生真实世界中的近大远小的效果。因为正交投影没有把透视考虑进去,所以,远处的物体不会变小,这适用于一些特定的场合。

透视投影

透视投影是能够产生近大远小效果的,就像我们人眼一样,看远处的物体就变得很小了。

它所用到的矩阵就是透视投影矩阵。

透视投影也会创建一个视景体,类似于锥形。它同样也有着近平面距离和远平面距离,而且也是将近平面的内容映射到屏幕视口中,但不同与正交投影近平面和远平面大小相同,所以它的左、上、右、下距离都是相对于近平面的。

可以看到,透视投影的投影线互不平行,都相交于视点。因此,同样尺寸的物体,才会近处的投影出来大,远处的投影出来小。

透视除法

当坐标经过投影矩阵的变换到裁剪空间之后,紧接着就会进行透视除法的操作。

透视除法是在三维绘制中产生近大远小效果非常关键重要的一步。

在此之前要先来了解一下 OpenGL 中的 w 分量

OpenGL 坐标系中除了 x、y、z 坐标外,还有 w 分量,默认情况下都是 1 。而经过透视投影变换之后,w 分量不再是 1 了,正交投影不改变 w 分量。

而 OpenGL 进行裁剪,实质上是 GPU 进行裁剪的过程,就是将 x、y、z 坐标的绝对值与 w 分量绝对值进行比较,只要有一个分量的绝对值大于 w 的绝对值,就认为不在视景体内,会被裁剪掉。

经过裁剪之后,再进行透视除法。就是将 x、y、z 坐标分别除以 w 分量,得到新的 x、y、z 坐标。由于 x、y、z 坐标的绝对值都小于 w 的绝对值,所以得到新的坐标值都是位于 $ [-1,1] $ 的区间内的。此时得到的坐标,也就是归一化设备坐标

归一化设备坐标是独立于屏幕的,而且它的坐标系用的是左手坐标系。

经过透视投影矩阵变换之后,每个坐标的 w 分量都不相同了,这样再经过透视除法操作,就会使得远处的物体看起来变小了。

屏幕空间

有了归一化设备坐标,最后一步就是将坐标投射到屏幕上,这一步是由 OpenGL 来完成的。

OpenGL 会使用 glViewPort 函数来将归一化设备坐标映射到屏幕坐标,每个坐标都关联了屏幕上的一个点,这个过程称为视口变换。这一步操作不再需要变换矩阵了。

就这样,一个点的坐标就完成了从局部空间坐标 $(x,y,z,w)$ 到屏幕坐标 $(x,y)$ 的转变。

用一张图总结如下:

坐标的矩阵操作

点的坐标可以看作是一个向量,用 $V$ 表示,而矩阵用 $M$ 表示。

那么,从 局部空间 -> 世界空间 -> 观察空间 -> 裁剪空间 ,四个空间的转换,需要用到三个转换矩阵,点从某个坐标系变换到另一个坐标系的时候都要左乘某个变换矩阵,最后裁剪空间的坐标可以表示如下:

$$V_{clip}=M_{projection} \cdot M_{view} \cdot M_{model} \cdot V_{local}$$

而在着色器脚本中,gl_Position 对应的也是 $V_{clip}$ 裁剪坐标。

有了裁剪空间坐标后,接下来的事情就交个 OpenGL 去完成裁剪和透视除法就好了。

图形适应宽高比

在文章一开始提到的,绘制的圆形变成了椭圆,绘制的正多边形却东倒西歪的,现在也能给出原因了。

默认情况下,局部空间、世界空间、观察空间、裁剪空间的坐标系都是重合的,都是以$(0,0,0)$为坐标原点。一开始只是给出了理想状态下的平面坐标点,并且定义着色器脚本如下:

attribute vec4 a_Position;
void main(){
    gl_Position = a_Position;
}

那么它经过一系列转换后,最后 OpenGL 用来裁剪的坐标还是我们定义的基于平面的坐标,只有$(x,y)$值,而 $z$ 坐标默认为 0,$w$ 坐标默认为 1 。经过透视除法后的归一化设备坐标依旧是$(x,y)$。

而归一化设备坐标假定的坐标空间是一个正方形,但手机屏幕的视口却是一个长方形,这样的话,就会有一个方向被拉伸。同样的份数,但长度越长,导致每一份的长度也增加了,所以也就被拉伸了。

要解决这种问题,可以在归一化设备坐标上进行操作,将较长的一边乘以相应的比例系数,转化到同样的长度比上。

// 1280 * 720 的宽高比
aspect =  width / height ;
x = x * aspect 
y = y 

这样一来,将较长的一边的比例放大了,取较短的那一边作为 1 的标准。

当然也可以在坐标转换成归一化设备坐标之前,也就是在投影时就把拉伸的情况考虑进去。

使用正交投影,再将物体的宽高投影到近平面上时,就把屏幕的宽高比例系数考虑进去,这样在转换成归一化设备坐标之前就已经完成了图形的宽高比适应。

这样的话,就需要修改着色器脚本语言,把投影矩阵考虑在内。

attribute vec4 a_Position;
uniform mat4 u_Matrix;
void main(){
    gl_Position = u_Matrix * a_Position;
}

具体实操下一篇博客再写了。

参考

  1. 《OpenGL ES 应用开发实践指南》
  2. 《OpenGL ES 3.x 游戏开发》
  3. http://blog.csdn.net/iispring/article/details/27970937

具体代码详情,可以参考我的 Github 项目: https://github.com/glumes/AndroidOpenGLTutorial

原创文章,转载请注明来源:    OpenGL 学习系列---坐标系统