Log In  

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:

Cart #sallyneptunefloors-0 | 2022-03-09 | Code ▽ | Embed ▽ | Forks ▽ | No License
1

P#108307 2022-03-09 02:36

:: shy
2

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
P#108316 2022-03-09 06:56 ( Edited 2022-03-09 07:05)
1

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!

P#108318 2022-03-09 07:34
:: shy

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.

P#108320 2022-03-09 08:47 ( Edited 2022-03-09 08:53)

or use Poom engine 😜

P#108321 2022-03-09 09:15

@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.

P#108323 2022-03-09 12:22

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.

P#108325 2022-03-09 13:19 ( Edited 2022-03-09 13:32)

you indeed have depth information
you could pset the grid corners to improve sense of speed

P#108327 2022-03-09 14:33

@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
P#108330 2022-03-09 15:11

@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.

P#110320 2022-04-16 13:23

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

P#110322 2022-04-16 14:08

[Please log in to post a comment]

Follow Lexaloffle:        
Generated 2022-09-28 12:29:07 | 0.039s | Q:27