本教程转载自 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基础教程之 三维透视投影相关推荐

  1. 【GDAL基础教程】多张二维tif数据转三维tif数据

    [GDAL基础教程]多张二维tif数据转三维tif数据 今天分享一下多张二维单波段tif数据合并为一张三维多波段tif数据的脚本,话不多说,详见代码. 原数据 # -*- encoding: utf- ...

  2. ArcGIS二次开发基础教程(10):三维分析

    ArcGIS二次开发基础教程(10):三维分析 坡度分析 请务必学会使用帮助文档!!! //DEM数据的坡度分析 将分析结果添加到地图上 //首先获取DEM数据,方法有很多例如从个人地理数据库获取,也 ...

  3. (转)OpenLayers3基础教程——OL3基本概念

    http://blog.csdn.net/gisshixisheng/article/details/46756275 OpenLayers3基础教程--OL3基本概念 从本节开始,我会陆陆续续的更新 ...

  4. OpenLayers3基础教程——OL3基本概念

    从本节开始,我会陆陆续续的更新有关OL3的相关文章--OpenLayers3基础教程,欢迎大家关注我的博客,同时也希望我的博客能够给大家带来一点帮助. 概述: OpenLayers 3对OpenLay ...

  5. 人机交互基础教程-复习总结

    人机交互基础教程 题型分布 考纲及重点 第 1 章 绪论 1.1 什么是人机交互 1.2 人机交互的研究内容(7个) 1.3 人机交互的发展历史(3阶段) 1.4 人机交互的应用 习题 第 2 章 感 ...

  6. Babylonjs 基础教程与填坑④sandbox+inspector面板中的Mesh类

    目录 inspector基本操作 1.在场景想要通过鼠标点击选中模型 2.鼠标点击场景内的小桌子,选中模型后,可以通过左边的眼睛开关,确认是否选择正确. 3.inspector左侧面板的上方的可以单选 ...

  7. WebGL简易教程(十四):阴影

    文章目录 1. 概述 2. 示例 2.1. 着色器部分 2.1.1. 帧缓存着色器 2.1.2. 颜色缓存着色器 2.2. 绘制部分 2.2.1. 整体结构 2.2.2. 具体改动 2.2.2.1. ...

  8. three.js基础教程学习之camera的理解

    最近学习了three.js基础教程,由于WebGL中文网中对camera的解说不够详细,自己又找了几篇博文看,以下是我对camera的理解,有错误的地方希望得到大佬们的指出,同时也希望能帮到像我一样的 ...

  9. python 科学计算基础教程电子版-Python 科学计算基础 (整理)

    Python是一种面向对象的.动态的程序设计语言,具有非常简洁而清晰的语法,既可以用于快速开发程序脚本,也可以用于开发大规模的软件,特别适合于完成各种高层任务. 随着NumPy.SciPy.matpl ...

最新文章

  1. 在word、excel中如果运用VBA进行编程?
  2. CC2530的串口实验
  3. 解决 iOS 11 webview 顶部空白条的问题
  4. 进制转换,字符串,字节串之间转换
  5. 给部署在openshift上的WordPress添加wptouch插件
  6. %@taglib prefix=c uri=http://java.sun.com/jsp/jst1/core%报错
  7. js传参不是数字_js调用函数时传入的参数个数与函数定义时的参数个数不符时的操作...
  8. 当大数据遇上“智慧园区”会怎样?
  9. python中config命令_python的logging.config使用详解
  10. “干掉”程序员饭碗后,OpenAI 又对艺术家下手了!
  11. python有三个包如何只导入两个包_云计算开发学习笔记:Python3如何从一个包中导入*...
  12. 人人都能学会的python编程教程12:函数的参数
  13. java 获取文件的全路径
  14. 你不知道网络安全有多严峻
  15. QT MD4 MD5 Sha1等几种加密方式
  16. 计算机课ppt免费,第1课 认识计算机ppt课件.ppt
  17. mac foxmail html签名,Foxmail怎么设置签名?Foxmail个性签名设置方法
  18. Python 第二章 字典
  19. 汇编 bne 1b和bne 1f浅析
  20. 广度优先搜索——动态类迷宫问题

热门文章

  1. 美国数据经纪商监管制度对我国数据服务业发展的启示
  2. BL55077 驱动程序
  3. Android端MVVM从入门到实战(第四篇) - DataBinding运算符
  4. Got timeout reading communication packets解决方法
  5. 聚星Note01 - 后台管理环境搭建(1)
  6. clangllvm简介
  7. 零跑C11纯电动汽车的典范
  8. 开源一款监控数据采集器,啥都能监控
  9. 初学者指南端到端测试
  10. java中snakeyaml工具包操作yaml文件,什么是yaml文件,yaml如何解析转换为实体,实体如何生成yaml文件