From 027b973d7550ac2e72e5de1e2155e26b35f16353 Mon Sep 17 00:00:00 2001 From: tslil clingman <> Date: Sun, 12 Sep 2021 00:17:24 -0400 Subject: More efficient surface rendering! Now, outside of sprites, every pixel on the screen is draw at most once. Hoorah. Unfortunately things still seem to be a little slow, perhaps its the z_buffer access or the fact that vertical floor slice drawing is bad? --- floors.png | Bin 399 -> 0 bytes src/main.zig | 26 +++++------ src/raycast.zig | 141 +++++++++++++++++++++++++++----------------------------- surfaces.png | Bin 0 -> 637 bytes 4 files changed, 81 insertions(+), 86 deletions(-) delete mode 100644 floors.png create mode 100644 surfaces.png diff --git a/floors.png b/floors.png deleted file mode 100644 index 19254e7..0000000 Binary files a/floors.png and /dev/null differ diff --git a/src/main.zig b/src/main.zig index 218ba92..c44b5e6 100644 --- a/src/main.zig +++ b/src/main.zig @@ -55,7 +55,7 @@ pub fn main() !void { } map.cells.items[16 * 7 + 7] = level.Cell{ .height = constants.MAX_HEIGHT, .wall_texture = 1 }; - map.cells.items[16 * 7 + 6] = level.Cell{ .height = constants.MAX_HEIGHT / 4, .wall_texture = 1, .floor_texture = 0 }; + map.cells.items[16 * 7 + 6] = level.Cell{ .height = constants.MAX_HEIGHT / 4, .wall_texture = 1, .floor_texture = 1 }; try map.objects.append(level.Object{ .pos_x = 5.5, .pos_y = 7.5, .pos_z = 1, .texture = 2, .height = 1, .width = 1 }); try map.objects.append(level.Object{ .pos_x = 4, .pos_y = 8.5, .texture = 1, .height = 1, .width = 1 }); @@ -103,18 +103,18 @@ pub fn main() !void { defer objects_sprite.destroy(); // Load floor image - var floors_image = try sf.Image.createFromFile("floors.png"); - defer floors_image.destroy(); + var surfaces_image = try sf.Image.createFromFile("surfaces.png"); + defer surfaces_image.destroy(); - var rendered_floors_texture = try sf.Texture.create(.{ .x = constants.PlaneWidth, .y = constants.PlaneHeight }); - defer rendered_floors_texture.destroy(); - rendered_floors_texture.setSmooth(false); + var rendered_surfaces_texture = try sf.Texture.create(.{ .x = constants.PlaneWidth, .y = constants.PlaneHeight }); + defer rendered_surfaces_texture.destroy(); + rendered_surfaces_texture.setSmooth(false); - var rendered_floors_sprite = try sf.Sprite.createFromTexture(rendered_floors_texture); - defer rendered_floors_sprite.destroy(); + var rendered_surfaces_sprite = try sf.Sprite.createFromTexture(rendered_surfaces_texture); + defer rendered_surfaces_sprite.destroy(); - rendered_floors_sprite.setPosition(.{ .x = 0, .y = 0 }); - rendered_floors_sprite.setScale(.{ .x = constants.HFact, .y = constants.VFact }); + rendered_surfaces_sprite.setPosition(.{ .x = 0, .y = 0 }); + rendered_surfaces_sprite.setScale(.{ .x = constants.HFact, .y = constants.VFact }); // Start your engines! //-------------------------------------------------------------------------- @@ -169,9 +169,9 @@ pub fn main() !void { window, walls_sprite, objects_sprite, - floors_image, - rendered_floors_texture, - rendered_floors_sprite, + surfaces_image, + rendered_surfaces_texture, + rendered_surfaces_sprite, map, ); diff --git a/src/raycast.zig b/src/raycast.zig index 1b78983..2b55f49 100644 --- a/src/raycast.zig +++ b/src/raycast.zig @@ -114,29 +114,29 @@ pub fn Player(PlaneWidth: f32, PlaneHeight: f32) type { window: RenderWindow, walls_sprite: Sprite, objects_sprite: Sprite, - floors_image: Image, - rendered_floors_texture: Texture, - rendered_floors_sprite: Sprite, + surfaces_image: Image, + rendered_surfaces_texture: Texture, + rendered_surfaces_sprite: Sprite, map: level.Map, ) !void { - // TODO: this is wasteful -- we render the floors and draw the - // texture, draw over that for the walls, then change the floor - // texture for the tops of things and blank everywhere else, then - // redraw the floor texture. - - // First render the floors - try self.renderFloorsToTexture(floors_image, rendered_floors_texture, map); - window.draw(rendered_floors_sprite, null); - - // then reset the z_buffer + // Fist reset the z_buffer var i: usize = 0; while (i < self.z_buffer.len) : (i += 1) { self.z_buffer.set(i, std.math.inf(f32)); } - // then render all the walls and populate the z_buffer - try self.renderCells(window, walls_sprite, floors_image, rendered_floors_texture, map); - window.draw(rendered_floors_sprite, null); + var pixels = [_]Colour{Colour.fromRGBA(0, 0, 0, 0)} ** (PlaneWidth * PlaneHeight); + + // then draw all the walls and populate the z_buffer, while also + // rendering the surfaces below the horizon to the pixel array + self.renderCells(window, walls_sprite, surfaces_image, map, &pixels); + + // then render the ceilings to our pixel array + self.renderCeilingsToTexture(surfaces_image, map, &pixels); + + // we're now ready to draw the surfaces + try rendered_surfaces_texture.updateFromPixels(&pixels, null); + window.draw(rendered_surfaces_sprite, null); // use the z_buffer to render sprites self.renderObjects(window, objects_sprite, map); @@ -220,12 +220,10 @@ pub fn Player(PlaneWidth: f32, PlaneHeight: f32) type { self: *@This(), window: RenderWindow, walls_sprite: Sprite, - floors_image : Image, - rendered_floors_texture : Texture, + floors_image: Image, map: level.Map, - ) !void { - var pixels = [_]Colour{Colour.fromRGBA(0, 0, 0, 0)} ** (PlaneWidth * PlaneHeight); - + pixels: []Colour, + ) void { // This is a TERRIBLE hack: for whatever reason *linearly* // interpolating on the direction vectors gives // perspective-correct-seeming walls! @@ -279,6 +277,7 @@ pub fn Player(PlaneWidth: f32, PlaneHeight: f32) type { dist_x = (self.pos_y - @intToFloat(f32, ipos_y)) * dx_for_y_step; } + var top: f32 = undefined; var distance: f32 = 0; var still_drawing = true; var highest_point: f32 = 0; @@ -302,7 +301,7 @@ pub fn Player(PlaneWidth: f32, PlaneHeight: f32) type { // Is there a wall? if (cell.height > 0) { // project the top of the wall - var top = PlaneHeight / 2 + PlaneDist * (cell.height - self.height) / distance; + top = PlaneHeight / 2 + PlaneDist * (cell.height - self.height) / distance; // Does the wall extend above what we've draw? if (top > highest_point) { @@ -340,61 +339,60 @@ pub fn Player(PlaneWidth: f32, PlaneHeight: f32) type { } highest_point = top; } + } - // do we potentially draw the top of this cell? - if (highest_point < PlaneHeight / 2) { - if (dist_y < dist_x) { - distance = dist_y; - } else { - distance = dist_x; - } + // do we potentially draw the top of this cell? + if (highest_point < PlaneHeight / 2) { + if (dist_y < dist_x) { + distance = dist_y; + } else { + distance = dist_x; + } - // Note: next_top can never exceed PlaneHeight / 2 - // in the body of the next block. If the wall is - // taller than us the back edge is lower than the - // front one so this check will fail as we just drew - // it (or higher than it). If the wall is shorter - // then the back edge is at most the horizon. - const next_top = PlaneHeight / 2 + PlaneDist * (cell.height - self.height) / distance; - - // only if we can see some part of it - if (next_top > highest_point) { - top = highest_point; - while (top <= next_top and top < PlaneHeight / 2) : (top += 1) { - const row_dist = (self.height - cell.height) * PlaneDist / (PlaneHeight / 2 - top); - const ptop = @floatToInt(usize, top + 1); - const itop = @floatToInt(usize, PlaneHeight) - ptop; - - // draw the correct pixel - const sx = std.math.modf(self.pos_x + row_dist * cosra); - const sy = std.math.modf(self.pos_y + row_dist * sinra); - const toff = cell.floor_texture * @floatToInt(c_uint, constants.TextureDim); - const px = @floatToInt(c_uint, constants.TextureDim * std.math.fabs(sx.fpart)); - const py = @floatToInt(c_uint, constants.TextureDim * std.math.fabs(sy.fpart)); - const val = floors_image.getPixel(.{ .x = toff + px, .y = py }); - pixels[itop * @floatToInt(usize, PlaneWidth) + col] = val; - - // record in the z_buffer + // Note: next_top can never exceed PlaneHeight / 2 + // in the body of the next block. If the wall is + // taller than us the back edge is lower than the + // front one so this check will fail as we just drew + // it (or higher than it). If the wall is shorter + // then the back edge is at most the horizon. + const next_top = PlaneHeight / 2 + PlaneDist * (cell.height - self.height) / distance; + + // only if we can see some part of it + if (next_top > highest_point) { + top = highest_point; + while (top <= next_top and top < PlaneHeight / 2) : (top += 1) { + const row_dist = (self.height - cell.height) * PlaneDist / (PlaneHeight / 2 - top); + const ptop = @floatToInt(usize, top + 1); + const itop = @floatToInt(usize, PlaneHeight) - ptop; + + // draw the correct pixel + const sx = std.math.modf(self.pos_x + row_dist * cosra); + const sy = std.math.modf(self.pos_y + row_dist * sinra); + const toff = cell.floor_texture * @floatToInt(c_uint, constants.TextureDim); + const px = @floatToInt(c_uint, constants.TextureDim * std.math.fabs(sx.fpart)); + const py = @floatToInt(c_uint, constants.TextureDim * std.math.fabs(sy.fpart)); + const val = floors_image.getPixel(.{ .x = toff + px, .y = py }); + pixels[itop * @floatToInt(usize, PlaneWidth) + col] = val; + + // record in the z_buffer only if we're above the floor! + if (cell.height > 0) { const index = col * @floatToInt(usize, PlaneHeight) + ptop; self.z_buffer.set(index, row_dist); } - highest_point = top; } + highest_point = top; } } } } - try rendered_floors_texture.updateFromPixels(&pixels, null); } - fn renderFloorsToTexture( + fn renderCeilingsToTexture( self: @This(), - floors_image: Image, - rendered_floors_texture: Texture, + surfaces_image: Image, map: level.Map, - ) !void { - var pixels = [_]Colour{Colour.fromRGBA(0, 0, 0, 0)} ** (PlaneWidth * PlaneHeight); - + pixels: []Colour, + ) void { // Again, another TERRIBLE hack: we do the same nasty linear // interpolation trick and for whatever reason the floors look fine. const cos_first = std.math.cos(self.ang + 0.5 * FOV); @@ -403,11 +401,9 @@ pub fn Player(PlaneWidth: f32, PlaneHeight: f32) type { const sin_last = std.math.sin(self.ang - 0.5 * FOV); var row: usize = 0; - while (row < PlaneHeight) : (row += 1) { - if (row == PlaneHeight / 2) continue; - const frow = std.math.fabs(PlaneHeight / 2 - @intToFloat(f32, row)); - const scale = if (row < PlaneHeight / 2) constants.MAX_HEIGHT - self.height else self.height; - const row_dist = scale * PlaneDist / frow; + while (row < PlaneHeight / 2) : (row += 1) { + const frow = (PlaneHeight / 2 - @intToFloat(f32, row)); + const row_dist = (constants.MAX_HEIGHT - self.height) * PlaneDist / frow; const dx_step = row_dist * (cos_last - cos_first) / PlaneWidth; const dy_step = row_dist * (sin_last - sin_first) / PlaneWidth; @@ -430,20 +426,19 @@ pub fn Player(PlaneWidth: f32, PlaneHeight: f32) type { const ix = @floatToInt(i32, sx.ipart); const iy = @floatToInt(i32, sy.ipart); - if (map.inBounds(ix, iy)) { + const index = col * @floatToInt(usize, PlaneHeight) + @floatToInt(usize, PlaneHeight - 1) - row; + if (map.inBounds(ix, iy) and row_dist < self.z_buffer.get(index)) { const cell = map.lookup(ix, iy); - const tex = if (row > PlaneHeight / 2) cell.floor_texture else cell.ceiling_texture; - const toff = tex * @floatToInt(c_uint, constants.TextureDim); + const toff = cell.ceiling_texture * @floatToInt(c_uint, constants.TextureDim); const px = @floatToInt(c_uint, constants.TextureDim * std.math.fabs(sx.fpart)); const py = @floatToInt(c_uint, constants.TextureDim * std.math.fabs(sy.fpart)); - const val = floors_image.getPixel(.{ .x = toff + px, .y = py }); + const val = surfaces_image.getPixel(.{ .x = toff + px, .y = py }); pixels[row * @floatToInt(usize, PlaneWidth) + col] = val; } } } - try rendered_floors_texture.updateFromPixels(&pixels, null); } }; } diff --git a/surfaces.png b/surfaces.png new file mode 100644 index 0000000..f699b8b Binary files /dev/null and b/surfaces.png differ -- cgit v1.3.1