BPL*_*BPL 2 opengl math 3d zooming
我正在尝试学习如何使用正交投影向鼠标缩放,到目前为止我已经得到了这个:
def dolly(self, wheel, direction, x, y, acceleration_enabled):
v = vec4(*[float(v) for v in glGetIntegerv(GL_VIEWPORT)])
w, h = v[2], v[3]
f = self.update_zoom(direction, acceleration_enabled) # [0.1, 4]
aspect = w/h
x,y = x-w/2, y-h/2
K1 = f*10
K0 = K1*aspect
self.left = K0*(-2*x/w-1)
self.right = K0*(-2*x/w+1)
self.bottom = K1*(2*y/h-1)
self.top = K1*(2*y/h+1)
Run Code Online (Sandbox Code Playgroud)
我得到的结果真的很奇怪,但我不知道我搞砸了公式的哪一部分。
你能找出我数学的哪一部分是错误的,或者只是发布一个我可以尝试的清晰的伪代码吗?只是为了记录,我已经在互联网上阅读并测试了很多版本,但还没有找到任何地方可以正确解释这个主题。
附言。您不需要发布与此主题相关的任何 SO 链接,因为我已经阅读了所有内容:)
我将基于以下一组假设以一般方式回答这个问题:
P(正交)投影矩阵描述您的眼睛空间视图体积到标准视图体积的实际映射[-1,1]^3OpenGL 将根据(另请参见假设 2)和V视图变换矩阵,即视图的位置和方向“相机”(如果有这样的东西,尤其是在正射投影中)并基本上建立一个眼睛空间,您的视野体积将相对于该空间定义。w坐标应用任何技巧。-zP是对称的,并且在缩放操作后它也应该保持对称,因此,为了补偿任何移动,也必须调整视图矩阵。right = -lefttop = -bottomV你想要得到的是一个缩放,使得鼠标光标下的对象点不会移动,因此成为缩放操作的中心。鼠标光标本身只是 2D 的,3D 空间中的整条直线将被映射到相同的像素位置。但是,在正射投影中,该线将与图像平面正交,因此我们无需过多考虑第三维。
所以,我们要的是规模与目前的情况P_old(由邻参数定义l_old,r_old,b_old,t_old,n_old和f_old)和V_old(由“照相机”的位置定义c_old和ortientationo_old通过缩放因子)s在鼠标位置(x,y) (从假设6的空间)。
我们可以直接看到几点:
n_new = n_old和f_new = f_old。o_new = o_olds,实际视图体积必须按 缩放1/s,因为当我们放大时,整个世界的一小部分在屏幕上比以前更小(并且看起来更大)。所以我们可以简单地缩放我们拥有的截锥体参数:
l_new = l_old / s, r_new = r_old / s, b_new = b_old / s,t_new = t_old / s如果 new 只替换P_old为P_new,我们得到缩放,但鼠标光标下的世界点会移动(除非鼠标正好在视图的中心)。所以我们必须通过修改相机位置来弥补这一点。
我们首先将鼠标坐标(x,y)放入 OpenGL 窗口空间(假设 5 和 6):
x_win = x + 0.5y_win = height - 0.5 - y请注意,除了镜像 y 之外,我还将坐标移动了半个像素。那是因为在 OpenGL 窗口空间中,像素中心位于中间坐标,而我假设您的整数鼠标坐标将代表您单击的像素的中心(在视觉上不会有很大的不同,但仍然如此)
现在让我们进一步将坐标放入标准化设备空间(这里依赖于假设 4):
x_ndc = 2.0 * x_win / width - 1y_ndc = 2.0 * y_win / height - 1根据假设 2,剪辑和 NDC 坐标将相同,我们可以将向量称为vNDC/空间鼠标坐标:v = (x_ndc, y_ndc, 0, 1)^T
我们现在可以声明我们的“鼠标下的点不能移动”条件:
inverse(V_old) * inverse(P_old) * v = inverse(V_new) * inverse(P_new) * v
Run Code Online (Sandbox Code Playgroud)
但是让我们进入眼睛空间,让我们看看发生了什么:
a = inverse(P_old) * v成为我们缩放之前鼠标光标下点的眼睛空间位置。b = inverse(P_new) * v成为我们缩放后鼠标光标下指针的眼睛空间位置。由于我们假设了一个对称的视图体积,我们已经知道对于 x 和 y 坐标,b = (1/s) *a成立(假设 7。如果该假设不成立,您也需要进行实际计算b,这也不难)。
因此,我们可以设置一个 2D眼空间偏移向量d,它描述了我们的兴趣点是如何被比例移动的:
d = b - a = (1 / s) *a - a = a (1/s - 1)
Run Code Online (Sandbox Code Playgroud)
为了补偿这种移动,我们必须反向移动我们的相机,所以通过-d。
如果您像我在假设 1 中所做的那样将相机位置分开,您只需要相应地更新相机位置c。你只需要注意c世界空间位置,而d眼睛空间偏移的事实:
c_new = c_old - inverse(V_old) * (d_x, d_y, 0, 0)^T
Run Code Online (Sandbox Code Playgroud)
并不是说如果您不将相机位置保留为单独的变量,而是直接保留视图矩阵,则可以简单地预先乘以平移: V_new = translate(-d_x, -d_y, 0) * V_old
更新
到目前为止我写的都是正确的,但我采用了一个捷径,在使用非无限精度数据类型时,这在数值上是一个非常糟糕的主意。如果放大很多,相机位置的误差累积得非常快。所以在@BPL 实现之后,这就是他得到的:
主要的问题似乎是,我直接计算偏移向量d在眼空间,不拿当前视图矩阵V_old(以及它的小误差考虑在内)。所以更稳定的方法是直接在世界空间中计算所有这些:
a = inverse(P_old * V_old) * v
b = inverse(P_new * V_old) * v
d = b - a
c_new = c_old - d
Run Code Online (Sandbox Code Playgroud)
(这样做使得假设 7 不再需要作为副产品,因此它直接适用于任意正交矩阵的一般情况)。
使用这种方法,缩放操作按预期工作: