Search code examples
iosopengl-espixelscaling

OpenGL ES Pixel Art - Scaling


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);

Simulator output at 100%

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:

Retro pixels - Number 8

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.


Solution

  • 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);