试图理解WebGL中透视矩阵背后的数学

Har*_*San 10 math opengl-es matrix perspectivecamera webgl

WebGL的所有矩阵库都具有某种perspective功能,您可以调用它来获取场景的透视矩阵.
例如,文件中的perspective方法是如此编码:mat4.jsgl-matrix

mat4.perspective = function (out, fovy, aspect, near, far) {
    var f = 1.0 / Math.tan(fovy / 2),
        nf = 1 / (near - far);
    out[0] = f / aspect;
    out[1] = 0;
    out[2] = 0;
    out[3] = 0;
    out[4] = 0;
    out[5] = f;
    out[6] = 0;
    out[7] = 0;
    out[8] = 0;
    out[9] = 0;
    out[10] = (far + near) * nf;
    out[11] = -1;
    out[12] = 0;
    out[13] = 0;
    out[14] = (2 * far * near) * nf;
    out[15] = 0;
    return out;
};
Run Code Online (Sandbox Code Playgroud)

我真的想要了解这种方法中的所有数学实际上是在做什么,但我在几点上绊倒了.

对于初学者来说,如果我们有一个画布如下,宽高比为4:3,那么aspect该方法的参数实际上是4 / 3正确的吗?

4:3宽高比

我也注意到45°似乎是一个普通的视野.如果是这种情况,那么fovy参数将是? / 4弧度,对吗?

尽管如此,f方法的变量是什么,它的目的是什么?
我试图想象实际的场景,我想象如下:

[3D场景中的透视侧视图

这样想着,我能理解为什么你把fovy通过2也为什么你采取比的切线,但为什么被存储在了倒数f?同样,我在理解f真正代表什么方面遇到了很多麻烦.

接下来,我得到的概念nearfar可沿Z轴的削波点,所以这是好的,但如果我使用的数字上面的图片(即? / 4,4 / 3,10100),并插入perspective方法,然后我结束了使用如下矩阵:

在此输入图像描述

在哪里f等于:

在此输入图像描述

所以我留下了以下问题:

  1. 什么是f
  2. 分配给out[10](即110 / -90)的值代表什么?
  3. 什么是-1分配给out[11]吗?
  4. 分配给out[14](即2000 / -90)的值代表什么?

最后,我应该注意到我已经阅读过Gregg Tavares关于透视矩阵的解释,但毕竟,我仍然有同样的困惑.

gma*_*man 15

让我们看看我是否可以解释这一点,或者在阅读完之后你可以想出一个更好的解释方法.

要实现的第一件事是WebGL需要剪辑空间坐标.它们在x,y和z中变为-1 < - > +1.因此,透视矩阵基本上被设计为占据平截头体内的空间并将其转换为剪辑空间.

如果你看这个图

截边

我们知道相切=相对(y)相邻(z),所以如果我们知道z,我们可以计算出坐在平截头体边缘的y给定的fovY.

tan(fovY / 2) = y / -z
Run Code Online (Sandbox Code Playgroud)

将两边乘以-z

y = tan(fovY / 2) * -z
Run Code Online (Sandbox Code Playgroud)

如果我们定义

f = 1 / tan(fovY / 2)
Run Code Online (Sandbox Code Playgroud)

我们得到

y = -z / f
Run Code Online (Sandbox Code Playgroud)

请注意,我们还没有完成从摄像机空间到剪辑空间的转换.我们所做的就是在摄像机空间中给定z的视场边缘计算y.视野的边缘也是剪辑空间的边缘.由于剪辑空间只是+1到-1,我们可以将相机空间y除以-z / f得到剪辑空间.

那有意义吗?再看一下图表.让我们假设蓝色z为-5,并且对于某些给定的视野y出现了+2.34.我们需要转换+2.34为+1 剪辑空间.通用版本是

clipY = cameraY*f/-z

看看'makePerspective'

function makePerspective(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
  ];
};
Run Code Online (Sandbox Code Playgroud)

f在这种情况下我们可以看到

tan(Math.PI * 0.5 - 0.5 * fovY)
Run Code Online (Sandbox Code Playgroud)

这实际上是一样的

1 / tan(fovY / 2)
Run Code Online (Sandbox Code Playgroud)

为什么这样写?我猜是因为如果你有第一种风格而且棕褐色出现在0你会除以0你的程序会崩溃在哪里如果你这样做就没有分裂所以没有机会除以零.

看到这-1matrix[11]现场意味着什么时候我们都完成了

matrix[5]  = tan(Math.PI * 0.5 - 0.5 * fovY)
matrix[11] = -1

clipY = cameraY * matrix[5] / cameraZ * matrix[11]
Run Code Online (Sandbox Code Playgroud)

因为clipX我们基本上做了完全相同的计算,除了缩放宽高比.

matrix[0]  = tan(Math.PI * 0.5 - 0.5 * fovY) / aspect
matrix[11] = -1

clipX = cameraX * matrix[0] / cameraZ * matrix[11]
Run Code Online (Sandbox Code Playgroud)

最后,我们必须将-zNear < - > -zFar范围内的cameraZ转换为-1 < - > + 1范围内的clipZ.

标准透视矩阵以相互函数的方式执行此操作,使得关闭相机的z值比远离相机的z值获得更高的分辨率.那个公式是

clipZ = something / cameraZ + constant
Run Code Online (Sandbox Code Playgroud)

让我们使用sfor somethingcfor constant.

clipZ = s / cameraZ + c;
Run Code Online (Sandbox Code Playgroud)

并解决sc.在我们的例子中我们知道

s / -zNear + c = -1
s / -zFar  + c =  1
Run Code Online (Sandbox Code Playgroud)

所以,将`c'移到另一边

s / -zNear = -1 - c
s / -zFar  =  1 - c
Run Code Online (Sandbox Code Playgroud)

乘以-zXXX

s = (-1 - c) * -zNear
s = ( 1 - c) * -zFar
Run Code Online (Sandbox Code Playgroud)

那两件事现在彼此相等

(-1 - c) * -zNear = (1 - c) * -zFar
Run Code Online (Sandbox Code Playgroud)

扩大数量

(-zNear * -1) - (c * -zNear) = (1 * -zFar) - (c * -zFar)
Run Code Online (Sandbox Code Playgroud)

简化

zNear + c * zNear = -zFar + c * zFar
Run Code Online (Sandbox Code Playgroud)

zNear向右移动

c * zNear = -zFar + c * zFar - zNear
Run Code Online (Sandbox Code Playgroud)

c * zFar向左移动

c * zNear - c * zFar = -zFar - zNear
Run Code Online (Sandbox Code Playgroud)

简化

c * (zNear - zFar) = -(zFar + zNear)
Run Code Online (Sandbox Code Playgroud)

被除以 (zNear - zFar)

c = -(zFar + zNear) / (zNear - zFar)
Run Code Online (Sandbox Code Playgroud)

解决 s

s = (1 - -((zFar + zNear) / (zNear - zFar))) * -zFar
Run Code Online (Sandbox Code Playgroud)

简化

s = (1 + ((zFar + zNear) / (zNear - zFar))) * -zFar
Run Code Online (Sandbox Code Playgroud)

改变1(zNear - zFar)

s = ((zNear - zFar + zFar + zNear) / (zNear - zFar)) * -zFar
Run Code Online (Sandbox Code Playgroud)

简化

s = ((2 * zNear) / (zNear - zFar)) * -zFar
Run Code Online (Sandbox Code Playgroud)

简化一些

s = (2 * zNear * zFar) / (zNear - zFar)
Run Code Online (Sandbox Code Playgroud)

我希望stackexchange支持数学,就像他们的数学网站一样:(

所以回到顶部.我们的论坛是

s / cameraZ + c
Run Code Online (Sandbox Code Playgroud)

我们知道sc现在.

clipZ = (2 * zNear * zFar) / (zNear - zFar) / -cameraZ -
        (zFar + zNear) / (zNear - zFar)
Run Code Online (Sandbox Code Playgroud)

让我们把-z移到外面

clipZ = ((2 * zNear * zFar) / zNear - ZFar) +
         (zFar + zNear) / (zNear - zFar) * cameraZ) / -cameraZ
Run Code Online (Sandbox Code Playgroud)

我们可以改变/ (zNear - zFar),以* 1 / (zNear - zFar)使

rangeInv = 1 / (zNear - zFar)
clipZ = ((2 * zNear * zFar) * rangeInv) +
         (zFar + zNear) * rangeInv * cameraZ) / -cameraZ
Run Code Online (Sandbox Code Playgroud)

回头makeFrustum看,我们看到它最终会成功

clipZ = (matrix[10] * cameraZ + matrix[14]) / (cameraZ * matrix[11])
Run Code Online (Sandbox Code Playgroud)

看看上面适合的公式

rangeInv = 1 / (zNear - zFar)
matrix[10] = (zFar + zNear) * rangeInv
matrix[14] = 2 * zNear * zFar * rangeInv
matrix[11] = -1
clipZ = (matrix[10] * cameraZ + matrix[14]) / (cameraZ * matrix[11])
Run Code Online (Sandbox Code Playgroud)

我希望这是有道理的.注意:大部分内容只是我对本文的重写.