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

本文介绍了试图理解 WebGL 中透视矩阵背后的数学原理的处理方法,对大家解决问题具有一定的参考价值

问题描述

WebGL 的所有矩阵库都有某种perspective 函数,您可以调用该函数来获取场景的透视矩阵.
例如,,但在这一切之后,我还是有同样的困惑.

解决方案

让我们看看我能不能解释这个,或者也许你读了这个之后你能想出一个更好的方法来解释它.

首先要意识到的是,WebGL 需要裁剪空间坐标.它们在 x、y 和 z 中变为 -1 <-> +1.因此,透视矩阵基本上设计用于获取视锥体内的空间并将其转换为剪辑空间.

如果你看这张图

我们知道切线 = 在相邻 (z) 上的相反 (y),因此如果我们知道 z,我们就可以计算出对于给定 fovY 位于截锥体边缘的 y.

tan(fovY/2) = y/-z

两边乘以-z

y = tan(fovY/2) * -z

如果我们定义

f = 1/tan(fovY/2)

我们得到

y = -z/f

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

有意义吗?再看图.让我们假设蓝色 z 是 -5 并且对于某些给定的视野 y 出来的是 +2.34.我们需要将 +2.34 转换为 +1 clipspace.通用版本是

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/(近 - 远);返回 [f/纵横比, 0, 0, 0,0, f, 0, 0,0, 0, (near + far) * rangeInv, -1,0, 0, Near * far * rangeInv * 2, 0];};

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

tan(Math.PI * 0.5 - 0.5 * fovY)

其实是一样的

1/tan(fovY/2)

为什么这么写?我猜是因为如果你有第一种风格并且棕褐色变成 0 你会被 0 除你的程序会崩溃,如果你这样做的话,没有除法所以没有机会除以零.

看到 -1matrix[11] 点意味着我们都完成了

matrix[5] = tan(Math.PI * 0.5 - 0.5 * fovY)矩阵[11] = -1剪辑Y = 相机Y * 矩阵[5]/相机Z * 矩阵[11]

对于 clipX,我们基本上做了完全相同的计算,除了针对纵横比进行缩放.

matrix[0] = tan(Math.PI * 0.5 - 0.5 * fovY)/aspect矩阵[11] = -1剪辑X = 相机X * 矩阵[0]/相机Z * 矩阵[11]

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

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

clipZ = something/cameraZ + 常数

让我们用 s 表示 somethingc 表示常量.

clipZ = s/cameraZ + c;

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

s/-zNear + c = -1s/-zFar + c = 1

所以,把‘c’移到另一边

s/-zNear = -1 - cs/-zFar = 1 - c

乘以-zXXX

s = (-1 - c) * -zNears = ( 1 - c) * -zFar

这两件事现在彼此相等

(-1 - c) * -zNear = (1 - c) * -zFar

扩大数量

(-zNear * -1) - (c * -zNear) = (1 * -zFar) - (c * -zFar)

简化

zNear + c * zNear = -zFar + c * zFar

zNear 向右移动

c * zNear = -zFar + c * zFar - zNear

c * zFar 向左移动

c * zNear - c * zFar = -zFar - zNear

简化

c * (zNear - zFar) = -(zFar + zNear)

除以(zNear - zFar)

c = -(zFar + zNear)/(zNear - zFar)

解决s

s = (1 - -((zFar + zNear)/(zNear - zFar))) * -zFar

简化

s = (1 + ((zFar + zNear)/(zNear - zFar))) * -zFar

1改为(zNear - zFar)

s = ((zNear - zFar + zFar + zNear)/(zNear - zFar)) * -zFar

简化

s = ((2 * zNear)/(zNear - zFar)) * -zFar

简化一些

s = (2 * zNear * zFar)/(zNear - zFar)

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

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

s/cameraZ + c

我们现在知道sc.

clipZ = (2 * zNear * zFar)/(zNear - zFar)/-cameraZ -(zFar + zNear)/(zNear - zFar)

让我们把 -z 移到外面

clipZ = ((2 * zNear * zFar)/zNear - ZFar) +(zFar + zNear)/(zNear - zFar) * cameraZ)/-cameraZ

我们可以将 /(zNear - zFar) 改为 * 1/(zNear - zFar) 所以

rangeInv = 1/(zNear - zFar)clipZ = ((2 * zNear * zFar) * rangeInv) +(zFar + zNear) * rangeInv * cameraZ)/-cameraZ

回顾 makeFrustum 我们看到它最终会制作

clipZ = (matrix[10] * cameraZ + matrix[14])/(cameraZ * matrix[11])

看看上面那个适合的公式

rangeInv = 1/(zNear - zFar)矩阵[10] = (zFar + zNear) * rangeInv矩阵[14] = 2 * zNear * zFar * rangeInv矩阵[11] = -1clipZ = (matrix[10] * cameraZ + matrix[14])/(cameraZ * matrix[11])

我希望这是有道理的.注意:其中大部分只是我对 这篇文章.

All matrix libraries for WebGL have some sort of perspective function that you call to get the perspective matrix for the scene.
For example, the perspective method within the mat4.js file that's part of gl-matrix is coded as such:

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;
};

I'm really trying to understand what all the math in this method is actually doing, but I'm tripping up on several points.

For starters, if we have a canvas as follows with an aspect ratio of 4:3, then the aspect parameter of the method would in fact be 4 / 3, correct?

I've also noticed that 45° seems like a common field of view. If that's the case, then the fovy parameter would be π / 4 radians, correct?

With all that said, what is the f variable in the method short for and what is the purpose of it?
I was trying to envision the actual scenario, and I imagined something like the following:

Thinking like this, I can understand why you divide fovy by 2 and also why you take the tangent of that ratio, but why is the inverse of that stored in f? Again, I'm having a lot of trouble understanding what f really represents.

Next, I get the concept of near and far being the clipping points along the z-axis, so that's fine, but if I use the numbers in the picture above (i.e., π / 4, 4 / 3, 10 and 100) and plug them into the perspective method, then I end up with a matrix like the following:

Where f is equal to:

So I'm left with the following questions:

  1. What is f?
  2. What does the value assigned to out[10] (i.e., 110 / -90) represent?
  3. What does the -1 assigned to out[11] do?
  4. What does the value assigned to out[14] (i.e., 2000 / -90) represent?

Lastly, I should note that I have already read Gregg Tavares's explanation on the perspective matrix, but after all of that, I'm left with the same confusion.

解决方案

Let's see if I can explain this, or maybe after reading this you can come up with a better way to explain it.

The first thing to realize is WebGL requires clipspace coordinates. They go -1 <-> +1 in x, y, and z. So, a perspective matrix is basically designed to take the space inside the frustum and convert it to clipspace.

If you look at this diagram

we know that tangent = opposite (y) over adjacent(z) so if we know z we can compute y that would be sitting at the edge of the frustum for a given fovY.

tan(fovY / 2) = y / -z

multiply both sides by -z

y = tan(fovY / 2) * -z

if we define

f = 1 / tan(fovY / 2)

we get

y = -z / f

note we haven't done a conversion from cameraspace to clipspace. All we've done is compute y at the edge of the field of view for a given z in cameraspace. The edge of the field of view is also the edge of clipspace. Since clipspace is just +1 to -1 we can just divide a cameraspace y by -z / f to get clipspace.

Does that make sense? Look at the diagram again. Let's assume that the blue z was -5 and for some given field of view y came out to +2.34. We need to convert +2.34 to +1 clipspace. The generic version of that is

clipY = cameraY * f / -z

Looking at `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
  ];
};

we can see that f in this case

tan(Math.PI * 0.5 - 0.5 * fovY)

which is actually the same as

1 / tan(fovY / 2)

Why is it written this way? I'm guessing because if you had the first style and tan came out to 0 you'd divide by 0 your program would crash where is if you do it the this way there's no division so no chance for a divide by zero.

Seeing that -1 is in matrix[11] spot means when we're all done

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

clipY = cameraY * matrix[5] / cameraZ * matrix[11]

For clipX we basically do the exact same calculation except scaled for the aspect ratio.

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

clipX = cameraX * matrix[0] / cameraZ * matrix[11]

Finally we have to convert cameraZ in the -zNear <-> -zFar range to clipZ in the -1 <-> + 1 range.

The standard perspective matrix does this with as reciprocal function so that z values close the the camera get more resolution than z values far from the camera. That formula is

clipZ = something / cameraZ + constant

Let's use s for something and c for constant.

clipZ = s / cameraZ + c;

and solve for s and c. In our case we know

s / -zNear + c = -1
s / -zFar  + c =  1

So, move the `c' to the other side

s / -zNear = -1 - c
s / -zFar  =  1 - c

Multiply by -zXXX

s = (-1 - c) * -zNear
s = ( 1 - c) * -zFar

Those 2 things now equal each other so

(-1 - c) * -zNear = (1 - c) * -zFar

expand the quantities

(-zNear * -1) - (c * -zNear) = (1 * -zFar) - (c * -zFar)

simplify

zNear + c * zNear = -zFar + c * zFar

move zNear to the right

c * zNear = -zFar + c * zFar - zNear

move c * zFar to the left

c * zNear - c * zFar = -zFar - zNear

simplify

c * (zNear - zFar) = -(zFar + zNear)

divide by (zNear - zFar)

c = -(zFar + zNear) / (zNear - zFar)

solve for s

s = (1 - -((zFar + zNear) / (zNear - zFar))) * -zFar

simplify

s = (1 + ((zFar + zNear) / (zNear - zFar))) * -zFar

change the 1 to (zNear - zFar)

s = ((zNear - zFar + zFar + zNear) / (zNear - zFar)) * -zFar

simplify

s = ((2 * zNear) / (zNear - zFar)) * -zFar

simplify some more

s = (2 * zNear * zFar) / (zNear - zFar)

dang I wish stackexchange supported math like their math site does :(

so back to the top. Our forumla was

s / cameraZ + c

And we know s and c now.

clipZ = (2 * zNear * zFar) / (zNear - zFar) / -cameraZ -
        (zFar + zNear) / (zNear - zFar)

let's move the -z outside

clipZ = ((2 * zNear * zFar) / zNear - ZFar) +
         (zFar + zNear) / (zNear - zFar) * cameraZ) / -cameraZ

we can change / (zNear - zFar) to * 1 / (zNear - zFar) so

rangeInv = 1 / (zNear - zFar)
clipZ = ((2 * zNear * zFar) * rangeInv) +
         (zFar + zNear) * rangeInv * cameraZ) / -cameraZ

Looking back at makeFrustum we see it's going to end up making

clipZ = (matrix[10] * cameraZ + matrix[14]) / (cameraZ * matrix[11])

Looking at the formula above that fits

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])

I hope that made sense. Note: Most of this is just my re-writing of this article.

这篇关于试图理解 WebGL 中透视矩阵背后的数学原理的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,WP2

admin_action_{$_REQUEST[‘action’]}

do_action( "admin_action_{$_REQUEST[‘action’]}" )动作钩子::在发送“Action”请求变量时激发。Action Hook: Fires when an ‘action’ request variable is sent.目录锚点:#说明#源码说明(Description)钩子名称的动态部分$_REQUEST['action']引用从GET或POST请求派生的操作。源码(Source)更新版本源码位置使用被使用2.6.0 wp-admin/admin.php:...

日期:2020-09-02 17:44:16 浏览:1159

admin_footer-{$GLOBALS[‘hook_suffix’]}

do_action( "admin_footer-{$GLOBALS[‘hook_suffix’]}", string $hook_suffix )操作挂钩:在默认页脚脚本之后打印脚本或数据。Action Hook: Print scripts or data after the default footer scripts.目录锚点:#说明#参数#源码说明(Description)钩子名的动态部分,$GLOBALS['hook_suffix']引用当前页的全局钩子后缀。参数(Parameters)参数类...

日期:2020-09-02 17:44:20 浏览:1060

customize_save_{$this->id_data[‘base’]}

do_action( "customize_save_{$this-&gt;id_data[‘base’]}", WP_Customize_Setting $this )动作钩子::在调用WP_Customize_Setting::save()方法时激发。Action Hook: Fires when the WP_Customize_Setting::save() method is called.目录锚点:#说明#参数#源码说明(Description)钩子名称的动态部分,$this->id_data...

日期:2020-08-15 15:47:24 浏览:799

customize_value_{$this->id_data[‘base’]}

apply_filters( "customize_value_{$this-&gt;id_data[‘base’]}", mixed $default )过滤器::过滤未作为主题模式或选项处理的自定义设置值。Filter Hook: Filter a Customize setting value not handled as a theme_mod or option.目录锚点:#说明#参数#源码说明(Description)钩子名称的动态部分,$this->id_date['base'],指的是设置...

日期:2020-08-15 15:47:24 浏览:887

get_comment_author_url

过滤钩子:过滤评论作者的URL。Filter Hook: Filters the comment author’s URL.目录锚点:#源码源码(Source)更新版本源码位置使用被使用 wp-includes/comment-template.php:32610...

日期:2020-08-10 23:06:14 浏览:925

network_admin_edit_{$_GET[‘action’]}

do_action( "network_admin_edit_{$_GET[‘action’]}" )操作挂钩:启动请求的处理程序操作。Action Hook: Fires the requested handler action.目录锚点:#说明#源码说明(Description)钩子名称的动态部分$u GET['action']引用请求的操作的名称。源码(Source)更新版本源码位置使用被使用3.1.0 wp-admin/network/edit.php:3600...

日期:2020-08-02 09:56:09 浏览:873

network_sites_updated_message_{$_GET[‘updated’]}

apply_filters( "network_sites_updated_message_{$_GET[‘updated’]}", string $msg )筛选器挂钩:在网络管理中筛选特定的非默认站点更新消息。Filter Hook: Filters a specific, non-default site-updated message in the Network admin.目录锚点:#说明#参数#源码说明(Description)钩子名称的动态部分$_GET['updated']引用了非默认的...

日期:2020-08-02 09:56:03 浏览:855

pre_wp_is_site_initialized

过滤器::过滤在访问数据库之前是否初始化站点的检查。Filter Hook: Filters the check for whether a site is initialized before the database is accessed.目录锚点:#源码源码(Source)更新版本源码位置使用被使用 wp-includes/ms-site.php:93910...

日期:2020-07-29 10:15:38 浏览:825

WordPress 的SEO 教学:如何在网站中加入关键字(Meta Keywords)与Meta 描述(Meta Description)?

你想在WordPress 中添加关键字和meta 描述吗?关键字和meta 描述使你能够提高网站的SEO。在本文中,我们将向你展示如何在WordPress 中正确添加关键字和meta 描述。为什么要在WordPress 中添加关键字和Meta 描述?关键字和说明让搜寻引擎更了解您的帖子和页面的内容。关键词是人们寻找您发布的内容时,可能会搜索的重要词语或片语。而Meta Description则是对你的页面和文章的简要描述。如果你想要了解更多关于中继标签的资讯,可以参考Google的说明。Meta 关键字和描...

日期:2020-10-03 21:18:25 浏览:1691

谷歌的SEO是什么

SEO (Search Engine Optimization)中文是搜寻引擎最佳化,意思近于「关键字自然排序」、「网站排名优化」。简言之,SEO是以搜索引擎(如Google、Bing)为曝光媒体的行销手法。例如搜寻「wordpress教学」,会看到本站的「WordPress教学:12个课程…」排行Google第一:关键字:wordpress教学、wordpress课程…若搜寻「网站架设」,则会看到另一个网页排名第1:关键字:网站架设、架站…以上两个网页,每月从搜寻引擎导入自然流量,达2万4千:每月「有机搜...

日期:2020-10-30 17:23:57 浏览:1298