图形学坐标系统梳理小结
1.计算机图形学中的坐标系统
下面解释了从一个数值坐标到输出到屏幕上的坐标变换过程,也是计算机中各种图形学API所遵循采用的方法。
[1]空间与变换
不同的坐标系统也被称作相应的空间,首先来看一下,一个只有数值的顶点在最终被转化到你在电脑看到的点需要经历哪些坐标系统(空间):
- 局部空间(Local Space)/ 物体空间(Object Space)
- 世界空间(World Space)
- 观察空间(View Space)/ 视觉空间(Eye Space)
- 裁剪空间(Clip Space)
- 屏幕空间(Screen Space)
顶点坐标(位于局部空间),称为局部坐标,然后顶点坐标会从局部空间按照上述顺序一步步变换到屏幕空间💻
而坐标从一个空间到另一个空间则需要变换矩阵来完成这一过程:
- 模型矩阵(Model Matrix)
- 视图矩阵(View Matrix)
- 投影矩阵(Projection Matrix)
[2]局部空间
局部空间是指物体所在的坐标空间,即对象最开始所在的地方。在局部空间中,物体位于空间的原点,所有的调整都是基于物体的相对位置进行的
[3]世界空间
世界空间是指虚拟的场景所在的空间,比如游戏场景等。它指顶点相对于场景的坐标,当所有物体导入程序时,它们有可能会都挤在世界的原点(0.0, 0.0, 0.0)
上,我们需要为每一个物体定义一个位置,从而能在更大的场景中合理的摆放让它们。模型矩阵的作用就是通过对物体进行位移、缩放、旋转等操作将其摆放到场景中的不同位置。
[4]观察空间
我们通过WebGL
在屏幕上展现给用户的内容并不是世界空间中摆放的全部内容,而是通过摄像机来模拟用户的眼睛所呈现的场景。观察空间就是从摄像机的视角所观察到的空间,也会称作摄像机空间或视觉空间。
以摄像机位置为原点,观察方向为-z
轴方向的一个空间通常用一系列的平移和旋转变换把世界空间中的物体转换到观察空间中。
[5]裁剪空间
摄像机有朝向,也有拍摄的视野范围,所有在视野范围之外的东西都是看不到的,都要被剔除。在每个顶点着色器运行结束的时候,OpenGL
希望坐标都在一个指定范围内,超出范围的坐标都会被裁剪掉,剩下的坐标才会进入片元着色阶段,这也就是裁剪空间名字的由来。而投影矩阵就是将物体从观察空间转换到裁剪空间。
研究表明人两眼重合视域通常为124度,当集中注意力时约为五分之一,即25度(知道为什么当一个人全神贯注时可能会忽略周边发生的事情了吧🤷🏼),单眼的舒适视域为60度。当然,在OpenGL
中摄像机的左右、上下方向也都有一定的范围,这个范围称作视野角度(FOV)。
图中蓝色的w
和h
就确定了摄像机上下左右能看到的范围大小;通常设置上下左右视野都是90度,同时设置一个近裁剪面和远裁剪面:比近裁剪面近、比远裁剪面远的物体被剔除,把两个裁剪面之间所有的物体都映射到投影平面上✄
我们再说回到投影矩阵,投影矩阵分为两种:正射投影矩阵和透视投影矩阵(将特定范围内坐标转化到标准化设备坐标系(NDC)的过程被称之为投影)。正射投影和透视投影的差别很明显,透视投影看起来更加立体真实(就想学画画时老师教的“近大远小,近高远低”),而正射投影则没有这种效果,使用正射投影的物体没有透视,远处的物体也不会显得更小,在正射投影中每个顶点距离观察者的距离都是一样的,当然正射投影也有它的用处,比如用于渲染一些二维的建筑或工程的程序,在这些场景中工程师们更希望顶点不会被透视所影响。
为上述每一个步骤都创建相应的变换矩阵:模型矩阵、观察矩阵和投影矩阵。一个顶点坐标将会根据以下过程被变换到裁剪坐标:
V
c
l
i
p
=
M
p
r
o
j
⋅
M
v
i
e
w
⋅
M
m
o
d
e
l
⋅
V
l
o
c
a
l
V_{clip}=M_{proj}\cdot M_{view}\cdot M_{model}\cdot V_{local}
Vclip=Mproj⋅Mview⋅Mmodel⋅Vlocal
顶点着色器的输出要求所有的顶点都在裁剪空间内,这就是使用变换矩阵做的,然后OpenGL
对裁剪坐标执行透视除法从而将它们变换到标准化设备坐标;OpenGL使用内部参数来标准化设备坐标映射到屏幕坐标,每一个坐标都关联了一个屏幕上的点,这个过程称为视口变换。
透视除法是将4D裁剪空间坐标变换为3D标准化设备坐标的过程
[6]屏幕空间
该空间可以简单地理解为应用窗口,投影平面上的东西和窗口上的像素通过一一对应的方式映射到窗口,在窗口上显示。对于WebGL需要调用相应的API来将裁剪空间转换到屏幕空间,具体见下文
2.SVG坐标系统
SVG使用的坐标系统或者说网格系统,和Canvas用的差不多(所有计算机绘图都差不多)。这种坐标系统是:以页面的左上角为(0,0)坐标点,坐标以像素为单位,x轴正方向是向右,y轴正方向是向下。(同cesium中的屏幕坐标系)
示例:
<rect x="0" y="0" width="100" height="100" />
定义一个矩形,即从左上角开始,向右延展100px,向下延展100px,形成一个100*100大的矩形
3.Canvas坐标系统
以屏幕左上角未原点,向右为x,下为y,单位为px
注意:Canvas 元素上的 width 和 height 属性不等同于 Canvas 元素的 CSS 样式的属性,Canvas 元素上的 width 和 height是其画布的大小。
4.WebGL坐标系统
WebGL使用的是正交右手坐标系 x , y , x x,y,x x,y,x的分量为 [ − 1 , 1 ] [-1,1] [−1,1],因为使用的是canvas画布,他默认是根据画布的大小来分配至 [ 0 , 1 ] [0,1] [0,1]中
注意:WebGL的顶点着色器只能将数值坐标转化到裁剪空间中,WebGL下面的坐标也就是器裁剪空间中的坐标,无论你的画布有多大,裁剪空间的坐标范围永远是 -1 到 1 。
而要将WebGL的裁剪空间转化到屏幕/像素空间,也是转化到和canvas一样的坐标系中,可以使用WebGLContent.viewport,下面为举例对比说明
使用gl.viewport()与不使用gl.viewport()对比
不使用:(默认在canvas的坐标系下建立webgl坐标系,给分量的取值范围为0~1)
使用后:
5.Canvas的颜色缓冲区大小和样式大小
每个Canvas画布都有两个大小,而这两个大小对于Canvas、WebGL绘图系统的都有所影响。这两个大小分别为颜色缓冲区drawingbuffer大小(表示该画布显示的像素的多少,是canvas自带的属性,可以理解为实际的canvas画布的尺寸)和Canvas显示尺寸(通过CSS设定的大小)大小
如下示例的画布的显示尺寸是 400x300,颜色缓冲区drawingbuffer 是10x15
<canvas id="c" width="10" height="15" style="width: 400px; height: 300px;"></canvas>
然后再该画布下使用webgl绘制了一个宽度为1px的线,其结果如下
该图的Canvas尺寸大小为10x15而,而其CSS显示尺寸大小为300x400那么绘图结果是再原来10x15的画布大小中重采样(拉伸)到了300x400的画布中
那么如何解决这两个尺寸不同的问题?
可以通过HTML元素的clientWidth、clientHeight属性获取canvas画布的显示尺寸,然后将其赋值给颜色缓冲区尺寸大小即可
function resize(canvas) {
// 获取浏览器中画布的显示尺寸
var displayWidth = canvas.clientWidth;
var displayHeight = canvas.clientHeight;
// 检尺寸是否相同
if (canvas.width != displayWidth ||
canvas.height != displayHeight) {
// 设置为相同的尺寸
canvas.width = displayWidth;
canvas.height = displayHeight;
}
}
将其封装为一个函数,这样可以再webgl绘制动画时调用改函数动态的修正,通常在重新调用改函数时还需调用gl.viewport()来将webgl裁剪空间转为像素/屏幕坐标
还有用了上面的代码,就给canvas设置元素高宽了,直接给它的样式设定即可
<canvas id="canvas"></canvas>
<style>
/* 移除边界 */
body {
border: 0;
background-color: white;
}
/* 设置欢度大小为视域大小 */
canvas {
width: 100vw;
height: 100vh;
display: block;
}
</style>
参考:
- https://mp.weixin.qq.com/s?__biz=MzIxMzY1OTQxOQ==&mid=2247483800&idx=1&sn=d0e1ce2f5d7dcab439e915ec0465fd6a&chksm=97b23beca0c5b2fa90e23e4583d922c7be1e4d6d8ed4bfcbd173472e37808275bce7b9313d46&scene=178&cur_album_id=1341289715194036227#rd
- https://webglfundamentals.org/webgl/lessons/zh_cn/