Open forthealllight opened 4 years ago
在前一章中我们介绍了三维图形基础,提到了要想在画布中渲染出三维图形,必须同时确定视图矩阵和投影矩阵,这章我们来详细的介绍一下什么是投影模型。
webgl中的坐标系统 什么是投影 正射投影 透视投影
这个系列的源码地址为:源码的地址为: https://github.com/forthealllight/webgl-demo
在了解投影之前,我们现来看一下webgl中的坐标系统,在webgl中有两个坐标系统,一个是裁剪面坐标,另一个是纹理坐标。其中跟旋转变换以及图形渲染可视有关的是裁剪面坐标,这里我们先介绍webgl中的裁剪面坐标,而纹理坐标会在纹理贴图章节介绍。
webgl中的裁剪面坐标如下所示:
从上述裁剪坐标系统的示意图中我们可以看出,webgl中整个裁剪平面的中心坐标是(0,0),对于二维的裁剪平面而言其水平方向从(-1,0)到(1,0),其竖直方向从(0,-1)到(0,1).
也就是说其裁剪坐标任何方向的值在区间[-1,1]内。
注意一点:裁剪平面是决定如何映射到画布上,因为画布是二维的,因此裁剪平面也是二维的。webgl中z轴的坐标并不限制在(-1,1),可以为任何值。
那么画布坐标呢,比如我们以canvans640 X 480大小的画布为例:
画布坐标的原点位于左下角。在webgl中我们推荐对于所有的点使用裁剪面坐标来表示,这样比较方便绘制,如何将裁剪面坐标表示的点展示到画布上呢,可以通过webgl的函数来实现:
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
有些场景比较特殊,我们将在webgl中使用像素坐标(画布坐标系下的坐标),那么需要额外的操作,在顶点着色器中,需要将画布坐标转化为裁剪平面坐标,转化的例子如下,在顶点着色器中我们使用:
uniform vec2 u_resolution; 用于读取画布的坐标,比如(640,480)向量 void main() { // 从像素坐标转换到 0.0 到 1.0 vec2 zeroToOne = a_position / u_resolution; // 再把 0->1 转换 0->2 vec2 zeroToTwo = zeroToOne * 2.0; // 把 0->2 转换到 -1->+1 (裁剪空间) vec2 clipSpace = zeroToTwo - 1.0; gl_Position = vec4(clipSpace, 0, 1); }
这样,我们在其他webgl的代码中就可以使用像素坐标,下面是一个使用像素坐标来绘制一个三角形的例子:
以640 * 480 的canvas画布为例,像素坐标系下的点:
positions = [ 160, 120, 320, 360, 480, 120, ]
和裁剪平面下的坐标点:
positions = [ 0.0,0.5, -0.5,-0.5, 0.5,-0.5 ]
这两组坐标是等价的。具体例子可见:
https://github.com/forthealllight/webgl-demo/tree/master/demo7
在第三章我们将到了视图矩阵,得出一个公式就是:
观测到的经过矩阵变换三维图形的坐标 = <视图矩阵> x <模型矩阵> 图形的原始坐标
这个模型下,我们认为无论观测者位于哪里,都可以看到被观测的物体,我们简单的认为可视空间是无限大的。显然这样是不对的,因为在现实生活中,在北京的人不可能可以观测到上海的物体。限定可视区的模型就是投影。
投影有两种,一种是正射投影,也就是说在在投影范围内,看到物体是等大小的。另一种是透视投影,遵循近大远小的规则。现实生活中,透视投影比较贴近。
有些时候我们可能搞混淆了视图矩阵和投影模型矩阵,其实有一句诗可以帮助我们理解:
横看成岭侧成峰,远近高低各不同
前半句就是视点不同,从而导致视图矩阵不同得到不同的观测结果,后半句就是更多侧重于投影规则引起的近大远小。
引入投影模型后,完整的图形渲染公式应该是:
观测到的经过矩阵变换三维图形的坐标 = <投影矩阵> x <视图矩阵> x <模型矩阵> 图形的原始坐标
下节我们会详细的介绍正射投影和透视投影。
正射投影就是可视区近处和远处看到的物体是等大小的。由正射投影模型产生的可视区称为盒状可视区,盒状可视区模型如下:
盒装可视区就是一个三维长方体,近裁剪面就是能看到的最近的z轴距离,远裁剪面就是能看到的最远的z轴的距离,在远近裁剪面之间的三维长方体就是可视区。
我们从模型中可以看出,这个正射投影矩阵或者说可视矩阵由6个坐标来决定,分别是near,top,left,right,bottom,far。
完整的三维图形观测渲染模型为:
观测到的经过矩阵变换三维图形的坐标 = <正射投影矩阵> x <视图矩阵> x <模型矩阵> 图形的原始坐标
下面我们来绘制一个在正射投影模型下的三维立方体:
const zNear = 3; const zFar = 100.0; const projectionMatrix = mat4.create(); mat4.ortho(projectionMatrix, -1, 1, -1, 1, zNear, zFar);
上述的代码就是构建了<正射投影矩阵> x <视图矩阵>的过程。具体的例子可以看:
https://github.com/forthealllight/webgl-demo/tree/master/demo8
绘制的图案如图所示:
这个正方体太大了,并且因为是正射投影的原因,因此只能在画布中看到正方体的一部分。
透视投影模型就是复合我们常识的近大远小的观测模型,我们来看透视模型的示意图:
从上述的透视投影模型来看,它的可视区不是一个长方体,而是一个棱柱,是底面是大小不想等的长方形,在透视投影模型下的可视区又称为金字塔可视空间。
我们同样的在金字塔可视空间内绘制同样的一个正方体,修改代码使其变成金字塔模型
const fieldOfView = 45 * Math.PI / 180; // in radians const aspect = gl.canvas.clientWidth / gl.canvas.clientHeight; const zNear = 3; const zFar = 100.0; const projectionMatrix = mat4.create(); mat4.perspective(projectionMatrix, fieldOfView, aspect, zNear, zFar);
最后的绘制结果为:
显然这是不符合我们预期的,我们立方体的8个面分别有8个颜色,从上述的例子中我们不仅可以看到正面,还能看到背面的正方形,这显然是不符合人眼观测的,为了消除这种异常,我们要了解一下深度缓存。
我们前面降到在裁剪平面中的xy值的范围是-1到1,而对于z轴不做处理,或者没有值的范围。这句话说对也对,说不对也不对。 诚然裁剪面内不会对z轴做任何处理,但是在webgl的深度缓存中会自动对z的深度做处理,将z值转化到0->1。那么这种自动对于深度的处理如何转化到裁剪平面,从而影响绘制结果呢,让我们只能看到正面而看不到背面。
答案就是,在深度缓存中支持深度检测,当检测到在同样位置第二次绘制的图形的深度小于第一次,那么就不会绘制,如何第二次绘制图形的深度大于第一次,那么会重新绘制并覆盖第一次。
这里的深度其实要加引号,深度越大,其实是离观察者越近,因此深度大的优先显示。
开启深度检测也很简单,只需要:
//开启深度检测 gl.enable(gl.DEPTH_TEST); //在绘制前清空深度缓存 gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
进行深度检测后绘制的立方体的图形如下所示:
绘制出上述的正方体后,我们还是没能体现出透视投影近大远小的成像原理,我们可以尝试平移我们的视点位置,在z轴方向平移,
平移前:
mat4.translate(modelViewMatrix, modelViewMatrix, [-0.0, 0.0, -6.0]);
平移后:
mat4.translate(modelViewMatrix, modelViewMatrix, [-0.0, 0.0, -16.0]);
视点的位置从(0,0,-6)平移到了(0,0,-16),平移后我们来看渲染结果,负像的位移是使得视点远离物体的,最后的渲染结果为:
从直观上来看,正方体变小了,这就是我们所说的在透视投影模型中的近大远小。
本例的地址为:https://github.com/forthealllight/webgl-demo/tree/master/demo9
webgl + 图形学发展很大啊
hah是的,蛮好玩的
我也想往这方面靠靠,做做游戏 flutter 引擎啥的,加油
哈哈 一起加油
优雅的学习webgl(4)—webgl中的投影模型
在前一章中我们介绍了三维图形基础,提到了要想在画布中渲染出三维图形,必须同时确定视图矩阵和投影矩阵,这章我们来详细的介绍一下什么是投影模型。
这个系列的源码地址为:源码的地址为: https://github.com/forthealllight/webgl-demo
一、webgl中的坐标系统
在了解投影之前,我们现来看一下webgl中的坐标系统,在webgl中有两个坐标系统,一个是裁剪面坐标,另一个是纹理坐标。其中跟旋转变换以及图形渲染可视有关的是裁剪面坐标,这里我们先介绍webgl中的裁剪面坐标,而纹理坐标会在纹理贴图章节介绍。
webgl中的裁剪面坐标如下所示:
从上述裁剪坐标系统的示意图中我们可以看出,webgl中整个裁剪平面的中心坐标是(0,0),对于二维的裁剪平面而言其水平方向从(-1,0)到(1,0),其竖直方向从(0,-1)到(0,1).
也就是说其裁剪坐标任何方向的值在区间[-1,1]内。
注意一点:裁剪平面是决定如何映射到画布上,因为画布是二维的,因此裁剪平面也是二维的。webgl中z轴的坐标并不限制在(-1,1),可以为任何值。
那么画布坐标呢,比如我们以canvans640 X 480大小的画布为例:
画布坐标的原点位于左下角。在webgl中我们推荐对于所有的点使用裁剪面坐标来表示,这样比较方便绘制,如何将裁剪面坐标表示的点展示到画布上呢,可以通过webgl的函数来实现:
有些场景比较特殊,我们将在webgl中使用像素坐标(画布坐标系下的坐标),那么需要额外的操作,在顶点着色器中,需要将画布坐标转化为裁剪平面坐标,转化的例子如下,在顶点着色器中我们使用:
这样,我们在其他webgl的代码中就可以使用像素坐标,下面是一个使用像素坐标来绘制一个三角形的例子:
以640 * 480 的canvas画布为例,像素坐标系下的点:
和裁剪平面下的坐标点:
这两组坐标是等价的。具体例子可见:
https://github.com/forthealllight/webgl-demo/tree/master/demo7
二、什么是投影
在第三章我们将到了视图矩阵,得出一个公式就是:
观测到的经过矩阵变换三维图形的坐标 = <视图矩阵> x <模型矩阵> 图形的原始坐标
这个模型下,我们认为无论观测者位于哪里,都可以看到被观测的物体,我们简单的认为可视空间是无限大的。显然这样是不对的,因为在现实生活中,在北京的人不可能可以观测到上海的物体。限定可视区的模型就是投影。
投影有两种,一种是正射投影,也就是说在在投影范围内,看到物体是等大小的。另一种是透视投影,遵循近大远小的规则。现实生活中,透视投影比较贴近。
有些时候我们可能搞混淆了视图矩阵和投影模型矩阵,其实有一句诗可以帮助我们理解:
横看成岭侧成峰,远近高低各不同
前半句就是视点不同,从而导致视图矩阵不同得到不同的观测结果,后半句就是更多侧重于投影规则引起的近大远小。
引入投影模型后,完整的图形渲染公式应该是:
观测到的经过矩阵变换三维图形的坐标 = <投影矩阵> x <视图矩阵> x <模型矩阵> 图形的原始坐标
下节我们会详细的介绍正射投影和透视投影。
三、正射投影
正射投影就是可视区近处和远处看到的物体是等大小的。由正射投影模型产生的可视区称为盒状可视区,盒状可视区模型如下:
盒装可视区就是一个三维长方体,近裁剪面就是能看到的最近的z轴距离,远裁剪面就是能看到的最远的z轴的距离,在远近裁剪面之间的三维长方体就是可视区。
我们从模型中可以看出,这个正射投影矩阵或者说可视矩阵由6个坐标来决定,分别是near,top,left,right,bottom,far。
完整的三维图形观测渲染模型为:
观测到的经过矩阵变换三维图形的坐标 = <正射投影矩阵> x <视图矩阵> x <模型矩阵> 图形的原始坐标
下面我们来绘制一个在正射投影模型下的三维立方体:
上述的代码就是构建了<正射投影矩阵> x <视图矩阵>的过程。具体的例子可以看:
https://github.com/forthealllight/webgl-demo/tree/master/demo8
绘制的图案如图所示:
这个正方体太大了,并且因为是正射投影的原因,因此只能在画布中看到正方体的一部分。
四、透视投影
透视投影模型就是复合我们常识的近大远小的观测模型,我们来看透视模型的示意图:
从上述的透视投影模型来看,它的可视区不是一个长方体,而是一个棱柱,是底面是大小不想等的长方形,在透视投影模型下的可视区又称为金字塔可视空间。
我们同样的在金字塔可视空间内绘制同样的一个正方体,修改代码使其变成金字塔模型
最后的绘制结果为:
显然这是不符合我们预期的,我们立方体的8个面分别有8个颜色,从上述的例子中我们不仅可以看到正面,还能看到背面的正方形,这显然是不符合人眼观测的,为了消除这种异常,我们要了解一下深度缓存。
我们前面降到在裁剪平面中的xy值的范围是-1到1,而对于z轴不做处理,或者没有值的范围。这句话说对也对,说不对也不对。 诚然裁剪面内不会对z轴做任何处理,但是在webgl的深度缓存中会自动对z的深度做处理,将z值转化到0->1。那么这种自动对于深度的处理如何转化到裁剪平面,从而影响绘制结果呢,让我们只能看到正面而看不到背面。
答案就是,在深度缓存中支持深度检测,当检测到在同样位置第二次绘制的图形的深度小于第一次,那么就不会绘制,如何第二次绘制图形的深度大于第一次,那么会重新绘制并覆盖第一次。
这里的深度其实要加引号,深度越大,其实是离观察者越近,因此深度大的优先显示。
开启深度检测也很简单,只需要:
进行深度检测后绘制的立方体的图形如下所示:
绘制出上述的正方体后,我们还是没能体现出透视投影近大远小的成像原理,我们可以尝试平移我们的视点位置,在z轴方向平移,
平移前:
平移后:
视点的位置从(0,0,-6)平移到了(0,0,-16),平移后我们来看渲染结果,负像的位移是使得视点远离物体的,最后的渲染结果为:
从直观上来看,正方体变小了,这就是我们所说的在透视投影模型中的近大远小。
本例的地址为:https://github.com/forthealllight/webgl-demo/tree/master/demo9