Stickers in opengl map application

Short version

How can I draw short text labels in an OpenGL mapping application without having to manually recount the coordinates as the user grows and shrinks?

Long version

I have an OpenGL mapping application where I need to be able to draw datasets with an accuracy of 250 thousand points. Each dot can have a short text label, usually about 4 or 5 characters.

I am currently doing this using a single text containing all characters. For each point, I define a square for each character in my label. Thus, a dot labeled “Fred” would have four quads associated with it, and each square uses the texture coordinates in this single texture to draw the corresponding character.

When I draw a map, I draw the map points themselves in map coordinates (for example, longitude / latitude). Then I calculate the position of each point in the coordinates of the screen and update the four corner points for each of these squares of the point label, again in the coordinates of the screen. (For example, if I determine that the point is drawn on the screen point 100, 150, I could set the square for the first character in the point label as a rectangle starting at the top left point 105, 155 and having a width of 6 pixels and a height of 12 pixels, in depending on the particular character. Then the second character may begin with 120, 155, etc.). Then, when all these squares of the label characters are positioned correctly, I draw them using the projection orthogonal screen.

The problem is that the process of updating all these four-coordinate coordinates of the characters is slow, taking about half a second for a certain set of test data with 150 thousand points (which means that, since each label has a length of about four characters, 150k * [4 characters in each point] * [4 coordinate pairs per symbol], which must be set for each update.

If the map application did not include scaling, I would not need to recalculate all these coordinates with each update. I could just calculate the coordinates of the labels once, and then just shift my view rectangle to show the correct area. But with scaling, I can't figure out how to make it work without performing consistent calculations, because otherwise the characters will grow huge when you zoom in and out when you zoom out.

What I want (and what I understand, OpenGL does not provide) is a way to tell OpenGL that the square should be drawn in a fixed rectangle of the screen coordinates, but the top left position of this rectangle should be a fixed distance from the given point in the coordinate card space. Therefore, I want both a primitive hierarchy (a given point on the map to be the parent of its squares of label characters) and the ability to combine two different coordinate systems in this hierarchy.

I'm trying to figure out if there is some kind of magic transformation matrix that I can install that will do all of this from me, but I don’t see how to do it.

Another alternative that I examined is to use a shader at each point to handle the calculation of the squares of the label characters for that point. I have not worked with shaders before, and I'm just trying to understand (a) if shaders can be used for this, and (b) does it really calculate all these points in the shader code, something, (By the way, I confirmed that the big narrow the place calculates the square coordinates rather than loading the updated coordinates in the GPU.The latter takes a little time, but this calculation is the net amount of updated coordinates, which takes up the bulk of this half second.)

(Of course, another alternative is the ability to determine which labels should be drawn in this view in the first place. But now I would like to focus on the solution, assuming that all labels should be drawn.)

+4
source share
3 answers

Just to keep track of the resolution:

I really did not solve this problem, but in the end I became smarter when I drew shortcuts in the first place. I was able to quickly determine if I was going to draw too many characters (i.e., so many characters that on a typical screen with a typical density of dot points, the labels would be too close to read in a useful way), and then I just don’t mark at all. When composing up to about 5000 characters at a time, a noticeable slowdown does not recalculate the coordinates of the characters, as described above.

0
source

So, the main problem ("because otherwise the symbols will grow huge when you zoom in and out when you zoom out") is that you are doing calculations in the coordinates of the map, and not in the coordinates of the screen? And if you did this in screen coordinates, would it require more computation? Obviously, any rendering should translate from map coordinates to screen coordinates. The problem is that you transfer too late from the map to the screen. Therefore, instead of making a single map for the screen for each point, and then working in screen coordinates, you work mainly in cartographic coordinates, and then translate each character to screen coordinates at the very end. And the slow part is that you work in screen coordinates, and then manually translate back to the map coordinates to inform OpenGL about the coordinates of the map, and it converts them back to screen coordinates! Is this a fair assessment of your problem?

Therefore, the solution is to advance this conversion earlier in your pipeline. However, I can understand why this is difficult, because at first glance, OpenGL seems to want to do everything in "world coordinates" (for you, map coordinates), but not in screen coordinates.

Firstly, I wonder why you do separate coordinate calculations for each character. What font rendering system are you using? Something like FreeType will automatically generate a bitmap image of the whole line and does not require you to work on the symbol [ edit : this is not entirely true; see comments]. You definitely do not need to calculate the map coordinate (or even the screen coordinate) for each character. Calculate the screen coordinate in the upper left corner of the label, and your font rendering system will produce a bitmap image of the entire label in one go. This should speed things up about four times (since you take 4 characters per label).

Now, with regard to working in screen coordinates, it may be useful to learn a little about shaders. The more you learn about OpenGL, the more you will find out that in reality this is not a 3D engine at all. It is just a 2D graphics library with very fast matrix primitives. OpenGL actually works at the lowest level in screen coordinates (and not in pixel coordinates - it works in the normalized screen space, I think, from memory from -1 to 1 both on the X axis and the Y axis). The only reason he "feels" how you work in world coordinates is the creation of these matrices.

So, I think that the reason why you work all the time in cartographic coords to the end is because it is the easiest way: OpenGL, of course, converts a map into a screen (using matrices). You have to change this because you want to work on the screen cords yourself, and therefore you need to do the transformation long before OpenGL receives your data. Therefore, when you go to draw a label, you should manually apply the display transformation matrix to the screen at each point, as shown below:

  • You have a specific point (which needs a sticker) in the coordinates of the map.
  • Apply a display matrix to the screen to convert the coordinates of the point to the screen. This probably means multiplying the point by the MODELVIEW and PROJECTION matrices, using the same algorithm as OpenGL when rendering the vertex. Thus, you can glGet GL_MODELVIEW_MATRIX and GL_PROJECTION_MATRIX to extract the current OpenGL matrices, or you can manually save a copy of the matrix yourself.
  • Now that you have the map label in screen coordinates, calculate the position of the label text. This is just adding 5 pixels along the X and Y axis, as you said above. However, remember that you are not in the pixel space, but in the normalized screen space, so you work as a percentage (add 0.05 units, add, for example, 5% of the screen). It is probably best not to think in pixels, because then your application will scale according to the resolution. But if you really want to think in pixels, you will need to calculate pixels per unit based on resolution.
  • Use glPushMatrix to save the current matrix, then glLoadIdentity to set the current matrix to an identifier - tell OpenGL not to convert your vertices. (I think you will have to do this for both PROJECTION matrices and MODELVIEW matrices.)
  • Draw your label in the coordinates of the screen.

Therefore, you do not need to write a shader. You could do it in the shader, and it will certainly make step 2 faster (no need to write your own multiplication code by the program matrix, multiply the matrix by the GPU very quickly). But it will be later optimization and a lot of work. I think that the above steps will help you work in the coordinates of the screen and do not spend a lot of time to give the coordinates of the OpenGL map.

+2
source

Comment on the side:

"" generate a bitmap of the whole line and does not require you to work on each character ... Calculate the screen coordinate in the upper left corner of the label, and your font visualization system will produce a bitmap of the entire label in one go. This should speed things up about four times (since you take 4 characters per shortcut). ""

Freetype or no, you can probably compute a bitmap for each label, not for each character, but this will require one of:

  • saving thousands of different textures, one for each label

    • It seems like a bad idea to store a lot of textures, but maybe it is not.

    or

  • rendering of each label for each point each time the screen is refreshed.

    • it will be too slow.
0
source

All Articles