WebGL原来如此:搞懂三维坐标系变换
还记得刚入职就迷迷糊糊接手一个原生WebGL项目,对于没有任何图形学基础的我,只是葫芦画瓢地去凑着色器需要的顶点数据,对于控制台输出的那些0-1之间的小数似懂非懂,对于为什么传给着色器这个变换矩阵不求甚解,拼拼凑凑实现了功能也很开心。
随着逐渐学习,才发现那时候的我欠缺了对图形渲染最基础的认知。
对于做前端的同学而言,在web框架日新月异的迭代之间,图形渲染是个相对小众且门槛略高的方向,除了必备的编程能力外还要应用高等数学与计算机图形学知识。但随着浏览器引擎渲染能力增强,对交互效果的要求越来越高,并且有Three.js等库的协助,三维渲染也逐渐成为前端研发的一项重要技能。
那么,既然要做图形渲染,就一起知其然且知其所以然吧~
正文
三维渲染,顾名思义,就是要将多角度可观测的三维物体绘制到一个固定的二维矩形屏幕上,那么三维到二维是如何映射的呢?想必这之间必然经历了一些坐标转换。这也就是前面我说的对图形渲染最基础的认知———坐标转换。
先贴一张我画的WebGL/OpenGL坐标变换流程图随意感受一下:
这也太复杂了吧?
不妨想象一个生活中常见的场景——拍照。
你发现了一处适合拍照的场景,并站过去摆好姿势。(模型变换,model transform)
摄影师找到一个好角度,举起相机对准你。(视图变换,view transform)
摄影师调整焦距,摒除掉干扰物进行拍摄。(投影变换,projection transform)
这就是所谓的MVP变换,整个坐标系变换的核心。
大致有个概念之后,我们再来逐个了解这些变换到底是怎么一回事呢。
一、物体坐标系 -> 世界坐标系(模型变换)
假设我们想要在屏幕上渲染一只喵和一只汪,对于小喵和小汪这两个模型,他们都有以自己为原点的坐标系,这就是物体坐标系。
世界坐标系可以理解为是这个所有模型共享的虚拟场景空间,要将喵和汪这两个模型都导入到这个大场景中来,并指定其各自的位置。这就需要对物体进行平移、旋转等操作,从而将其摆放到合适的位置上。
这里对物体的操作就是模型变换(Model Transform),将物体坐标与模型矩阵相乘就得到了其在世界坐标系下的坐标。
备注:
具体编程中,我们会发现着色器中接收到的顶点坐标在原来(x,y,z)基础上增加了一维1,即齐次坐标,代码如下:
attribute vec3 a_position; void main() {gl_Position = vec4(a_position, 1); }
之所以使用齐次坐标是由于平移不同于缩放和旋转的线性变换,为了将平移统一进行矩阵计算,就增加一维来做平移,统称为仿射变换。
二、世界坐标系->观察空间(视图变换)
如同拍摄,对于多角度可观测的三维场景,在屏幕上,我们并不能一下子看到所有视角画面,而是经过摄像机模拟人眼裁剪后所呈现的特定场景。这就需要将坐标变换到以摄像机为原点的观察空间中,该变换过程称为视图变换(View/Camera Transformation)。
我们很容易理解的一个现象,如果物体和相机的相对位置不变的情况下,同时移动相机和物体拍摄出来的结果是一样的。
所以为了计算方便,我们先定义相机的三要素:放置位置在原点(0,0,0)、朝向-z、向上方向为y。
如上图所示,经过平移和旋转将相机变换到约定的位置上来,物体只要相对跟着变换。将平移矩阵与旋转矩阵相乘,就得到了视图变换矩阵Mview = RviewTviewMview=RviewTview。
备注:
1.观察空间使用的是右手坐标系,z轴是摄像机的正前方,故z轴数值表示物体距离摄像机的远近,即深度,此时的深度值还是线性的。
2.如何得到这里的旋转矩阵RviewRviewRview?如果是将任意位置的-g旋转到-z不容易,但反过来容易。所以这里求逆矩阵,并利用“正交矩阵的逆等于转置”性质就得到RviewRviewRview。
3.这样约定相机位置后,结合前一步,其实变换都是作用在了物体上,可以将两个矩阵合并成一个矩阵,也就是大家常说的模型视图变换(ModelView Transform)。
三、观察空间->裁剪空间(投影变换)
在观察空间中,我们知道只有位于视椎体内的物体才会被摄像机渲染可见,那么对于可见的3D物体如何将其映射到2D平面上呢?就需要接下来的投影变换(Projection Transform)。
投影变换包括了 正交投影(Orthographic Projection) 和 透视投影(Perspective Projection),两者区别由下图显见,正交投影构造的是一个立方体,透视投影构造的是一个上下左右面不平行、远近面大小不一的 视椎体(frustum),所以正交投影变换得到的远近物体大小都一样,而透视投影变换能产生近大远小的效果。
要实现远近裁剪面内的物体能够投影到近平面上,正交投影的标准立方体就很容易实现,但对于透视投影远平面大于近平面的情况就比较复杂。那么拆解问题,首先挤压椎体上下左右平面将其变成跟正交投影一样的立方体,然后再进行正交投影即可。
挤压规则约定三点:① 近平面不变;② 远平面z值不变;③ 远平面中心点不变;
再结合相似三角形性质,可以推导出将透视投影变换到正交投影的矩阵。
此外,对于视椎体定义两个变量:① 宽高比aspect ratio = width / height; ② 垂直可视角fovY
最终得到的透视矩阵:
- near:近裁剪平面距离
- far:远裁剪平面距离
- fov:椎体竖直方向的张开角度(当视野更大时,物体通常变小)
- aspect:摄像机的宽高比(该参数解决了当画布调整大小和形状时模型的变形问题)
至此,经过变换得到了裁剪坐标。转换过程中对x,y,z分量都进行了不同程度的缩放和平移,x,y是屏幕横纵坐标,z是垂直屏幕的深度坐标。裁剪是将变换后的x,y,z与w值作比较,如果位于[-w,w]范围内保留,否则剔除。
备注:
1、 ,由于矩阵乘法很耗时,并且矩阵具有结合律,通常我们会将MV矩阵先相乘得到一个模型视图矩阵。
2、性质:将齐次坐标(x,y,z,1)每个分量都乘以不等于0的常数k,得到的(kx,ky,kz,k)在3D空间中与(x,y,z,1)表示同一个点。
3、透视矩阵需要注意的是,会翻转z轴。裁剪空间坐标系是左手坐标系(z轴指向远离观察者并指入屏幕的位置)。
四、裁剪空间 -> 标准化设备空间(齐次除法)
经过前面的变换,我们在视椎体中得到裁剪后要展示的部分,接下来要将视椎体内物体映射到近平面上,以在2D平面上展示。
就需要将坐标转换到一个与硬件设备无关的 规范化设备坐标(NDC, Normalized Deviced Coordinates),以描述映射到近平面上的坐标,这一步的变换称为 齐次除法 或 透视除法,即将x,y,z分量分别除以w分量,将其变换到[−1,1]3[-1,1]^3[−1,1]3范围内。以x轴为例转换公式: (公式)
在这之前,为了方便仿射变换,我们一直使用的是齐次坐标(x,y,z,w),经过齐次除法变换回笛卡尔坐标(x,y,z)。如下图所示,变换后的原点在(0,0,0),xyz分量均为2个单位的立方体中,并且z轴进行了翻转(由右手系变成左手系)。
备注:
做完投影变换与齐次除法后,物体坐标都变换到[-1, 1]^3[−1,1]3范围内,会导致物体拉伸,后面还会进行一次视口变换再拉伸回来。
五、标准设备空间 -> 屏幕坐标(视口变换)
最后一步视口变换,将NDC的[−1,1]3[-1,1]^3[−1,1]3立方体中的坐标变换为视口坐标(屏幕坐标),从而在屏幕上进行像素绘制。
WebGL绘制的画布是canvas元素,定义左下角为坐标原点,右上角像素坐标为(pixelWidth, pixelHeight)。
这个坐标变换只对x,y进行操作,由 [-1,1]^2[−1,1]2 变换到 [0,width]*[0,height] 范围。如下面矩阵所示,对x和y进行缩放拉伸,并将其移到屏幕的原点。实现了标准设备坐标与屏幕窗口像素的一一对应。
在webgl中有直接设置视口(ViewPort)的方法:
gl.viewport(0, 0, this.cvs.width, this.cvs.height);
等等,那z坐标呢?经过前面齐次除法得到的z分量用来表示深度信息,将被用于深度缓冲(Z-buffer)算法,计算每个像素的深度测试,以实现正确的遮挡效果。这属于着色内容,本文不多赘述。
总结
哇,你好棒,耐心看到这里~
最后再贴一张图总结一下,读完上面的变换原理后,这张图是不是一下子就明白了呢?!
那么在WebGL中,着色器是如何应用上述坐标转换并结合纹理渲染成像的呢,在之后的着色原理中再详述。
参考:
WebGL model view projection - Web API 接口参考 | MDN
坐标系统 - LearnOpenGL CN
WebGL 坐标系统 - 知乎
https://www.songho.ca/opengl/gl_projectionmatrix.html
Trying to understand the math behind the perspective matrix in WebGL - Stack Overflow
WebGL原来如此:搞懂三维坐标系变换相关推荐
- 彻底搞懂“旋转矩阵/欧拉角/四元数”,让你体会三维旋转之美
目录 旋转矩阵 坐标变换的作用 实现坐标变换所需的数据 位姿变换 坐标变换中旋转的实质 坐标变换中平移的实质 如何计算坐标系B各坐标轴在坐标系A上的投影?(多坐标变换) 如何实现坐标变换? 欧拉角 欧 ...
- 搞懂RTK定位,看这一篇就够了
搞懂RTK定位,看这一篇就够了! [导读]说到定位,相信大家一定不会觉得陌生.如今我们所处的信息时代,人人都有手机.每天,我们都会用到与地图和导航有关的APP. 这些APP,就是基于定位技术的.说到定 ...
- 一文搞懂什么VR,什么是6Dof,欧拉角,四元数转视图矩阵
目录 一.什么是VR 二.什么是3Dof,6Dof, 9Dof 三.欧拉角(姿态角) 四.Android手机的欧拉角与坐标系 五.安卓坐标系转换欧拉角 六.根据姿态四元数求视图矩阵 一文搞懂什么VR, ...
- 一文彻底搞懂前端监控 等推荐
大家好,我是若川.话不多说,这一次花了几个小时精心为大家挑选了20余篇好文,供大家阅读学习.本文阅读技巧,先粗看标题,感兴趣可以都关注一波,一起共同进步. 前端点线面 前端点线面 百度前端研发工程师, ...
- 一文搞懂HMM(隐马尔可夫模型)-Viterbi algorithm
***一文搞懂HMM(隐马尔可夫模型)*** 简单来说,熵是表示物质系统状态的一种度量,用它老表征系统的无序程度.熵越大,系统越无序,意味着系统结构和运动的不确定和无规则:反之,,熵越小,系统越有序, ...
- C语言指针超全面透析(原来你一直没有搞懂C语言指针是因为没有理解其中的规律)
文章目录 写在前面 一.思考指针的基础 1.指针的实质 2.指针的层次 3.指针的分类 4.两个符号(&和*) 二.单指针(int *p) 三.指针数组(int *p[10]) 四.行指针(i ...
- Unity学习笔记 球形全景图平面像素坐标与三维坐标系上的坐标之间的转换
前言 本文将讲解如何通过球形全景图上的二维坐标通过换算得到三维坐标系上的三维坐标.具体场景就是,已知道一张全景图上某个点的像素位置(px,py),最终可以算出该点对应在球体上的三维坐标(X,Y,Z). ...
- 三维图形变换:三维几何变换,投影变换(平行/ 透视 投影)
通过三维图形变换,可由简单图形得到复杂图形,三维图形变化则分为三维几何变换和投影变换. 6.1 三维图形几何变换 三维物体的几何变换是在二维方法基础上增加了对 z 坐标的考虑得到的. 有关二维图形几何 ...
- 一文搞懂全排列、组合、子集问题
微信搜一搜:[bigsai] 获取更多肝货知识 春风十里,感谢有你 前言 Hello,大家好,我是bigsai,long time no see!在刷题和面试过程中,我们经常遇到一些排列组合类的问题, ...
最新文章
- 【Java并发系列04】线程锁synchronized和Lock和volatile和Condition
- 疫情冬天过去,二手经济春天到来
- 工业用微型计算机笔记(2)-二进制有符号数
- BZOJ 2818 Gcd
- html 中word的超链接,word中如何实现添加超链接的方法
- YD5141SYZ后压缩式垃圾车的上装箱体设计
- struts2 ognl.OgnlException: target is null for setProperty(null, pageNO, [Ljava.lang.String;@c3bb57)
- python+ UIAutomator2+WEditor环境安装详情教学以及案例
- java中intern,在Java中什么时候使用String.intern()方法?
- Nginx常用Rewrite(伪静态规则)WordPress/PHPCMS/ECSHOP/ShopEX/SaBlog/Discuz/DiscuzX/PHPWind/Typecho/DEDECMS...
- phyton的函数与类的学习
- 软件开发应遵循的原则
- 会说话的汤姆猫2 Talking Tom 2(含数据包) v2.0.3
- 苹果cms用Fusion app对接封装app源码教程
- 分享一些好玩有趣的软件给你
- 回溯法解决01背包-非递归算法-效率低
- 『实用教程』VSPD虚拟串口工具——从此告别硬件串口调试
- 初学css能做的实战 登录页面制作
- OpenInfra Summit 2022 | 安超云用户脱颖而出 入围超级用户大奖
- Consolas-with-Yahei