5

iPhone 上的 Screen-to-world问题

我有一个在 EAGLView 中渲染的 3D 模型 (CUBE),我希望能够检测到我何时触摸立方体的给定面的中心(从任何方向角度)。听起来很容易,但它不是......

问题:
如何准确地将屏幕坐标(触摸点)与世界坐标(OpenGL 3D 空间中的位置)关联起来?当然,将给定点转换为屏幕/世界轴的“百分比”似乎是合乎逻辑的解决方法,但是当我需要缩放或旋转 3D 空间时会出现问题。注意:旋转和放大和缩小 3D 空间会改变 2D 屏幕坐标与 3D 世界坐标的关系...此外,您必须允许 3D 空间中的视点和对象之间存在“距离” . 起初,这似乎是一项“简单的任务”,但当您实际检查需求时,情况就会发生变化。而且我没有发现人们在 iPhone 上这样做的例子。这通常是如何完成的?

一个“简单”的任务?:
当然,一个人可能会承担编写一个 API 来充当屏幕和世界之间的中间人的任务,但是创建这样一个框架的任务需要一些认真的设计,并且可能需要“时间”要做的事——不是一个人可以4 小时内完成的事情……而 4 小时恰好是我的最后期限。

问题:

  • 有哪些最简单的方法可以知道我是否在 iPhone OpenGL ES 世界中触摸了 3D 空间中的特定位置?
4

5 回答 5

7

您现在可以在http://code.google.com/p/iphone-glu/中找到gluUnProject。我与 iphone-glu 项目没有任何关联,我自己也没有尝试过,只是想分享链接。

你将如何使用这样的功能?该 PDF提到:

实用程序库例程gluUnProject()执行这种转换的反转。给定一个位置的三维窗口坐标以及影响它们的所有变换,gluUnProject() 返回它起源的世界坐标。

int gluUnProject(GLdouble winx, GLdouble winy, GLdouble winz, 
const GLdouble modelMatrix[16], const GLdouble projMatrix[16], 
const GLint viewport[4], GLdouble *objx, GLdouble *objy, GLdouble *objz);

使用由模型视图矩阵 (modelMatrix)、投影矩阵 (projMatrix) 和视口 (viewport) 定义的变换,将指定的窗口坐标 (winx, winy, winz) 映射到对象坐标。生成的对象坐标在 objx、objy 和 objz 中返回。该函数返回GL_TRUE,表示成功,或GL_FALSE,表示失败(例如不可逆矩阵)。此操作不会尝试将坐标裁剪到视口或消除超出glDepthRange()的深度值。

试图扭转转型过程存在固有的困难。二维屏幕位置可能源自三维空间中整行的任何位置。为了消除结果的歧义,gluUnProject() 要求提供窗口深度坐标 (winz) 并根据 glDepthRange() 指定 winz。对于 glDepthRange() 的默认值,0.0 的 winz 将请求近裁剪平面上变换点的世界坐标,而 1.0 的 winz 将请求远裁剪平面上的点。

示例 3-8(再次参见PDF)演示了 gluUnProject(),方法是读取鼠标位置并确定转换后的近端和远端剪裁平面上的三维点。计算的世界坐标被打印到标准输出,但渲染的窗口本身只是黑色的。

在性能方面,我通过 Google 很快找到了一个示例,说明您可能不想使用 gluUnProject 执行哪些操作,并附有指向可能导致更好替代方案的链接。我完全不知道它对 iPhone 的适用性,因为我还是 OpenGL ES 的新手。一个月后再问。;-)

于 2009-12-15T07:54:45.643 回答
3

您需要有 opengl 投影和模型视图矩阵。将它们相乘以获得模型视图投影矩阵。反转此矩阵以获得将剪辑空间坐标转换为世界坐标的矩阵。变换您的触摸点,使其与剪辑坐标相对应:屏幕的中心应为零,而 X 和 Y 的边缘应分别为 +1/-1。

构造两个点,一个在 (0,0,0) 和一个在 (touch_x,touch_y,-1) 并通过逆模型视图投影矩阵变换两者。

做透视除法的倒数。

您应该得到两个点来描述从相机中心到“远距离”(远平面)的线。

根据模型的简化边界框进行挑选。您应该能够在网上找到大量的光线/框相交算法。

另一种解决方案是将每个模型以稍微不同的颜色绘制到屏幕外缓冲区中,并从那里读取触摸点的颜色,告诉你哪个 brich 被触摸了。

这是我为一个使用子弹物理的小项目编写的光标的来源:

float x=((float)mpos.x/screensize.x)*2.0f -1.0f;
    float y=((float)mpos.y/screensize.y)*-2.0f +1.0f;
    p2=renderer->camera.unProject(vec4(x,y,1.0f,1));
    p2/=p2.w;
    vec4 pos=activecam.GetView().col_t;
    p1=pos+(((vec3)p2 - (vec3)pos) / 2048.0f * 0.1f);
    p1.w=1.0f;

    btCollisionWorld::ClosestRayResultCallback rayCallback(btVector3(p1.x,p1.y,p1.z),btVector3(p2.x,p2.y,p2.z));
    game.dynamicsWorld->rayTest(btVector3(p1.x,p1.y,p1.z),btVector3(p2.x,p2.y,p2.z), rayCallback);
    if (rayCallback.hasHit())
    {
        btRigidBody* body = btRigidBody::upcast(rayCallback.m_collisionObject);
        if(body==game.worldBody)
        {
            renderer->setHighlight(0);
        }
        else if (body)
        {
            Entity* ent=(Entity*)body->getUserPointer();

            if(ent)
            {
                renderer->setHighlight(dynamic_cast<ModelEntity*>(ent));
                //cerr<<"hit ";
                //cerr<<ent->getName()<<endl;
            }
        }
    }
于 2009-07-11T16:51:21.503 回答
2

想象一条线从观察者的眼睛
通过屏幕触摸点延伸到您的 3D 模型空间。

如果该线与立方体的任何面相交,则用户已经触摸了立方体。

于 2009-07-02T08:23:23.147 回答
1

出现了两种解决方案。他们都应该实现最终目标,尽管通过不同的方式:而不是回答“鼠标下的世界坐标是什么?”,他们回答的问题是“鼠标下渲染了什么对象?”。

一种是将模型的简化版本绘制到屏幕外缓冲区,使用不同的颜色渲染每个面的中心(并调整照明以保持相同的颜色)。然后,您可以检测缓冲区中的这些颜色(例如 pixmap),并将鼠标位置映射到它们。

另一种是使用OpenGL拾取。这里有一个看起来不错的教程。基本思想是将 OpenGL 置于选择模式,将视口限制在兴趣点周围的一个小(可能是 3x3 或 5x5)窗口,然后使用 OpenGL“名称”(整数)渲染场景(或其简化版本)标识符)来识别组成每个面的组件。在此过程结束时,OpenGL 可以为您提供在选择视口中呈现的名称列表。将这些标识符映射回原始对象将让您确定鼠标光标下的对象。

于 2009-07-11T16:11:20.023 回答
0

Google for opengl screen to world (例如,有人想要在GameDev.net上做你正在寻找的事情的线程)。有一个gluUnProject函数可以做到这一点,但它在 iPhone 上不可用,所以你必须移植它(参见Mesa 项目的这个源代码)。或者,也许某处已经有一些公开可用的资源?

于 2009-07-02T06:44:13.873 回答