Clément
Clément

Reputation: 109

How to optimize floor and ceiling rendering in a 2.5D engine?

I am making a 2.3D environment with raycasting for the walls and scanline for the floors. Here is the function I am using to draw the floors and ceilings.

void    cast_floor_and_ceiling(t_game *game, t_floor_ceiling *f, t_rendering_threads *thread)
{
    int     px, py, cell_x, cell_y;
    int     floor_tx, floor_ty, ceiling_tx, ceiling_ty;
    float   floor_x, floor_y, ceiling_x, ceiling_y;
    float   step_x, step_y, row_distance, pos_z;
    int     y, x, p;
    Uint32  ceiling_color, floor_color;

    // Set horizon position and texture pointers
    f->horizon = (WIND_HEIGHT / 2) + CAM_SHIFT;
    f->ceiling_pixels = game->textures.ceiling.pixels;
    f->floor_pixels = game->textures.floor.pixels;

    // Calculate ray directions for floor and ceiling casting
    f->ray_dir_x_0 = PLAYER_DIR_X - PLAYER_CAM_X;
    f->ray_dir_y_0 = PLAYER_DIR_Y - PLAYER_CAM_Y;
    f->ray_dir_x_1 = PLAYER_DIR_X + PLAYER_CAM_X;
    f->ray_dir_y_1 = PLAYER_DIR_Y + PLAYER_CAM_Y;

    // Render Ceiling
    pos_z = 0.5 * WIND_HEIGHT - (PLAYER_HEIGHT / 2);
    for (y = 0; y < f->horizon; y++)
    {
        // Distance from the player to the current row
        p = (WIND_HEIGHT / 2) - y + CAM_SHIFT;
        row_distance = (pos_z / p) * 2;

        // Calculate step size for texture sampling
        step_x = row_distance * (f->ray_dir_x_1 - f->ray_dir_x_0) / WIND_WIDTH;
        step_y = row_distance * (f->ray_dir_y_1 - f->ray_dir_y_0) / WIND_WIDTH;

        // Compute the starting position for ceiling drawing
        ceiling_x = PLAYER_X + row_distance * f->ray_dir_x_0 + thread->start * step_x;
        ceiling_y = PLAYER_Y + row_distance * f->ray_dir_y_0 + thread->start * step_y;

        // Iterate through the row
        for (x = thread->start; x < thread->end; x++)
        {
            cell_x = (int)ceiling_x;
            cell_y = (int)ceiling_y;

            // Check if the ceiling tile is valid
            if (cell_x >= 0 && cell_y >= 0 && cell_x < MAP_WIDTH && cell_y < MAP_HEIGHT
                && ((MAPS[LEVEL][cell_y][cell_x] == EMPTY || MAPS[LEVEL][cell_y][cell_x] == TRIGGER)
                || IS_HALF_BLOCK_UP(MAPS[LEVEL][cell_y][cell_x])))
            {
                if (f->ceiling_pixels)
                {
                    // Compute texture coordinates
                    ceiling_tx = ((int)(game->textures.ceiling.width * (ceiling_x - cell_x)))
                        & (game->textures.ceiling.width - 1);
                    ceiling_ty = ((int)(game->textures.ceiling.height * (ceiling_y - cell_y)))
                        & (game->textures.ceiling.height - 1);

                    // Fetch ceiling pixel color
                    ceiling_color = f->ceiling_pixels[game->textures.ceiling.width * ceiling_ty + ceiling_tx];

                    // Set pixel if it's not occluded
                    px = x;
                    py = y;
                    if (check_z_buffer(game, py * WIND_WIDTH + px, row_distance))
                        f->pixels[py * WIND_WIDTH + px] = ceiling_color;
                }
            }
            ceiling_x += step_x;
            ceiling_y += step_y;
        }
    }

    // Render Floor
    pos_z = 0.5 * WIND_HEIGHT + (PLAYER_HEIGHT / 2);
    for (y = f->horizon; y < WIND_HEIGHT; y++)
    {
        // Distance from the player to the current row
        p = y - (WIND_HEIGHT / 2) - CAM_SHIFT;
        row_distance = (pos_z / p) * 2;

        // Calculate step size for texture sampling
        step_x = row_distance * (f->ray_dir_x_1 - f->ray_dir_x_0) / WIND_WIDTH;
        step_y = row_distance * (f->ray_dir_y_1 - f->ray_dir_y_0) / WIND_WIDTH;

        // Compute the starting position for floor drawing
        floor_x = PLAYER_X + row_distance * f->ray_dir_x_0 + thread->start * step_x;
        floor_y = PLAYER_Y + row_distance * f->ray_dir_y_0 + thread->start * step_y;

        // Iterate through the row
        for (x = thread->start; x < thread->end; x++)
        {
            cell_x = (int)floor_x;
            cell_y = (int)floor_y;

            // Check if the floor tile is valid
            if (cell_x >= 0 && cell_y >= 0 && cell_x < MAP_WIDTH && cell_y < MAP_HEIGHT
                && ((MAPS[LEVEL][cell_y][cell_x] == EMPTY || MAPS[LEVEL][cell_y][cell_x] == TRIGGER)
                || IS_HALF_BLOCK_DOWN(MAPS[LEVEL][cell_y][cell_x])))
            {
                if (f->floor_pixels)
                {
                    // Compute texture coordinates
                    floor_tx = ((int)(game->textures.floor.width * (floor_x - cell_x)))
                        & (game->textures.floor.width - 1);
                    floor_ty = ((int)(game->textures.floor.height * (floor_y - cell_y)))
                        & (game->textures.floor.height - 1);

                    // Fetch floor pixel color
                    floor_color = f->floor_pixels[game->textures.floor.width * floor_ty + floor_tx];

                    // Set pixel if it's not occluded
                    px = x;
                    py = y;
                    if (check_z_buffer(game, py * WIND_WIDTH + px, row_distance))
                    {
                        set_z_buffer(game, row_distance, py * WIND_WIDTH + px);
                        f->pixels[py * WIND_WIDTH + px] = floor_color;
                    }
                }
            }
            floor_x += step_x;
            floor_y += step_y;
        }
    }
}

enter image description here

As you can see, I have different floor and ceiling heights, and the only way I’ve found to adjust their height is by modifying pos_z (the camera height).

The problem is that pos_z is set outside the loop, and since nearly everything else depends on it, I haven’t found a way to adjust floor or ceiling heights dynamically within the loop.

Right now, I render all standard floors and ceilings using the function above. Then, I call the function below, which scans the entire screen again to find floor tiles with an offset height. This function runs for every floor height that should be visible based on the player's height. However, since ceilings require a similar approach, the whole screen ends up being scanned about five times per frame, leading to a massive FPS drop.

I managed to improve performance by splitting the rendering across multiple threads, but I still feel like my approach is highly inefficient.

Is there a way to merge all these functions into one, so that it's called only once per frame and can correctly calculate the height of every floor pixel without redundant calculations?

// Draws a floor tile based on the given type
static void draw_floor_tile(t_game *game, t_floor_ceiling *f,
                t_rendering_threads *thread, char type)
{
    int     px, cell_x, cell_y, floor_tx, floor_ty;
    Uint32  floor_color;
    float   floor_x, floor_y, step_x, step_y, row_distance, pos_z;
    int     y, x, p, row_start;
    double  height;

    // Determine floor height based on tile type
    height = (type == WALL_0) * 0.402
        + (type == WALL_1) * 0.202
        + (type == WALL_2) * 0.0
        + (type == WALL_3) * -0.200;

    // Calculate perspective height and set pixel data
    pos_z = height * WIND_HEIGHT + (PLAYER_HEIGHT / 2);
    f->floor_pixels = game->textures.floor_light.pixels;

    // Iterate through each row below the horizon
    y = f->horizon - 1;
    while (++y < WIND_HEIGHT)
    {
        // Calculate row distance based on perspective
        p = y - (WIND_HEIGHT / 2) - CAM_SHIFT;
        row_distance = (pos_z / p) * 2;

        // Compute step increments for texture sampling
        step_x = row_distance * (f->ray_dir_x_1 - f->ray_dir_x_0) / WIND_WIDTH;
        step_y = row_distance * (f->ray_dir_y_1 - f->ray_dir_y_0) / WIND_WIDTH;

        // Initialize floor coordinates
        floor_x = PLAYER_X + row_distance * f->ray_dir_x_0 + thread->start * step_x;
        floor_y = PLAYER_Y + row_distance * f->ray_dir_y_0 + thread->start * step_y;

        row_start = y * WIND_WIDTH;
        x = thread->start - 1;

        // Process each column in the current row
        while (++x < thread->end)
        {
            cell_x = (int)floor_x;
            cell_y = (int)floor_y;

            // Check if the current tile matches the target type
            if (cell_x >= 0 && cell_y >= 0
                && cell_x < MAP_WIDTH && cell_y < MAP_HEIGHT
                && MAPS[LEVEL][cell_y][cell_x] == type)
            {
                // Get texture coordinates
                floor_tx = ((int)(game->textures.floor_light.width * (floor_x - cell_x)))
                    & (game->textures.floor_light.width - 1);
                floor_ty = ((int)(game->textures.floor_light.height * (floor_y - cell_y)))
                    & (game->textures.floor_light.height - 1);

                // Retrieve floor texture color
                floor_color = f->floor_pixels[game->textures.floor_light.width * floor_ty + floor_tx] | 0x010101;
                px = x;

                // Store pixel if it passes the depth test
                if (check_z_buffer(game, row_start + px, row_distance))
                    f->pixels[row_start + px] = floor_color;
            }
            // Move to the next floor tile
            floor_x += step_x;
            floor_y += step_y;
        }
    }
}

Upvotes: 0

Views: 71

Answers (0)

Related Questions