How do I display a non-power-of-2-texture as sprit

2020-07-23 09:16发布

问题:

I want to draw an arbitrary size sprite as a png, say something TOTALLY CRAZY like 56 wide x 30 high. Not a power of 2 in either dimension. Also I may want to draw another different sprite that's 72 wide x 33 high. Indicating this because no 'tricks' are acceptable here, I need to handle general case.

So I have this png (with transparency) and I want to draw it as a sprite with absolutely no stretching, interpolation, etc. I want it to map 1:1 with pixels on the screen. Pretend these sprites are pixel art (they may or may not be but they are drawn for the exact resolution).

I understand sprite drawing - 2 triangles as a quad, rendering my texture to that via texture coordinate mapping - however I only understand it for powers of 2. And I also do not understand how to size my 'quad' and set up my GL matricies such that I can make the sprite be the exact same size in pixels as my sprite

I'm using OpenGL ES so using the new extensions to use non-power-of-2 textures is not acceptable.

I'm only drawing 3-10 sprites at a time so I'm open to suggestions about efficiency but I'm most interested in 'accurate' looking results.

回答1:

To get a pixel-perfect coordinate system, you could set up your matrices as follows:

glViewport(0, window_width, 0, window_height);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(0, window_width, 0, window_height, 0, 1);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();

This ensures a 1:1 mapping of OpenGL coordinates to pixels, and puts your origin in the bottom left corner.

As to your texture, if you don't want to rely on the non-power-of-two extension, you must pad it to be a power of two. Even though the OpenGL ES spec doesn't say it has to be square as well (as far as I know), I've seen strange things happen on Android with texture sizes like 512 x 128, so it might be best to stick to square power-of-two textures.

The thing to note is that texture coordinates in OpenGL are always between 0 and 1, no matter what the pixel size of your texture is. So if your sprite is 48 pixels wide, and you padded it to a texture of 64 x 64, then it will span the x coordinates from 0 to 0.75:

1 +--------+
  |        |
  |        |
  +-----+  |
  |\_O_/|  |
  |  O  |  |
  | / \ |  |
0 +-----+--+
  0   0.75 1   <- texture coordinates
  0     48 64  <- pixels

So you have to set up these texture coordinates for the corners of your quad. Because we have a pixel-perfect projection, the quad must also be exactly 48 pixels wide.

In conclusion, to draw your sprite at position x, y (in pixels from the bottom left), do something like this:

float texcoord_x = (float)sprite_width / texture_width;
float texcoord_y = (float)sprite_height / texture_height;

glEnable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, texture_id);

glBegin(GL_QUADS);
// bottom left
glTexCoord2f(0, 0);
glVertex2i(x, y);
// bottom right
glTexCoord2f(texcoord_x, 0);
glVertex2i(x + sprite_width, y);
// top right
glTexCoord2f(texcoord_x, texcoord_y);
glVertex2i(x + sprite_width, y + sprite_height);
// top left
glTexCoord2f(0, texcoord_y);
glVertex2i(x, y + sprite_height);
glEnd();

I know OpenGL ES doesn't have glVertex2i and so on, so you'll have to put these coordinates into a buffer. And it doesn't allow quads, so you'll have to split it up into triangles. But this is the basic idea.