5

简洁版本

如何在 OpenGL 映射应用程序中绘制短文本标签,而无需在用户放大和缩小时手动重新计算坐标?

长版

我有一个基于 OpenGL 的映射应用程序,我需要能够在其中绘制多达 250k 点的数据集。每个点都可以有一个短文本标签,通常大约 4 或 5 个字符长。

目前,我使用包含所有字符的单个文本来执行此操作。对于每个点,我为其标签中的每个字符定义一个四边形。因此,带有“Fred”标签的点将有四个与之关联的四边形,每个四边形使用纹理坐标到该单个纹理中来绘制其对应的字符。

当我绘制地图时,我在地图坐标(例如,经度/纬度)中绘制地图点本身。然后我计算屏幕坐标中每个点的位置,并更新每个点的标签四边形的四个角点,再次在屏幕坐标中。(例如,如果我确定该点在屏幕点 100、150 处绘制,我可以将点标签中第一个字符的四边形设置为从左上点 105、155 开始且宽度为6 像素和 12 像素的高度,适合特定字符。然后第二个字符可能从 120、155 等开始。)然后,一旦所有这些标签字符四边形都正确定位,我使用正交屏幕绘制它们投影。

问题是更新所有这些字符四边形坐标的过程很慢,对于具有 150k 点的特定测试数据集大约需要半秒(这意味着,由于每个标签大约有四个字符长,所以大约有 150k * [每点 4 个字符] * [每个字符 4 个坐标对] 每次更新时需要设置的坐标对。

如果地图应用程序不涉及缩放,我不需要在每次刷新时重新计算所有这些坐标。我可以只计算一次标签坐标,然后简单地移动我的查看矩形以显示正确的区域。但是对于缩放,如果不进行坐标计算,我看不到如何使它工作,因为否则角色会在放大时变大,在缩小时变小。

我想要的(以及我理解 OpenGL 没有提供的)是一种告诉 OpenGL 应该在固定的屏幕坐标矩形中绘制四边形的方法,但是该矩形的左上角位置应该是固定距离地图坐标空间中的给定点。所以我想要一个原始层次结构(给定的地图点是它的标签字符四边形的父级)和在这个层次结构中混合两个不同坐标系的能力。

我试图了解是否有一些我可以设置的魔法转换矩阵可以完成这一切,但我不知道该怎么做。

我考虑的另一种选择是在每个点上使用着色器来处理计算该点的标签字符四边形坐标。我以前没有使用过着色器,我只是想了解 (a) 是否可以使用着色器来执行此操作,以及 (b) 在着色器代码中计算所有这些点是否真的比我自己计算它们更重要. (顺便说一句,我已经确认最大的瓶颈是计算四边形坐标,而不是将更新的坐标上传到 GPU。后者需要一些时间,但它是计算,更新的坐标的绝对数量,占据了那半秒的大部分时间。)

(当然,另一种选择是更聪明地了解首先需要在给定视图中绘制哪些标签。但现在我想专注于解决方案,假设所有标签都需要绘制。)

4

3 回答 3

4

所以基本问题(“因为否则角色会在放大时变大而在缩小时变小”)是您在地图坐标而不是屏幕坐标中进行计算?如果你在屏幕坐标中进行,这需要更多的计算吗?显然,任何渲染都需要从地图坐标转换为屏幕坐标。问题似乎是您从地图翻译到屏幕太晚了。因此,不是为每个点做一个单一的地图到屏幕,然后在屏幕坐标中工作,而是主要在地图坐标中工作,然后在最后将每个字符转换为屏幕坐标。缓慢的部分是你在屏幕坐标中工作,然后必须手动转换回地图坐标只是为了告诉 OpenGL 地图坐标,它会将这些转换回屏幕坐标!这是对您的问题的公平评估吗?

因此,解决方案是在您的管道中更早地推动该转换。但是,我明白为什么它很棘手,因为乍一看,OpenGL 似乎想要在“世界坐标”(对你来说,地图坐标)中做所有事情,而不是在屏幕坐标中。

首先,我想知道您为什么要为每个角色进行单独的坐标计算。你用的是什么字体渲染系统?FreeType 之类的东西会自动生成整个字符串的位图图像,并且不需要您按字符工作 [编辑:这不太正确;看评论]。您绝对不需要为每个字符计算地图坐标(甚至屏幕坐标) 。计算标签左上角的屏幕坐标,让您的字体渲染系统一次性生成整个标签的位图。这应该加快速度大约四倍(因为您假设每个标签有 4 个字符)。

现在至于在屏幕坐标中工作,了解一些关于着色器的知识可能会有所帮助。您对 OpenGL 了解得越多,就越会发现它根本就不是 3D 渲染引擎。它只是一个内置了一些非常快速的矩阵基元的 2D 图形库。OpenGL实际上在最低级别在屏幕坐标中工作(不是像素坐标——它在标准化的屏幕空间中工作,我认为在X和Y轴上从内存从-1到1)。它“感觉”就像您在世界坐标中工作的唯一原因是因为您设置了这些矩阵。

所以我认为你一直在地图坐标中工作直到最后的原因是因为它最简单:OpenGL 自然地为你做了地图到屏幕的转换(使用矩阵)。你必须改变它,因为你想自己在屏幕坐标中工作,因此你需要在 OpenGL 得到你的数据之前进行很长时间的转换。所以当你去绘制一个标签的时候,你应该在每个点上手动应用地图到屏幕的变换矩阵,如下:

  1. 您在地图坐标中有一个特定点(需要绘制标签)。
  2. 应用地图到屏幕矩阵将点转换为屏幕坐标。这可能意味着将点乘以 MODELVIEW 和 PROJECTION 矩阵,使用与 OpenGL 渲染顶点时相同的算法。因此,您可以glGet GL_MODELVIEW_MATRIX 和 GL_PROJECTION_MATRIX 来提取 OpenGL 的当前矩阵,或者您可以自己手动保留矩阵的副本。
  3. 现在你有了屏幕坐标中的地图标签,计算标签文本的位置。正如您上面所说,这只是在 X 和 Y 轴上添加 5 个像素。但是,请记住,您不是在像素空间中,而是在标准化的屏幕空间中,因此您使用的是百分比(例如,添加 0.05 个单位,将添加 5% 的屏幕空间)。最好不要以像素为单位考虑,因为那样您的应用程序将缩放以匹配分辨率。但是,如果您真的想以像素为单位,则必须根据分辨率计算像素到单位。
  4. 使用glPushMatrix保存当前矩阵,然后使用 glLoadIdentity将当前矩阵设置为恒等 - 告诉 OpenGL 不要转换您的顶点。(我认为您必须对 PROJECTION 和 MODELVIEW 矩阵都这样做。)
  5. 在屏幕坐标中绘制您的标签。

所以你真的不需要写一个着色器。您当然可以在着色器中执行此操作,并且肯定会使第 2 步更快(无需编写您自己的软件矩阵乘法代码;在 GPU 上乘法矩阵非常快)。但这将是以后的优化,并且需要大量工作。我认为上述步骤将帮助您在屏幕坐标中工作,避免为了提供 OpenGL 地图坐标而浪费大量时间。

于 2011-06-16T03:34:20.027 回答
0

旁注:

""" 生成整个字符串的位图图像,并且不需要您按字符工作...计算标签左上角的屏幕坐标,并让您的字体渲染系统生成位图一次完成整个标签。这应该加快速度大约四倍(因为您假设每个标签有 4 个字符)。“”“

无论是否使用 Freetype,您当然可以为每个标签而不是每个字符计算位图图像,但这需要以下之一:

  • 存储数千种不同的纹理,每个标签一个

    • 存储这么多纹理似乎个坏主意,但也许不是。

    或者

  • 在每次屏幕更新时为每个点渲染每个标签。

    • 这肯定太慢了。
于 2011-06-16T18:26:46.393 回答
0

只是为了跟进决议:

我并没有真正解决这个问题,但我最终在绘制标签时变得更聪明了。我能够快速确定我是否要绘制太多字符(即,字符太多以至于在具有典型点密度的典型屏幕上,标签会靠得太近而无法以有用的方式阅读),然后我只是根本不贴标签。一次最多可以绘制大约 5000 个字符,如上所述重新计算字符坐标并没有明显的减速。

于 2011-08-01T19:21:51.190 回答