使用glFrustum的离轴投影

6
我正在尝试使用OpenGL进行场景的非轴投影,我阅读了Robert Kooima的非轴投影文档,并且现在对实际需要完成的工作有了更好的了解,但是这里仍然有一些我觉得棘手的部分。我知道OpenGL的非轴投影代码如下所示:

代码1:

glMatrixMode(GL_PROJECTION);  
    glLoadIdentity();            
    glFrustum(fNear*(-fFov * ratio + headX),  
              fNear*(fFov * ratio + headX),  
              fNear*(-fFov + headY),  
              fNear*(fFov + headY),  
              fNear, fFar);  
          
    glMatrixMode(GL_MODELVIEW);  
    glLoadIdentity();  
    gluLookAt(headX*headZ, headY*headZ, 0, headX*headZ, headY*headZ, -1, 0, 1, 0);
    glTranslatef(0.0,0.0,headZ);

如果这是一个以用户为屏幕中心的普通透视投影方式,那么我可以理解。
               Screen  
                   |
                   |  h = H/2
                   |  
x----- n -----------
                   |
                   |  h = H/2
                   |

当用户在x位置,距离屏幕为n时,glFrustum的顶部和底部坐标将被计算为:(假设theta是视场角(fov),我假设它被认为是30度
h = n * tan (theta/2);
tanValue = DEG_TO_RAD * theta/2;
[EDIT Line additon here>>]: fFov = tan(tanValue);
h = n * tan (tanValue);

因此,glFrustum参数中得到了顶部和底部(否定了顶部的值)。左侧目前是左/右。
Now, Aspect Ratio, r = ofGetWidth()/ofGetHeight();
Right = n * (fFov * r); , where r is the aspect ratio [Edit1>> Was written tanValue*r earlier here]

问题1:以上的(tanValue*r)是获取水平视野角度,然后应用相同的角度来获取左/右数值吗?
问题2:计算headX和headY的方法是什么?为什么要从以上减去-0.5?我观察到它将x值带到(-0.5到0.5),将y值带到(0.5到-0.5),而msX和msY变化。
问题3:在上述代码(Code 1)中,如何将headY添加到计算出的tan(fov/2)值中?
-fFov + headY
fFov + headY

这个值能为我们提供什么?-fFov是theta/2的计算tan值,但头部Y值该如何直接添加?
-fFov * ratio + headX
-fFov * ratio + headX

以上如何给我们提供一个价值,当乘以n(近值)时,为非对称glFrustum调用的左右投影提供值?
问题4)我理解glLookAt必须用于视点,将视锥的顶点移动到用户眼睛所在的位置(在这种情况下是鼠标所在的位置)。请注意上面代码中的行:
gluLookAt(headX * headZ,headY * headZ,0,headX * headZ,headY * headZ,-1,0,1,0); headX * headZ如何给我眼睛的xPosition,headY * headZ如何给我眼睛的yPosition,我可以在此处用于gluLookAt()
编辑:完整的问题描述在此处添加:pastebin.com/BiSHXspb
1个回答

6
你做了这个很棒的ASCII艺术图片。
               Screen  
                   B
                   |  h = H/2
                   |  
x----- n ----------A
                   |
                   |  h = H/2
                   B'

视野(Field of view)被定义为屏幕"线"(由两个端点B和B'组成)与点x之间形成的角度fov = angle((x,B), (x,B'))。三角函数正切(tan)的定义为

h/n = tan( angle((x,A), (x,B)) )

既然length(A, B) == length(A, B') == h == H/2,我们知道

H/(2·n) == tan( fov ) == tan( angle((x,B), (x,B')) ) == tan( 2·angle((x,A), (x,B)) )

由于三角函数中的角度是以弧度为单位给出的,但大多数人更熟悉角度制,因此您可能需要将角度从度转换为弧度。

因此,我们只对屏幕跨度(= h)的一半感兴趣,我们必须将角度减半。如果我们想要接受角度制,则还必须将其转换为弧度。这就是这个表达式的意义所在。

tanValue = DEG_TO_RAD * theta/2;

使用这个方法,我们可以通过以下方式计算h

h = tan(tanValue) * n

如果FOV是关于屏幕的水平或垂直跨度,则取决于字段跨度H如何与宽高比缩放。
headX和headY是如何计算的?从上面减去-0.5有什么用?我观察到它将x值带到了(-0.5到0.5),y值带到了(0.5到-0.5),而msX和msY则变化。
你给出的计算假定屏幕空间坐标范围为[0,screenWidth]×[0,screenHeight]。但是,由于我们在归一化范围[-1,1]²中进行视锥体计算,因此我们想要将设备绝对鼠标坐标转换为归一化中心相对坐标。然后可以指定相对于归一化近平面大小的轴偏移量。这是没有偏移的情况(此图片中网格距离为0.1单位):
并且应用了X偏移量-0.5之后,它看起来像这样(橙色轮廓),正如您所看到的,近平面的左侧边缘已经向-0.5偏移。
现在只需想象一下网格是您的屏幕,您的鼠标指针会像那样拖动投影锥体近平面边界。
这个值给我们提供了什么?-fFov是计算的tan(theta / 2),但是headY如何直接添加?
因为fFov不是角度,而是您ASCII艺术图片中的跨度H/2 = h。而headX和headY是归一化近投影平面上相对位移量。
headX headZ给我眼睛的xPosition,headY headZ给我眼睛的yPosition,我可以在gluLookAt()中使用它们吗?
您引用的代码似乎是该问题的一个临时解决方案,以强调效果。在真正的头部跟踪立体系统中,您会略有不同。从技术上讲,headZ应该用于计算近平面距离或从其派生。
无论如何,主要思想是,头部位于投影平面的某个距离处,并且中心点以投影的相对单位移动。因此,您必须将相对的headX、headY与实际头部距离投影平面进行缩放,以使顶点修正起作用。
到目前为止,我们只考虑了将视野(fov)转换为屏幕范围的一个维度。为了使图像不失真,近裁剪面的[left,right] / [bottom,top]范围的宽高比必须与视口宽度/高度的宽高比相匹配。
如果我们选择将FoV角定义为垂直FoV,则近裁剪面范围的水平大小是垂直近裁剪面范围大小与宽高比比例缩放的结果。
这并不是异轴投影的特殊之处,而是可以在每个透视投影帮助函数中找到。请参考gluPerspective的源代码进行比较。
void GLAPIENTRY
gluPerspective(GLdouble fovy, GLdouble aspect, GLdouble zNear, GLdouble zFar)
{
   GLdouble xmin, xmax, ymin, ymax;

   ymax = zNear * tan(fovy * M_PI / 360.0); // M_PI / 360.0 == DEG_TO_RAD
   ymin = -ymax;

   xmin = ymin * aspect;
   xmax = ymax * aspect;

   glFrustum(xmin, xmax, ymin, ymax, zNear, zFar);
}

如果我们认为近裁剪平面范围是[- aspect,aspect]×[-1,1],那么headX的位置当然不在规范化范围[-1,1]内,而必须在范围[- aspect,aspect]中给出。
如果您查看您链接的论文,您会发现对于每个屏幕,跟踪器报告的头部位置都是相对于屏幕的绝对坐标进行转换的。
两周前,我有机会测试了一个名为“Z空间”的显示系统,其中极化立体显示器与头部跟踪器相结合,创建了一个偏离轴的视锥/观察组合,与您在显示器前的物理头部位置相匹配。它还提供了一个“笔”来与您面前的3D场景交互。这是我在过去几年中见过的最令人印象深刻的事情之一,我正在恳求我的老板给我们买一个 :)

非常感谢您的回复。我一直在思考3D图形的每一个小部分,而不仅仅是想着编写代码。我不得不阅读很多关于矩阵和其他主题的资料,但还存在一些空白。这个答案将成为我的参考。非常感谢您的努力 :) - user1240679
ASCII 艺术图形是屏幕和用户 (x) 的侧面描述。如您所指出的,以角度表示的角度 (angle ((x,A),(x,B))) 在弧度中变为 tanValue = DEG_TO_RAD * theta/2; ,随后 fFov = tan(tanValue)。因此,对于 glFrustum 中的 left/right 参数中的 Question 1fFov * ratio 的含义是什么?由于我需要获取斜轴投影的左右范围,我不确定我们是否使用 fFov * ratio 提取 x 位置。您能否再详细解释一下? - user1240679
考虑用户的位置,如此-> http://bit.ly/10H0HLh。根据这个图`screenHeight=768, mouseYPosition=668。因此,根据我上面的帖子中的逻辑headY=(768-668)/768 - 0.5 = -0.36。这意味着当用户的头部位置(由于鼠标Y是668)在第四象限中作为被移位的中心时,headY偏移量变为-0.36。随后,glFrustumbottom值变为(-fFov +headY)` ~= -0.5 - 0.36 = -0.86,顶部为0.5-0.36 = 0.14。(续) - user1240679
这个点的 top=0.14, bottom=-0.86 似乎在第一象限而不是第四象限形成。为什么呢?尽管 top 应该是 0.5+,bottom 应该是 -0.5-。这就是我问(问题3)我们如何直接将 headY 添加到 fFov 中,并且仍然希望能够更清楚一些,虽然我已经有了想法。 - user1240679
@user1240679:我做了一个小更新。使用Kinect实现这种类型的屏幕跟踪已经在我的计划中很久了,但我还没有开始做;不幸的是,使用Kinect实现校准的头部跟踪比只是一个周末项目要复杂一些;不过OpenGL投影设置相当容易。 - datenwolf
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接