OpenGL ES Pixel Art - Scaling

血红的双手。 提交于 2019-12-23 02:50:31

问题


Im having trouble displaying pixel based art (think retro tiles and art) on OpenGL Es 1.1 on the iPhones.

Tiles are represented using 8bytes (1 byte for each row) with each bit representing a pixel being set or not.

For example a tile with the number 8:

0 0 0 0 0 0 0 0      ->
0 0 1 1 1 0 0 0      ->        xxx
0 1 0 0 0 1 0 0      ->       x   x
0 1 0 0 0 1 0 0      ->       x   x
0 0 1 1 1 0 0 0      ->        xxx
0 1 0 0 0 1 0 0      ->       x   x
0 1 0 0 0 1 0 0      ->       x   x
0 0 1 1 1 0 0 0      ->        xxx

Converting this to OpenGL on iPhone using

glDrawArrays(GL_POINTS, 0, 64);

The logic is correct, but the problem is it doesn't give the retro effect. I was looking for more of a blocky/retro style. I read that i can turn off pixel smoothing which would cause pixels to be displayed as squares.(i think it was GL_Disable(POINT_SMOOTH), but not sure if this effects ES since nothing changed.)

Possible solutions i found to relating problems:

  1. use a frame buffer to render to a smaller resolution and then scale it up in the render buffer. I don't know how this is done or if it'll work.
  2. Create an image from the pixels, create a texture from that image and finally render that texture.

Possible solutions i thought off:

  1. For each pixel, draw two pixels instead both horizontally and vertically.
  2. Draw each pixel as a square using triangles.
  3. Use GLPointSize - gives a correct effect when set to 2, but coordinates are then messed up. Aligning becomes harder.

Ultimately i would like the tiles to be presented:

This is more of me understanding how OpenGL and pixels work, and I'm using a gameboy emulator to work this out. If someone thinks the correct way is to create the graphics manually and load them as textures, its not the feasible answer.


回答1:


There are quite a few ways of doing this and I would suggest the first one you already found. Draw the scene to a smaller buffer and then redraw it a canvas.

What you are looking here for is a FBO (frame buffer object). Find some examples on how to create a FBO and attach a texture to it. This will create a buffer for you with any dimensions you will input. Some common issues here are that you will most likely need a POT texture (a power of 2 dimensions: 2, 4, 8, 16... So 64x128 buffer for instance) so to control a different size you should use viewport which will then use only a part of the buffer you need.

So in the end this will create a low resolution texture which can be used to draw to the canvas (view). How you draw to it something you should experiment with. The points may not be the best solution, even in your case of a buffer I would use lines between the points you defined in your example. At this point you must choose to draw with or without the antialias. To enable it look for the multisampling on iOS.

After you have the texture to which the shape is drawn you will need to redraw it to the view. This is pretty much drawing a full-screen texture. Again you have multiple ways of drawing it. The most powerful tool here are the texture parameters: Using nearest will discard all the color interpolations and the squares should be visible; using linear (or trilinear) will do some interpolation and the result will probably be nearer to what you want to achieve. Then you may again play around with multisampling to create antialiasing and get a better result.

So the powers here are:

  • Different FBO buffer sizes
  • Antialiasing on the FBO
  • Texture parameters
  • Antialiasing when redrawing to canvas

As for the FBO this is one of the easiest things to do:

  • Generate frame buffer (glGenFramebuffers)
  • Bind the frame buffer (glBindFramebuffer)
  • Create a texture (glGenTextures) and bind it (glBindTexture)
  • Set texture data glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, twidth, theight, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
  • Attach the texture to the frame buffer glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture.textureID, 0);
  • Now when you draw to the texture you need to bind the FBO frame buffer and when you draw to the main buffer just bind that frame buffer.

Since this is quite a broad answer (as is the question)... If you will be implementing this and will have additional questions or issues it will be best to create a separate questions and probably link them in the comments.

Good luck.




回答2:


Im not sure if my question was not clear, but to draw pixels to screen you have to create a texture and pass in the pixel data to it, then render that texture onto the screen. It would be the equivalent of glDrawPixels.

The code would be:

#define W 255,255,255

#define G 192,192,192

//8 x 8 tile with 3 bytes for each pixel RGB format
GLubyte pixels[8 * 8 * 3] = {
W,W,W,W,W,W,W,W,
W,W,G,G,G,W,W,W,
W,G,W,W,W,G,W,W,
W,G,W,W,W,G,W,W,
W,W,G,G,G,W,W,W,
W,G,W,W,W,G,W,W,
W,G,W,W,W,G,W,W,
W,W,G,G,G,W,W,W
};

somewhere in setup:

glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
glGenTextures(1, &tex);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, tex);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 8, 8, 0, GL_RGB, GL_UNSIGNED_BYTE, pixels);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);

Then draw the texture as usual:

glActiveTexture(GL_TEXTURE0);
glUniform1i([program uniformLocation:@"s_texture"], 0);
glBindTexture(GL_TEXTURE_2D, tex);

glEnableVertexAttribArray(positionAttrib);
glVertexAttribPointer(positionAttrib, 2, GL_FLOAT, GL_FALSE, 0, v);
glEnableVertexAttribArray(texAttrib);
glVertexAttribPointer(texAttrib, 2, GL_FLOAT, GL_FALSE, 0, t);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_BYTE, i);


来源:https://stackoverflow.com/questions/38052734/opengl-es-pixel-art-scaling

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!