Search code examples
c++2drenderingframebufferimage-rotation

"Checkerboarding" effect when rotating sprite in software renderer?


I'm writing a 2D software renderer and when rotating in certain angles I will get a sort of "checkerboarding" effect with my sprite pixels:

rotation issue

To try and get proper rotation, I tried rounding/flooring/ceiling the rotated pixels, but that did nothing to fix it.

It seems the pixels get set to the wrong position and overlap each other. Not exactly sure why this happens? I presume this may be a precision issue or something, maybe, really not sure..

Relevant portion of my code:

uint width = image.width;
uint height = image.height;

uint originX = width / 2;
uint originY = height;

float radians = angleDegrees * PI / 180.0f;

float sine = sin(radians);
float cosine = cos(radians);
for (uint r = 0; r < height; r++)
{
    for (uint p = 0; p < width; p++)
    {
        int translatedR = r - originY;
        int translatedP = p - originX;

        int rotatedR = round(translatedP * sine + translatedR * cosine) + originY;
        int rotatedP = round(translatedP * cosine - translatedR * sine) + originX;

        size_t index = (x + rotatedP + (frameBufferWidth * y)) + (frameBufferWidth * rotatedR);
        size_t index2 = p + (image.width * r);

        if (!image.pixels[index2].a)
            continue;
        
        frameBuffer[index].r = image.pixels[index2].r;
        frameBuffer[index].g = image.pixels[index2].g;
        frameBuffer[index].b = image.pixels[index2].b;
    }
}

Solution

  • Your rotation algorithm visits pixels on the sprite from top to bottom and left to right, thus guaranteeing that all the source pixels will be visited; for each pixel, it computes the coordinates of the corresponding pixel on the frame buffer, and copies the pixel there.

    Unfortunately, this does not guarantee that all of the pixels that must be painted on the frame buffer will be painted.

    This is due to the imprecision inherent in any attempt to calculate integers (pixel coordinates) by multiplying integers by non-integers: the calculations naturally do not yield all of the desired target coordinates, while they yield some of the target coordinates twice.

    The result is that during the rendering of a single frame, some pixels of the frame buffer are left unpainted, while some pixels are painted and then needlessly re-painted.

    So, I am afraid I am going to disappoint you: It is not simple to solve.

    However, with enough work, (a lot more work than what you have done so far,) it is possible.

    Roughly, here is what you need to do:

    First of all, you need to compute how the rectangle of your sprite maps to a rectangle on the frame buffer, after rotation has been applied. Unless the rotation is a multiple of 90 degrees, this frame buffer rectangle will be slanted.

    You will be visiting the pixels of the frame buffer rectangle raster-line by raster-line, and within each raster line, one by one, as the following diagram shows:

                          ####
                         ##########
                         ###############
                        #####################
                        ##########################
                       ##########################
                            #####################
                                 ###############
                                      ##########
                                           ####
    

    This guarantees that all of the pixels in the frame buffer that must be painted, will be painted.

    The value of each pixel on the frame buffer must come from visiting pixels on the sprite along slanted lines, where the slope of the lines is dictated by the amount of rotation applied.

    To visit pixels on a sprite along a slanted line, I would advise you to read up on Bresenham's Line Algorithm. (Wikipedia.) It is a lot faster (insanely faster) than performing several multiplications per pixel to calculate each coordinate.

    Here it is, in action: https://www.youtube.com/watch?v=AApyC2a4KVw

    This was done by me in the early 1990s, in C++ and assembly, using the 320x200 pixel, 256 color mode of the VGA. By today's standards it is laughable, but back then it was quite something!