WebGL基础教程之 三维透视投影
本教程转载自 https://webglfundamentals.org/webgl/lessons/zh_cn/webgl-3d-perspective.html
此文上接WebGL系列文章,从基础概念开始, 上一篇是三维的基础内容,如果没读过请从那里开始。
上一篇文章讲述了如何实现三维,那个三维用的不是透视投影, 而是的所谓的“正射”投影,但那不是我们日常观看三维的方式。
我们应使用透视投影代替它,但什么是透视投影?它的基础特性就是离得越远显得越小。
在上方的示例中,远处的物体会变小,想要实现例子中近大远小的效果, 简单的做法就是将裁减空间中的 X 和 Y 值除以 Z 值。
你可以这么想:如果一个线段是 (10, 15) 到 (20,15), 它长度为十个单位,在当前的代码中它就是 10 个像素长, 但是如果我们将它除以 Z ,且 Z 值 为 1
10 / 1 = 10
20 / 1 = 20
abs(10-20) = 10
它将是 10 个像素长,如果 Z 值为 2
10 / 2 = 5
20 / 2 = 10
abs(5 - 10) = 5
就是 5 像素了,当 Z 值为 3 时
10 / 3 = 3.333
20 / 3 = 6.666
abs(3.333 - 6.666) = 3.333
你可以看出随着 Z 变大距离就变远了,画的也会小一点。如果我们除以裁剪空间中的 Z ,值可能会变大,因为 Z 是一个较小的值(-1 到 +1)。但是我们可以提供一个 fudgeFactor 因子和 Z 相乘,这样就可以调整缩放的程度。
让我们来试试,首先修改顶点着色器,除以 Z 再乘以我们的 "fudgeFactor" 因子。
<script id="3d-vertex-shader" type="x-shader/x-vertex">
...
uniform float u_fudgeFactor;
...
void main() {// 将位置和矩阵相乘vec4 position = u_matrix * a_position;// 调整除数float zToDivideBy = 1.0 + position.z * u_fudgeFactor;// x 和 y 除以调整后的除数gl_Position = vec4(position.xy / zToDivideBy, position.zw);
}
</script>
注意,由于裁减空间中的 Z 值是 -1 到 +1 的,所以 +1 是为了让 zToDivideBy
变成 0 到 +2 * fudgeFactor
还需要更新代码以设置 fudgeFactor。
...var fudgeLocation = gl.getUniformLocation(program, "u_fudgeFactor");...var fudgeFactor = 1;...function drawScene() {...// 设置 fudgeFactorgl.uniform1f(fudgeLocation, fudgeFactor);// 绘制几何体var primitiveType = gl.TRIANGLES;var offset = 0;var count = 16 * 6;gl.drawArrays(primitiveType, offset, count);
这是结果(https://webglfundamentals.org/webgl/webgl-3d-perspective.html)。
如果效果不明显,可以将 "fudgeFactor" 滑块从 1.0 拖到 0.0 来对比没添加这些代码之前的样子。
事实上WebGL会将我们提供给 gl_Position
的 x,y,z,w 值自动除以 w 。
我们可以通过修改着色器来证明,用 zToDivideBy
代替 gl_Position.w
...
<script id="2d-vertex-shader" type="x-shader/x-vertex">
...
uniform float u_fudgeFactor;
...
void main() {// 将位置和矩阵相乘vec4 position = u_matrix * a_position;// 调整除数float zToDivideBy = 1.0 + position.z * u_fudgeFactor;// 将 x y z 除以 zToDivideBygl_Position = vec4(position.xyz, zToDivideBy);// 传递颜色到给片断着色器v_color = a_color;
}
</script>
看他们多像(https://webglfundamentals.org/webgl/webgl-3d-perspective-w.html)。
为什么WebGL会自动除以 W ?因为使用矩阵的魔力,可以用把值从 z 传值到 w 。
一个这样的矩阵
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 1,
0, 0, 0, 0,
将会把 z 的值复制给 w , 你可以把每列看作
x_out = x_in * 1 +y_in * 0 +z_in * 0 +w_in * 0 ;y_out = x_in * 0 +y_in * 1 +z_in * 0 +w_in * 0 ;z_out = x_in * 0 +y_in * 0 +z_in * 1 +w_in * 0 ;w_out = x_in * 0 +y_in * 0 +z_in * 1 +w_in * 0 ;
简化后得到
x_out = x_in;
y_out = y_in;
z_out = z_in;
w_out = z_in;
如果 w 原来就是 1.0 就会加 1
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 1,
0, 0, 0, 1,
他会将 W 的运算变为
w_out = x_in * 0 +y_in * 0 +z_in * 1 +w_in * 1 ;
因为 w_in
= 1.0 是已知的
w_out = z_in + 1;
最后可以将 fudgeFactor 像这样放入矩阵中
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, fudgeFactor,
0, 0, 0, 1,
相当于
w_out = x_in * 0 +y_in * 0 +z_in * fudgeFactor +w_in * 1 ;
简化后为
w_out = z_in * fudgeFactor + 1;
我们来修改代码,使用这个矩阵。
首先将顶点着色器还原,又变成简单的样子
<script id="2d-vertex-shader" type="x-shader/x-vertex">
uniform mat4 u_matrix;void main() {// 位置和矩阵相乘gl_Position = u_matrix * a_position;...
}
</script>
接下来定义一个方法实现 Z → W 的矩阵。
function makeZToWMatrix(fudgeFactor) {return [1, 0, 0, 0,0, 1, 0, 0,0, 0, 1, fudgeFactor,0, 0, 0, 1,];
}
然后使用它:
...// 计算矩阵var matrix = makeZToWMatrix(fudgeFactor);matrix = m4.multiply(matrix, m4.projection(gl.canvas.clientWidth, gl.canvas.clientHeight, 400));matrix = m4.translate(matrix, translation[0], translation[1], translation[2]);matrix = m4.xRotate(matrix, rotation[0]);matrix = m4.yRotate(matrix, rotation[1]);matrix = m4.zRotate(matrix, rotation[2]);matrix = m4.scale(matrix, scale[0], scale[1], scale[2]);...
和之前的很像(https://webglfundamentals.org/webgl/webgl-3d-perspective-w-matrix.html):
这只是展示了除以 Z 值获可以实现透视投影,以及在WebGL中简单实现。但还有一些问题需要解决,比如将 Z 值设置为 -100 左右的时候会遇到下面的情形:
为什么会这样?为什么 F 提前消失了?WebGL裁剪空间中的 X 和 Y 会被 +1 和 -1 裁剪, Z也一样。我们看到的是 Z < -1 的情况。
我可以从数学方法深入探讨并寻找解决办法,但是你可以 联想 二维中的的解决方法。我们需要获取 Z 值,然后加上一些量, 缩放一些量,就可以将任意范围映射到 -1 到 +1 的范围内。
最有意思的是这件事可以在一个矩阵中完成,更方便的是, 我们可以定义一个 fieldOfView
代替 fudgeFactor
, 计算出更合适的值。
这是创建矩阵的方法。
ar m4 = {perspective: function(fieldOfViewInRadians, aspect, near, far) {var f = Math.tan(Math.PI * 0.5 - 0.5 * fieldOfViewInRadians);var rangeInv = 1.0 / (near - far);return [f / aspect, 0, 0, 0,0, f, 0, 0,0, 0, (near + far) * rangeInv, -1,0, 0, near * far * rangeInv * 2, 0];},...
这个矩阵会为我们完成所有转换。它可以调整单位以适应裁剪空间, 它可以自定义视场角,选择 Z-裁剪面。假设有一个眼睛或者摄像机 在原点(0, 0, 0),根据 zNear
和 fieldOfView
可以将 zNear
对应到 Z = -1
,在 zNear
平面上一半的 fieldOfView
长度 对应画布中心到 Y = -1
或 Y = 1
的距离,X 的值通过乘以aspect
获取,最后通过设置 zFar 对应 Z = 1
,控制缩放的程度。
这是矩阵的图解(https://webglfundamentals.org/webgl/frustum-diagram.html):
正方体所在的有四个侧面的椎体叫做“视锥”,矩阵将视锥中的空间转换到裁剪空间中, zNear
决定了被正面切割的位置,zFar
决定被背面切割的位置。 将 zNear
设置为 23 就会看到正方体正面被切割, 将 zFar
设置为 24 就会看到正方体背面被切割。
还有一个问题,矩阵假定观察位置为 0,0,0 并且看向 Z 轴负方向, Y 轴为上方向。这和我们目前为止做法不同, 为了解决这个问题我们需要将物体放到视图范围内。
我们在 (45, 150, 0) 绘制的 F,可以将它移动到 (-150, 0, -360)
使用 m4.projection
方法代替之前的投影方法,可以调用 m4.perspective
var aspect = gl.canvas.clientWidth / gl.canvas.clientHeight;
var zNear = 1;
var zFar = 2000;
var matrix = m4.perspective(fieldOfViewRadians, aspect, zNear, zFar);
matrix = m4.translate(matrix, translation[0], translation[1], translation[2]);
matrix = m4.xRotate(matrix, rotation[0]);
matrix = m4.yRotate(matrix, rotation[1]);
matrix = m4.zRotate(matrix, rotation[2]);
matrix = m4.scale(matrix, scale[0], scale[1], scale[2]);
结果在这里(https://webglfundamentals.org/webgl/webgl-3d-perspective-matrix.html):
我们讲了矩阵乘法,视角和自定义 Z 范围。还有很多没讲完, 但这篇文章已经很长了,所以接下来继续讲相机。
前端qq群:850038125
前端微信群:
10.WebGL 三维正射投影
9.WebGL 理论基础 - 二维矩阵
8.WebGL 理论基础 - 二维缩放
7.WebGL 理论基础 - 二维旋转
6.WebGL 理论基础 - 二维平移
5.WebGL 理论基础 - 图像处理 下
4.WebGL 理论基础 - 图像处理 上
3.WebGL 理论基础 - 着色器和 GLSL
2.WebGL 理论基础 - 工作原理
1.WebGL 理论基础 - 基础概念
WebGL基础教程之 三维透视投影相关推荐
- 【GDAL基础教程】多张二维tif数据转三维tif数据
[GDAL基础教程]多张二维tif数据转三维tif数据 今天分享一下多张二维单波段tif数据合并为一张三维多波段tif数据的脚本,话不多说,详见代码. 原数据 # -*- encoding: utf- ...
- ArcGIS二次开发基础教程(10):三维分析
ArcGIS二次开发基础教程(10):三维分析 坡度分析 请务必学会使用帮助文档!!! //DEM数据的坡度分析 将分析结果添加到地图上 //首先获取DEM数据,方法有很多例如从个人地理数据库获取,也 ...
- (转)OpenLayers3基础教程——OL3基本概念
http://blog.csdn.net/gisshixisheng/article/details/46756275 OpenLayers3基础教程--OL3基本概念 从本节开始,我会陆陆续续的更新 ...
- OpenLayers3基础教程——OL3基本概念
从本节开始,我会陆陆续续的更新有关OL3的相关文章--OpenLayers3基础教程,欢迎大家关注我的博客,同时也希望我的博客能够给大家带来一点帮助. 概述: OpenLayers 3对OpenLay ...
- 人机交互基础教程-复习总结
人机交互基础教程 题型分布 考纲及重点 第 1 章 绪论 1.1 什么是人机交互 1.2 人机交互的研究内容(7个) 1.3 人机交互的发展历史(3阶段) 1.4 人机交互的应用 习题 第 2 章 感 ...
- Babylonjs 基础教程与填坑④sandbox+inspector面板中的Mesh类
目录 inspector基本操作 1.在场景想要通过鼠标点击选中模型 2.鼠标点击场景内的小桌子,选中模型后,可以通过左边的眼睛开关,确认是否选择正确. 3.inspector左侧面板的上方的可以单选 ...
- WebGL简易教程(十四):阴影
文章目录 1. 概述 2. 示例 2.1. 着色器部分 2.1.1. 帧缓存着色器 2.1.2. 颜色缓存着色器 2.2. 绘制部分 2.2.1. 整体结构 2.2.2. 具体改动 2.2.2.1. ...
- three.js基础教程学习之camera的理解
最近学习了three.js基础教程,由于WebGL中文网中对camera的解说不够详细,自己又找了几篇博文看,以下是我对camera的理解,有错误的地方希望得到大佬们的指出,同时也希望能帮到像我一样的 ...
- python 科学计算基础教程电子版-Python 科学计算基础 (整理)
Python是一种面向对象的.动态的程序设计语言,具有非常简洁而清晰的语法,既可以用于快速开发程序脚本,也可以用于开发大规模的软件,特别适合于完成各种高层任务. 随着NumPy.SciPy.matpl ...
最新文章
- 在word、excel中如果运用VBA进行编程?
- CC2530的串口实验
- 解决 iOS 11 webview 顶部空白条的问题
- 进制转换,字符串,字节串之间转换
- 给部署在openshift上的WordPress添加wptouch插件
- %@taglib prefix=c uri=http://java.sun.com/jsp/jst1/core%报错
- js传参不是数字_js调用函数时传入的参数个数与函数定义时的参数个数不符时的操作...
- 当大数据遇上“智慧园区”会怎样?
- python中config命令_python的logging.config使用详解
- “干掉”程序员饭碗后,OpenAI 又对艺术家下手了!
- python有三个包如何只导入两个包_云计算开发学习笔记:Python3如何从一个包中导入*...
- 人人都能学会的python编程教程12:函数的参数
- java 获取文件的全路径
- 你不知道网络安全有多严峻
- QT MD4 MD5 Sha1等几种加密方式
- 计算机课ppt免费,第1课 认识计算机ppt课件.ppt
- mac foxmail html签名,Foxmail怎么设置签名?Foxmail个性签名设置方法
- Python 第二章 字典
- 汇编 bne 1b和bne 1f浅析
- 广度优先搜索——动态类迷宫问题