OpenGL逻辑学快速入门 卷四 空间与变换:坐标系链条的全部因果

news2026/4/29 4:04:18
卷四 空间与变换坐标系链条的全部因果难度★★☆视角[CPU][GPU]优先级P04.1~4.4, 4.6P14.5P24.7上一卷你看到一行gl_Position u_mvp * vec4(a_pos, 1.0)。这一卷把这一行展开——每个空间为什么存在每次乘法在做什么为什么必须按这个顺序。数学预备30 分钟够用集不是数学课。只补理解后续内容所需的最小集。向量与点向量方向 长度不依附于位置“向北 5 米”点空间位置“经度 116.4纬度 39.9”OpenGL 用 4 维齐次坐标统一表示点(x, y, z,1)向量(x, y, z,0)为什么第 4 维要区分平移变换对点有效对向量无效把方向向北 5 米平移没意义。第 4 维 0 时平移项被自动归零。这就是齐次坐标的核心收益。矩阵 × 向量4×4 矩阵乘 4 维列向量得 4 维列向量| a b c d | | x | | axbyczdw | | e f g h | × | y | | exfygzhw | | i j k l | | z | | ixjykzlw | | m n o p | | w | | mxnyozpw |矩阵 一种线性变换。一个矩阵代表一种把空间所有点统一映射到新位置的规则。矩阵乘法不可交换A·B ≠ B·A。变换的顺序很重要先转再平移 ≠ 先平移再转。复合变换(A·B)·v A·(B·v)——先用 B 变换 v再用 A 变换结果。OpenGL 中读法MVP·v “先 P再 V再 M”不是。是先 M再 V再 P。从右往左读数学约定。理解这个就够了。下面开始正题。4.1 为什么要这么多空间[CPU][图×1]视角[CPU]优先级P06 个空间一图速览┌──────────────┐ │ 模型空间 │ 顶点数据原始坐标以模型自身为原点 └──────┬───────┘ │ × Model 矩阵 ▼ ┌──────────────┐ │ 世界空间 │ 全场景的统一坐标系以世界原点为基准 └──────┬───────┘ │ × View 矩阵 摄像机的逆变换 ▼ ┌──────────────┐ │ 观察空间 │ 以摄像机为原点相机看 -z 方向 └──────┬───────┘ │ × Projection 矩阵 ▼ ┌──────────────┐ │ 裁剪空间 │ 齐次坐标系视锥被映射到 [-w, w]³ 立方体 └──────┬───────┘ │ ÷ w 透视除法硬件自动 ▼ ┌──────────────┐ │ NDC 空间 │ [-1,1]³ 标准立方体 └──────┬───────┘ │ × Viewport 变换 ▼ ┌──────────────┐ │ 屏幕空间 │ 像素坐标 (x,y) 深度 z∈[0,1] └──────────────┘每个空间存在的独特价值空间为什么需要模型空间让美术建模时不用关心模型摆在世界哪里世界空间让多个模型有统一坐标系能算彼此距离、光照观察空间让剔除背面、雾效等计算简单朝向相机的方向是固定的 -z裁剪空间让视锥变成简单立方体硬件秒杀裁剪测试NDC与具体屏幕分辨率解耦的标准化坐标屏幕空间实际像素位置光栅化要的就是它核心思想每多一个空间是为了让接下来某一类计算变简单。这是计算机科学里一个普适原则——用空间换简洁。为什么不能少几个合并模型和世界→ 一个模型摆在场景里 100 次得复制 100 份顶点数据。合并世界和观察→ 移动相机得改动所有顶点。跳过裁剪空间直接到屏幕→ 视锥裁剪成噩梦。每个空间砍掉都会让某个固定操作复杂化。这就是它存在的逻辑必然性。4.2 Model 矩阵[CPU][图×1]视角[CPU]优先级P0三种基本变换平移| 1 0 0 tx | | 0 1 0 ty | | 0 0 1 tz | | 0 0 0 1 |缩放| sx 0 0 0 | | 0 sy 0 0 | | 0 0 sz 0 | | 0 0 0 1 |旋转绕 z 轴 θ 角举例| cos -sin 0 0 | | sin cos 0 0 | | 0 0 1 0 | | 0 0 0 1 |复合顺序为何不可交换要把一个模型先放大 2 倍再绕 y 轴转 30°再平移到 (5, 0, 0)矩阵复合M T(5,0,0) · R_y(30°) · S(2)从右往左读先缩放再旋转再平移。为什么不能换顺序先平移再旋转物体会绕远离原点的位置画大圈 先旋转再平移物体在原地转好后挪到目标位置两种结果完全不同。这不是 OpenGL 的怪癖是矩阵代数本身的性质。经验法则99% 的情况都是T · R · S · v顺序——先缩放再旋转再平移。旋转的三种表示欧拉角pitch / yaw / roll优点直观、好懂、好存3 个数缺点万向锁Gimbal Lock——三个旋转轴在某些角度会重合丢失一个自由度适用相机控制、调试轴角axis angle优点直接对应绕某条轴转某个角度无万向锁缺点插值不自然适用单次旋转的描述四元数优点无万向锁、插值平滑slerp、紧凑4 个数缺点抽象、调试痛苦适用动画、骨骼蒙皮、相机平滑过渡——几乎所有长时间存储 平滑插值的场景结论表达 / 存储用四元数最终交给 GLSL 时转成矩阵。4.3 View 矩阵 摄像机的逆变换[CPU][图×1]视角[CPU]优先级P0等价性证明关键事实OpenGL 没有摄像机这个概念。你看到的相机视角是通过反向移动整个世界伪装出来的。设想相机在 (5, 0, 0) 看向原点。等价于把整个世界向 (-5, 0, 0) 平移然后假装相机在原点。数学上相机自己的世界变换C T·R先 R 再 T 把相机摆到位。看向相机视角 把世界用C⁻¹ R⁻¹·T⁻¹反着动。关键化简旋转矩阵是正交矩阵所以R⁻¹ Rᵀ——无需做矩阵求逆直接转置即可便宜。这就是 LookAt 实现里你看不到inverse()调用的原因。为什么这样设计渲染管线只需要一种原点视角——所有计算深度比较、雾效、光照都假设相机在原点让动态相机等价于反向变换世界——管线不需要新加任何处理View 矩阵 摄像机变换的逆。如果摄像机变换是C T(camera_pos) · R(camera_orientation)“摄像机从原点平移旋转到此处”则View C⁻¹。LookAt 矩阵的推导最常用的相机定义方式相机在哪 (eye)、看向哪 (center)、哪边算上 (up)。构造步骤算相机的本地 z 轴反视线方向因为 OpenGL 相机看 -zf normalize(center - eye) // 视线方向 z_axis -f // 相机 z 轴指向相机背后算本地 x 轴右方向x_axis normalize(cross(f, up))算本地 y 轴真正的上方向避免 up 不正交y_axis cross(x_axis, f) cross(z_axis, x_axis) 的反实际上y_axis cross(-z_axis, x_axis)cross(f, x_axis)摄像机变换C的旋转部分由(x_axis, y_axis, z_axis)三列构成、平移部分是eye。求逆。利用旋转矩阵的逆 转置平移部分变号View | x_axis.x x_axis.y x_axis.z -dot(x_axis, eye) | | y_axis.x y_axis.y y_axis.z -dot(y_axis, eye) | | z_axis.x z_axis.y z_axis.z -dot(z_axis, eye) | | 0 0 0 1 |不用背公式——理解推导思路就行相机变换是 T·R逆就是 R⁻¹·T⁻¹。GLM、Eigen 之类的库都有lookAt(eye, center, up)调即可。4.4 Projection 矩阵[CPU][图×2]视角[CPU]优先级P0两种投影正交投影视体是一个长方体保留平行性平行线投影后还是平行不产生近大远小用途CAD、UI、2D 游戏、阴影贴图透视投影视体是一个截头锥体frustum不保留平行性铁轨交于远点产生近大远小用途绝大多数 3D 渲染透视投影做了什么把视锥映射到立方体。这件事数学上叫同伦变换。视锥的特征近平面 z-near宽度 2·near·tan(fov/2)·aspect高度 2·near·tan(fov/2)远平面 z-far宽度 2·far·tan(fov/2)·aspect高度 2·far·tan(fov/2)远处更宽近处更窄立方体的特征均匀的 [-1, 1]³。要把远处的宽矩形压缩到和近处的小矩形一样宽——这就是近大远小的数学根源。透视矩阵GLM 风格P | 1/(aspect·tan(fov/2)) 0 0 0 | | 0 1/tan(fov/2) 0 0 | | 0 0 -(fn)/(f-n) -2fn/(f-n) | | 0 0 -1 0 |关键点第 4 行是(0, 0, -1, 0)——这一行让gl_Position.w -z观察空间 z。然后透视除法/w时所有 (x, y) 都被-z除——z 越大越远分母越大结果越小。这就是近大远小。为什么 z 不是线性映射把 (近 z -near, 远 z -far) 映射到 NDC 的 [-1, 1]最直觉的是线性映射。但 OpenGL 选了双曲映射NDC z (-(fn)/(f-n) · z - 2fn/(f-n)) / (-z)化简后NDC z对原始z是1/z的形式。为什么这么选精度分布1/z 形式让近处的 z 精度高、远处低。这恰好符合视觉需求——近的物体我们看得清远的差一点点没关系。硬件友好透视除法本来就要算 1/wz 用 1/z 形式刚好搭便车零额外开销。代价远处 z 精度差。两个相距 1cm 的远处物体深度 buffer 可能存成相同值发生Z-fighting深度撕裂。解决方案见 4.5。4.5 现代深度技巧Reverse-Z[GPU]视角[GPU]优先级P1问题标准 OpenGL近 z 映射到 NDC z -1远 z 映射到 1。深度 buffer 存 [0, 1]做映射(NDC z 1) / 2→ 近 0、远 1。由于上面1/z的精度分布最重要的近处区域反而精度集中在 z buffer 的小数末位——浮点数在小数附近精度高、在大数附近精度低正好和近精度高的需求反着来。Reverse-Z 的解法反过来让近 z 对应 buffer 的 1.0、远 z 对应 0.0。这样浮点精度在 1.0 附近最高 → 近处精度最高 ✅1/z分布让近处比例大 → 又一次集中在 1.0 附近 ✅两层近高远低的分布叠加→ Z-fighting 问题在大 far/near 比的场景下大幅缓解。Reverse-Z 不是改一个矩阵很多文章把 Reverse-Z 说成改投影矩阵就行——这不准确。它需要三处协同投影矩阵让 z 映射方向反转深度比较函数从GL_LESS更近 z 更小改为GL_GREATER更近 z 更大清屏深度值从glClearDepth(1.0)改为glClearDepth(0.0)漏一项就完全错。无限远平面把far设为无穷大避免在远处出现突然不见的裁剪。和 Reverse-Z 配套使用时深度 buffer 上的值由depth n / (-z_view)给出——z_view -n时 depth 1z_view → -∞时 depth → 0永不精确达 0但浮点下逼近足够近。常用于户外大场景、星空。具体矩阵推导——以下为 Reverse-Z 无限远 far 的复合形式有限 far 的纯 Reverse-Z 公式不同投影矩阵第 3 行 [0, 0, 0, n]第 4 行 [0, 0, -1, 0]。详细推导可参考 Sasha Willems / Reverse-Z 的经典文献。4.6 透视除法与视口变换[GPU][图×1]视角[GPU]优先级P0透视除法发生位置的统一澄清回到卷三 3.6 的图VS 输出 gl_Position裁剪空间齐次 (x,y,z,w) ↓ 图元装配仍在裁剪空间按齐次坐标做 ↓ 视锥裁剪在裁剪空间用 ±w 不等式 ↓ 透视除法 (x/w, y/w, z/w, 1) → NDC 空间 ← ★这里 ↓ 视口变换 NDC (x,y) → 屏幕像素坐标 ↓ 光栅化关键透视除法不在 VS 里不在图元装配里在裁剪之后。由硬件自动完成你写不到也改不了。齐次坐标 w 的全部使命gl_Position.w这个第 4 个分量承担了三件大事区分点和向量1 是点0 是向量齐次坐标基本约定携带透视信息投影矩阵让w -z_view透视除法时 (x, y) 都被 z 除——产生近大远小携带透视校正插值的权重FS 插值时用 1/w 重新加权卷三 3.7 的透视校正小节没有 w 的话这三件事都做不到。所以裁剪空间用齐次坐标不是炫技是必需。视口变换glViewport(x,y,width,height);把 NDC 的 [-1, 1] 映射到屏幕的 [x, xwidth] × [y, yheight]。改的是什么决定渲染画到屏幕的哪个矩形。多视口如分屏游戏就是不同 Draw Call 间切换 viewport。不改的是什么不改裁剪范围裁剪在裁剪空间已经做完了不改投影矩阵视口和投影是独立的坑viewport 默认 窗口大小。窗口大小变了用户拖窗口你必须手动重新glViewport否则渲染只填了原矩形那块。屏幕坐标的小细节OpenGL 的屏幕坐标左下角是 (0, 0)向右上为正。不是大多数 UI 库的左上角 (0, 0)、向右下。读glReadPixels、做截图、贴图坐标和屏幕坐标转换时容易出 y 翻转 bug。记住OpenGL 的世界y 朝上。新手雷图像加载时的 y 翻转几乎所有图像格式PNG、JPEG、BMP规定第一行像素 图像顶部——即 y 朝下。OpenGL 纹理则规定第一行像素 图像底部——即 y 朝上。如果你直接glTexImage2D(..., pixels)上传 stb_image 默认加载的数据得到的纹理在屏幕上上下颠倒。两种处理stbi_set_flip_vertically_on_load(true);// 加载时翻转一次最干净或者着色器里采样时翻转 vvec4 c texture(tex, vec2(uv.x, 1.0 - uv.y));。记住屏幕坐标、纹理坐标 OpenGL 都是 y 朝上外部图像是 y 朝下。这是新手最常见的纹理倒置 bug 根因。4.7 内存布局补丁列主序 vs 行主序[CPU]视角[CPU]优先级P2本节是查漏补缺。读完 4.1~4.6 已经能写代码本节解决为什么矩阵在内存里这么排。两种排法列主序OpenGL / GLSL 内存顺序: m00 m10 m20 m30 | m01 m11 m21 m31 | ... C 数组写法: float m[16] { col0..., col1..., col2..., col3... }; 行主序C 程序员习惯 / D3D 内存顺序: m00 m01 m02 m03 | m10 m11 m12 m13 | ...OpenGL 选列主序的原因数学习惯经典线性代数把向量当列向量矩阵乘列向量M·v。列主序内存布局让读取一列做点乘是连续访问。GLSL 一致性GLSL 里mat4 m; vec4 v; m * v;是矩阵×列向量与列主序自洽。代价C 程序员二维数组习惯mat[row][col]和列主序冲突。容易出现看着对的代码计算结果错。M * vvsv * MGLSL 中gl_Position M * v; // 矩阵 × 列向量标准用法不要写gl_Position v * M; // GLSL 会把 v 当行向量左乘 → 等价于 M^T * v实战建议存矩阵用 GLMglm::mat4。它内部就是列主序与 GLSL 直接对齐。上传时glUniformMatrix4fv(loc, 1, GL_FALSE, glm::value_ptr(m))第 3 个参数GL_FALSE “数据已是列主序不用转置”GL_TRUE时 OpenGL 会上传时转置行主序数据 → 内部列主序自己手写矩阵 → 一律按列填floatm[16]{1,0,0,0,// 第 1 列0,1,0,0,// 第 2 列0,0,1,0,// 第 3 列tx,ty,tz,1// 第 4 列包含平移};注意最后一列是平移不是最后一行。这就是列主序最容易混淆的地方。本卷自检读完本卷你应该能回答为什么 OpenGL 没有摄像机概念透视除法到底在哪一步发生由谁完成为什么 OpenGL 选列主序View 矩阵为什么是相机变换的逆Reverse-Z 需要改哪三处gl_Position.w承担了哪几个使命写一个把模型先缩放 2 倍、再绕 y 轴转 90°、再平移到 (5,0,0) 的复合矩阵顺序怎么写下一卷我们离开数学进入着色器语言 GLSL——同一个gl_Position行背后的 GLSL 是个怎样的语言、它和 C 的相似与不同从何而来。

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

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

相关文章

SpringBoot-17-MyBatis动态SQL标签之常用标签

文章目录 1 代码1.1 实体User.java1.2 接口UserMapper.java1.3 映射UserMapper.xml1.3.1 标签if1.3.2 标签if和where1.3.3 标签choose和when和otherwise1.4 UserController.java2 常用动态SQL标签2.1 标签set2.1.1 UserMapper.java2.1.2 UserMapper.xml2.1.3 UserController.ja…

wordpress后台更新后 前端没变化的解决方法

使用siteground主机的wordpress网站,会出现更新了网站内容和修改了php模板文件、js文件、css文件、图片文件后,网站没有变化的情况。 不熟悉siteground主机的新手,遇到这个问题,就很抓狂,明明是哪都没操作错误&#x…

网络编程(Modbus进阶)

思维导图 Modbus RTU(先学一点理论) 概念 Modbus RTU 是工业自动化领域 最广泛应用的串行通信协议,由 Modicon 公司(现施耐德电气)于 1979 年推出。它以 高效率、强健性、易实现的特点成为工业控制系统的通信标准。 包…

UE5 学习系列(二)用户操作界面及介绍

这篇博客是 UE5 学习系列博客的第二篇,在第一篇的基础上展开这篇内容。博客参考的 B 站视频资料和第一篇的链接如下: 【Note】:如果你已经完成安装等操作,可以只执行第一篇博客中 2. 新建一个空白游戏项目 章节操作,重…

IDEA运行Tomcat出现乱码问题解决汇总

最近正值期末周,有很多同学在写期末Java web作业时,运行tomcat出现乱码问题,经过多次解决与研究,我做了如下整理: 原因: IDEA本身编码与tomcat的编码与Windows编码不同导致,Windows 系统控制台…

利用最小二乘法找圆心和半径

#include <iostream> #include <vector> #include <cmath> #include <Eigen/Dense> // 需安装Eigen库用于矩阵运算 // 定义点结构 struct Point { double x, y; Point(double x_, double y_) : x(x_), y(y_) {} }; // 最小二乘法求圆心和半径 …

使用docker在3台服务器上搭建基于redis 6.x的一主两从三台均是哨兵模式

一、环境及版本说明 如果服务器已经安装了docker,则忽略此步骤,如果没有安装,则可以按照一下方式安装: 1. 在线安装(有互联网环境): 请看我这篇文章 传送阵>> 点我查看 2. 离线安装(内网环境):请看我这篇文章 传送阵>> 点我查看 说明&#xff1a;假设每台服务器已…

XML Group端口详解

在XML数据映射过程中&#xff0c;经常需要对数据进行分组聚合操作。例如&#xff0c;当处理包含多个物料明细的XML文件时&#xff0c;可能需要将相同物料号的明细归为一组&#xff0c;或对相同物料号的数量进行求和计算。传统实现方式通常需要编写脚本代码&#xff0c;增加了开…

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器的上位机配置操作说明

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器专为工业环境精心打造&#xff0c;完美适配AGV和无人叉车。同时&#xff0c;集成以太网与语音合成技术&#xff0c;为各类高级系统&#xff08;如MES、调度系统、库位管理、立库等&#xff09;提供高效便捷的语音交互体验。 L…

(LeetCode 每日一题) 3442. 奇偶频次间的最大差值 I (哈希、字符串)

题目&#xff1a;3442. 奇偶频次间的最大差值 I 思路 &#xff1a;哈希&#xff0c;时间复杂度0(n)。 用哈希表来记录每个字符串中字符的分布情况&#xff0c;哈希表这里用数组即可实现。 C版本&#xff1a; class Solution { public:int maxDifference(string s) {int a[26]…

【大模型RAG】拍照搜题技术架构速览:三层管道、两级检索、兜底大模型

摘要 拍照搜题系统采用“三层管道&#xff08;多模态 OCR → 语义检索 → 答案渲染&#xff09;、两级检索&#xff08;倒排 BM25 向量 HNSW&#xff09;并以大语言模型兜底”的整体框架&#xff1a; 多模态 OCR 层 将题目图片经过超分、去噪、倾斜校正后&#xff0c;分别用…

【Axure高保真原型】引导弹窗

今天和大家中分享引导弹窗的原型模板&#xff0c;载入页面后&#xff0c;会显示引导弹窗&#xff0c;适用于引导用户使用页面&#xff0c;点击完成后&#xff0c;会显示下一个引导弹窗&#xff0c;直至最后一个引导弹窗完成后进入首页。具体效果可以点击下方视频观看或打开下方…

接口测试中缓存处理策略

在接口测试中&#xff0c;缓存处理策略是一个关键环节&#xff0c;直接影响测试结果的准确性和可靠性。合理的缓存处理策略能够确保测试环境的一致性&#xff0c;避免因缓存数据导致的测试偏差。以下是接口测试中常见的缓存处理策略及其详细说明&#xff1a; 一、缓存处理的核…

龙虎榜——20250610

上证指数放量收阴线&#xff0c;个股多数下跌&#xff0c;盘中受消息影响大幅波动。 深证指数放量收阴线形成顶分型&#xff0c;指数短线有调整的需求&#xff0c;大概需要一两天。 2025年6月10日龙虎榜行业方向分析 1. 金融科技 代表标的&#xff1a;御银股份、雄帝科技 驱动…

观成科技:隐蔽隧道工具Ligolo-ng加密流量分析

1.工具介绍 Ligolo-ng是一款由go编写的高效隧道工具&#xff0c;该工具基于TUN接口实现其功能&#xff0c;利用反向TCP/TLS连接建立一条隐蔽的通信信道&#xff0c;支持使用Let’s Encrypt自动生成证书。Ligolo-ng的通信隐蔽性体现在其支持多种连接方式&#xff0c;适应复杂网…

铭豹扩展坞 USB转网口 突然无法识别解决方法

当 USB 转网口扩展坞在一台笔记本上无法识别,但在其他电脑上正常工作时,问题通常出在笔记本自身或其与扩展坞的兼容性上。以下是系统化的定位思路和排查步骤,帮助你快速找到故障原因: 背景: 一个M-pard(铭豹)扩展坞的网卡突然无法识别了,扩展出来的三个USB接口正常。…

未来机器人的大脑:如何用神经网络模拟器实现更智能的决策?

编辑&#xff1a;陈萍萍的公主一点人工一点智能 未来机器人的大脑&#xff1a;如何用神经网络模拟器实现更智能的决策&#xff1f;RWM通过双自回归机制有效解决了复合误差、部分可观测性和随机动力学等关键挑战&#xff0c;在不依赖领域特定归纳偏见的条件下实现了卓越的预测准…

Linux应用开发之网络套接字编程(实例篇)

服务端与客户端单连接 服务端代码 #include <sys/socket.h> #include <sys/types.h> #include <netinet/in.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <arpa/inet.h> #include <pthread.h> …

华为云AI开发平台ModelArts

华为云ModelArts&#xff1a;重塑AI开发流程的“智能引擎”与“创新加速器”&#xff01; 在人工智能浪潮席卷全球的2025年&#xff0c;企业拥抱AI的意愿空前高涨&#xff0c;但技术门槛高、流程复杂、资源投入巨大的现实&#xff0c;却让许多创新构想止步于实验室。数据科学家…

深度学习在微纳光子学中的应用

深度学习在微纳光子学中的主要应用方向 深度学习与微纳光子学的结合主要集中在以下几个方向&#xff1a; 逆向设计 通过神经网络快速预测微纳结构的光学响应&#xff0c;替代传统耗时的数值模拟方法。例如设计超表面、光子晶体等结构。 特征提取与优化 从复杂的光学数据中自…