Hi!
I am working on a 3D raycaster game. It's largely working; see the gif below:
I am basically using zep's cast demo as a starter - if you look through my code you will see his draw_3d() function, albeit pretty heavily hacked at this point. I use tline() to draw my walls, and i apply a fillp() based on distance from the camera to get a (currently ugly) dithered lighting effect.
I would like to do the same thing for my floors! Except, I am drawing my floors vertically. See the next gif:
I only draw a floor (vertical line() downward) if my casted ray hits a wall OR a new tile type (thats how i get different floor colors).
Anyone have a cpu-conservative tip for how I could get a gradient similar to my walls on the floor? I COULD draw the floors first as a flood-fill, but this would ignore different colors and elevations. I can't think of any way to apply a smart horizontal-fill strategy, which would allow me to apply fillp() based on distance from the camera.
Here's the current cart, if anyone is interested:
Best idea I have is to use bitplanes (see 0x5f5e): https://pico-8.fandom.com/wiki/Memory#Hardware_state
Flag one bitplane of the display for floor gradients, and draw your horizontal gradients as normal. Then draw all your vertical lines in a separate draw pass on the other three bitplanes, and do a palette remapping so that the colors look right. Any wall draws can draw to all four bitplanes via poke(0x5f5e,-1)
, resetting it to default behavior.
In case this isn't clear:
function _draw() cls() --horizontal draw on 4th bitplane: poke(0x5f5e,0b10001000) fillp(0x7fdf) rectfill(0,64,128,76,8) --color 8 is 0b1000 in binary, so only draws onto 4th bitplane -- might be able to avoid cls() call entirely by using solid fillp()s in a default draw fillp(░) rectfill(0,76,128,90,8) fillp(▒) rectfill(0,90,128,110,8) fillp(█) rectfill(0,110,128,128,8) --vertical draw on remaining 3 bitplanes: poke(0x5f5e,0b01110111) srand(0) for i=32,127 do line(i,rnd(32)+64,i,128,i/32) --only draws colors 0-7 (since 8+ draws onto the fourth bitplane, will be ignored) end --screen palette remap, since colors are weird now (shadow colors are 8+, rest of drawing will have to use this mapping now) pal({[0]=0,4,3,1,7,7,7,7, 5,10,11,12},1) end |
won’t work - you need multiple floor heights = multiple gradients in the same line
I tried something like for poom (but it’s a perf hog):
- for each x
- during raycasting records y coords of start-end of floor (with start depth/end depth)
- when rendering completes, iterate over all vertical spans, and find where it ends
- draw an horiz line with proper depth
- search for next horiz line (if any)
this extremely cpu heavy and why Poom renders floors as polygons!
Ah, that's true @freds72, varying heights would have the same/incorrect gradients. Might be good enough if only subtle shading in the distance, but won't be very accurate.
One other idea, though this is going further into the dark magic. Store shadow-only gradient slices in high memory and use poke(0x5f2c,135)
for a 90 degree rotated display, so screen memory is stored in vertical slices instead of horizontal ones. Since flat shadows at every relative height to camera can be calc'd in advance, you can just memcpy() the correct slice of that data to the screen, right before each vertical line draws. After that, use bitplanes to draw the color data, as described above.
However, every draw call would need swapped x/y values at this point, and memcpy isn't free. It would be a lot of work for minimal visual gains.
Instead, maybe just flatly shade your floors darker when their heights are lower than the current camera's height, and avoid bitplanes entirely? You could even add more gradient to your line draws based on how much lower they are.
@shy Thanks for the great tip! I thought something like this might be possible (masking parts of the screen before drawing floors), but I wasn't sure how to accomplish it. Of course, as @freds72 pointed out, I have different floor heights, so it may look janky.
I might still give this a try. I like how simple a raycaster is - I am way below my token allowance. I have some ambitions of adding ceilings and upper walls, a la doom, and while I think I have enough CPU to draw the upper walls I think drawing textured ceilings and floors is too much, so I don't even want to try.
Checking to see if I can post here, I heard it was locked...
I should have included this image up top. Originally, i was drawing a line for the floor every time my casted ray hit a new tile (this is the default cast.p8 behavior). This is a BIG drain on CPU, but it let me do things like capping each line off with a pset() of a different color, which gave me a nice grid effect that really helps to communicate movement to the player, as well as distance.
I might try a less CPU-intense way to get this grid again. Would be ideal to draw lines on top of my floors, but that's a whole bunch of calculations again. hmm...
I like @shy 's recomemndation of just using zheight as a function to recolor floors. That might give me SOMETHING. I might also try sprinkling around some dots that move correctly with perspective. Anything just to give some sensation of depth and movement to the floor.
you indeed have depth information
you could pset the grid corners to improve sense of speed
@freds72 I have the grid, but I am only finding y-pixel location onscreen when my ray hits a wall. To pset the grid corners, I would need to calculate y-location at each corner, wouldn't I? I like this idea though, it would just give a sense of motion. I would like to be able to pset these during my ray-march across the screen.
Is this possible, or would I need a separate loop to look at all the corners in my current FOV? now that I think about it, that might not actually be so painful, especially if I only look at corners close to the camera.
Relevant code is below, a little cleaned up for readability.
while (skip) do if (dist_x < dist_y) then ix=ix+dir_x -- last_dir = 0 dist_y = dist_y - dist_x tdist = tdist + dist_x dist_x = skip_x else iy=iy+dir_y -- last_dir = 1 dist_x = dist_x - dist_y tdist = tdist + dist_y dist_y = skip_y end -- prev cel properties local mcol0=mcol -- new cel properties mcol=mget(ix,iy) if mcol != mcol0 then -- only draw if i have hit a new tile type on the map. local celz0=celz local tiletype = tileinfo[mcol\16] local scale = unit/tdist poke(0x5F3A, tiletype[3]) poke(0x5F3B, tiletype[4]) local g_color0 = g_color g_color = tiletype[2] local col = mcol%16 celz=16-col*.5 -- inlined for speed if (col==15) skip = false if (tdist > drawdist) skip = false --max draw distance -- screen space local sy1 = ((celz0-z)*scale)+horizon -- inlined -- draw ground to new point if (sy1 < sy) then line(sx,sy1-1,sx,sy,g_color0) -- floor drawing pset(sx,sy,0) sy=sy1 end --lower floor? if (celz>celz0) then add(depthi,{tdist,sy1}) -- need this info to draw sprites end -- draw wall if higher if (celz < celz0) then sy1 = ((celz-z)*scale) + horizon if (sy1 < sy) then local wallx if dist_x == skip_x then wallx = .5*((y + tdist*vy)%2) else wallx = .5*((x + tdist*vx)%2) end fillp(patterns[min(flr(tdist/3),8)]) tline(sx,sy1-1,sx,sy,wallx,0,0,(.5/scale)) pset(sx,sy,0) pset(sx,sy1-1,0) sy=sy1 fillp() add(depthi,{tdist,sy1}) -- need this info for drawing sprites end end end end -- skipping |
@freds72 just wanted to say thanks for the help here! I ultimately took your advice and just pset the grid corners (plus an extra few pixels and it helps to give a nice, subtle sense of speed. Ultimately, better people than me have tried to crack this problem, and I don't think I'm about to do it.
@shy I still like your gradient idea. I think handling the differing floor heights and colors would have been a bridge too far for me. However, I did find a non-pico game, Anarch, which is a raycaster with a similar look to what I am pursuing. I am not sure how exactly they implement the floor, but I suspect they just offset the gradient by a fixed amount for different floor elevations, as opposed to perspective correcting. It still looks pretty good! I wish I could have cracked this one but I think it's a bit tough.
remember that pc game do not have the same issue as being forced to use tline or sspr for fast rendering
anarch floor is a mode7 render
[Please log in to post a comment]