I'm tinkering with a voxel space rendering tech demo as a PlayStation homebrew. After one weekend of work I'm getting decent results (like, 10-15 FPS) and I've yet to use the DMA, the GTE or even polylines primitives.
It's refreshing to dust up trigonometry and good old low-level optimization tricks. When the scratchbuffer has 1 KiB and the stack can only use a fraction of that, it makes me realize how spoiled I'm at work with the microcontrollers we have, with threads being allocated 8 KiB of stack and backtraces with over 50 functions of C++ templates on it.
Graphics programming in the early to mid 1990s was pretty fun: write pixel data into the memory-mapped video RAM and it appears on the screen! A pointer to 0xA0000 was all you needed - no API or anything. The reason for the non-square-pixel 320×200 VGA mode they mention was that the video buffer took 64000 bytes, which fit into a 16-bit segment, making addressing it easy in 16-bit code/CPUs.
Though your extender could make things a little more annoying on that front :-P
(DJGPP and Free Pascal -which use the same "go32" extender by DJ Delorie- do not do a full linear mapping so you need to do a bit more juggling to get stuff on screen there)
This is taking a lot of inspiration from Doom, but the actual raycasting engine is more like Doom's predecessors, the most well-known of which is probably Wolfenstein 3D: perpendicular walls, constant floor and ceiling height. Wolf3D didn't have textured floors and ceilings because of performance reasons, but several other similar games had them. Doom and IIRC Duke Nukem as well used a BSP engine which was much more flexible (walls could intersect at any angle, variable floor and ceiling heights), although the levels were still "flat" (you couldn't have several "stories" inside a level, e.g. you couldn't design a bridge that you could walk over and under).
The Build engine didn't use BSP, it treated connections between sectors as portals and rasterized the walls as (90 degree rotated) trapezoids while performing clipping against those portals. This allowed it to have dynamic wall geometry (e.g. moving trains, rotating light fixtures, etc) as well as "room-over-room" setups as long as you couldn't see both rooms at the same time (in both Blood and Shadow Warrior they found a workaround for it allowing to create more "3D" spaces by making identically shaped sectors with the floor of one sector acting as a portal to the ceiling of the other sector - supposedly this wasn't "natively" supported by the engine, but it was flexible enough for the game studios who used it -without even having access to the source- to do it themselves).
The first level of Duke Nukem 3D does use a few Build tricks - e.g. another one is that sprites can be "axis aligned" instead of following the camera and they can also have collision - this can be used to create rudimentary 3D geometry by treating each sprite as an axis aligned quad and in the first level it is used to make a bridge between two buildings (right before the level exit button).
I just noticed that this might be one of the rare shooters with a female protagonist: the cat has a calico pattern, and those are almost always female (https://en.wikipedia.org/wiki/Calico_cat).
It's not that rare, is it? Off-hand, and very mainstream; Perfect Dark, Mirrors Edge, Dishonored (don't remember if it's the first or second one), Metroid and more are all kind of "shooters" with female protagonist, although maybe Mirror's Edge is more just "first-person" than "shooter" to be 100% accurate.
Not to mention the large selection of "RPG + FPS" where you can be either man or woman.
---------
Seems the author also realize the thing with the pattern and likely gender of the cat:
> After all, I do need to give the protagonist his fair share. [image] (Yes, I know it's a female, but call it convention rooted in dialect.)
A lot of boomer shooters nowadays have a female protagonist, e.g. Selaco[0], Supplice[1], The Citadel[2] and its sequel[3], Zortch[4] (and its upcoming sequel[5]), Nighmare Reaper[6], COVEN[7], Viscerafest[8], Hedon[9], etc. If anything i'd say that nowadays there are way more boomer shooters with female protagonists than not :-P (combining the tags "boomer shooter" with "female protagonist" on Steam search gives 143 results, though that includes games where you can either choose your character's gender or you play as a woman for a part of the game even if you play as a man for most of it).
This is terrific. I love reading about the creative process involved in a project like this, finding cool solutions to self-imposed boundaries.
I think the mix of highly rational reasoning and "it just feels right" is a killer combo too, it gives a rigorous basis for a lot of the decisions made, while also allowing for a strongly personal aesthetic to emerge. Very cool indeed.
As a fellow 3d-engine-with-foolishly-unreasonable-constraints developer, I love the detail in the explanations here and seeing the process you went through.
Much respect -- at first glance when I saw the animated gif I thought this was just a project making assets from scratch for an existing game engine (e.g. Doom or Wolf 3D) but then I realize it's creating all the game code from scratch too! (But using similar techniques from the old days). Amazing work.
I really loved that article. Creating games always seemed so daunting to me since I don't know a lot about how it's done, there are so many different processes involved. The solutions described here are so satisfyingly compact and so easy to understand given the simple constraints, and yet they produce an actual game that looks nice.
This is a wonderful deep dive into your project. I'm early days on creating pixel art style procedural art systems, and this gives me plenty to think about.
I love this! I have been working on a similar project, recreating the originale BBC Elite but with multiplayer networking. Though I have not limited myself as much (I use SDL).
This game looks great I really like the style it is inspiring.
The author seems to consider open-sourcing the engine, I would also be interested in the mentioned scripts for asset creation. Those scripts would make a great toolset for asset creation in this style.
Nice, i've used similar approach for the lighting in Post Apocalyptic Petra[0] though i did use per-pixel LUT offset calculation[1] because it uses a generic 3D triangle rasterizer (the levels are based on grids like in Tomb Raider but they're rendered as triangles). Later i added sprite support for another gamejam but i never ended up finishing it and the sprite support is very rudimentary (and unoptimized - i just noticed i'm doing the LUT lookup for every pixel when drawing shaded sprites which isn't necessary).
I did write a tool for generating the sprites from 3D models though[2]. It uses plain old OpenGL 1.1 to draw the sprite and grabs the framebuffer directly. It is drawn fullbright so i can paint the lighting directly on the sprite's texture (using a Krita plugin i wrote[3][4] - the model is something i threw together with Blender's default generated UV since i didn't care for the details).
I wonder if doing some sort of postprocessing (after rendering with with shading) like you do with your game would help with the finer details since i also found that rendering from 3D models to sprites creates very "mushy" results most of the time because of all the details getting lost. I notice the colors also become more saturated after postprocessing in your examples, is this after it finds the closest color in the palette or the result of the postprocess? I'd like to keep the overall hue+saturation of the model so maybe doing post-processing on a grayscale render to shade the shadows/dark areas but keep highlights as-is and then multiplying that with the fullbright image would produce results that wont shift the saturation.
>I notice the colors also become more saturated after postprocessing in your examples, is this after it finds the closest color in the palette or the result of the postprocess?
It's the result of the Blender compositor postprocessing, just keep in mind it falls apart once you go low enough in resolution (it's an image space thing after all), so I'm not sure if that helps your case.
How so? Doing this with modern OpenGL would be much simpler than the software rasterizer solution.
I think I'm gonna have to do it anyway, because some players claim they get nausea when playing at such low resolution (320x240), and the only way to give them higher resolutions that perform reasonably is to have it hardware accelerated.
Renderer is abstracted away already, but the real difference would probably be occlusion culling... With raycasting, I get it for free, but if I'd go down the hardware accelerated path I'd have to pick something more clever.
Raycasting and software rendering in general tends to scale poorly with resolution, even with vectorization and all the bells and whistles of modern CPUs.
Sure, but it becomes a question of how far you can push things. Maybe you raytrace the whole thing. Maybe there's some fractal geometry going on. Maybe you use a fisheye lens projection. Maybe your levels are dynamically tesselated. Maybe you have to do a few fancy tricks to achieve equivalent texturing etc.
But ignoring the GPU you have on your system is boring
Unless you plan on rendering the level on some very retro hardware (think S3 Virge, maybe Voodoo 1) you can render the entire level in OpenGL with just zbuffer and alpha tested sprites and it'll run perfectly fine - if anything with such low polycount, chances are you're going to make the renderer slower by trying to do occlusion culling on any GPU released in the 21st century :-P. If you pack the geometry in a few vertex buffers (for each unique texture) even per-frame, you'll get four digit FPS in any relatively modern GPU.
As an example this[0] video shows the benchmark from Post Apocalyptic Petra running on my previous GPU (RX 5700 XT) which all it does is build a per-frame (client-side) vertex-buffer in OpenGL 1.1 (the engine was made for actual retro PCs running DOS and Win9x so it does some rudimentary occlusion culling but that mainly affects 90s hardware, not anything released since 2000 or so). If anything, the rendering has so little overhead that half of the framerate is "eaten" by the FPS counter overlay :-P.
Thinking about modern games, a single character model probably has more vertices than my entire level (and yours probably), so it's definitely reasonable to expect occlusion culling for such simple geometry might actually reduce performance rather than increase it.
Yeah, even this model[0] i made a few years ago for a game i wanted to make for the OG xbox (which has a GeForce3-like GPU) has ~2230 triangles and the entire first level of Post Apocalyptic Petra is ~5800 triangles, so you could say that even a turn of the century 3D character has more or less the same polycount as an entire 90s level (the game i wanted to make would have many characters on screen so i kept the polycount low but i've heard games having 5-6K character or more - e.g. Kingdom Under Fire had ~10K triangles for the main character and a game like Dead or Alive where there are few characters on screen had 15-20K triangles).
Meanwhile more current games have much higher polycounts, easily going above 100K triangles - e.g. Dante from DMC5, a ~7 year old game, apparently has ~190K triangles and that had to run on the more anemic PS4/XBone hardware :-P (though i'm not sure if it used the full 190K model there or some cut down version).
I rendered some Source engine levels on a shitty laptop in 2012ish and they still rendered at perfectly acceptable FPS (30+) just by rendering all the geometry in the level in one shot.
I'm tinkering with a voxel space rendering tech demo as a PlayStation homebrew. After one weekend of work I'm getting decent results (like, 10-15 FPS) and I've yet to use the DMA, the GTE or even polylines primitives.
It's refreshing to dust up trigonometry and good old low-level optimization tricks. When the scratchbuffer has 1 KiB and the stack can only use a fraction of that, it makes me realize how spoiled I'm at work with the microcontrollers we have, with threads being allocated 8 KiB of stack and backtraces with over 50 functions of C++ templates on it.
Graphics programming in the early to mid 1990s was pretty fun: write pixel data into the memory-mapped video RAM and it appears on the screen! A pointer to 0xA0000 was all you needed - no API or anything. The reason for the non-square-pixel 320×200 VGA mode they mention was that the video buffer took 64000 bytes, which fit into a 16-bit segment, making addressing it easy in 16-bit code/CPUs.
> A pointer to 0xA0000 was all you needed
Though your extender could make things a little more annoying on that front :-P
(DJGPP and Free Pascal -which use the same "go32" extender by DJ Delorie- do not do a full linear mapping so you need to do a bit more juggling to get stuff on screen there)
This is taking a lot of inspiration from Doom, but the actual raycasting engine is more like Doom's predecessors, the most well-known of which is probably Wolfenstein 3D: perpendicular walls, constant floor and ceiling height. Wolf3D didn't have textured floors and ceilings because of performance reasons, but several other similar games had them. Doom and IIRC Duke Nukem as well used a BSP engine which was much more flexible (walls could intersect at any angle, variable floor and ceiling heights), although the levels were still "flat" (you couldn't have several "stories" inside a level, e.g. you couldn't design a bridge that you could walk over and under).
> Wolf3D didn't have textured floors and ceilings because of performance reasons, but several other similar games had them
Blake Stone Rise of the Triad used later versions of the Wolf3D engine and had textured floors/ceilings
> Doom and IIRC Duke Nukem as well used a BSP engine which was much more flexible
Duke Nukem (Build engine) did not use BSP
https://www.jonof.id.au/forum/topic-137.html#msg1548
> Duke Nukem as well used a BSP engine
The Build engine didn't use BSP, it treated connections between sectors as portals and rasterized the walls as (90 degree rotated) trapezoids while performing clipping against those portals. This allowed it to have dynamic wall geometry (e.g. moving trains, rotating light fixtures, etc) as well as "room-over-room" setups as long as you couldn't see both rooms at the same time (in both Blood and Shadow Warrior they found a workaround for it allowing to create more "3D" spaces by making identically shaped sectors with the floor of one sector acting as a portal to the ceiling of the other sector - supposedly this wasn't "natively" supported by the engine, but it was flexible enough for the game studios who used it -without even having access to the source- to do it themselves).
The first level of Duke Nukem 3D does use a few Build tricks - e.g. another one is that sprites can be "axis aligned" instead of following the camera and they can also have collision - this can be used to create rudimentary 3D geometry by treating each sprite as an axis aligned quad and in the first level it is used to make a bridge between two buildings (right before the level exit button).
Later on, in Shadow Warrior, you could even do that, i think they used portals to implement it and i remeber it was a pain to set up in the editor.
I thought at first it was just a skinned Wolfenstein 3D. Which is grossly unfair. A lot of work here.
I just noticed that this might be one of the rare shooters with a female protagonist: the cat has a calico pattern, and those are almost always female (https://en.wikipedia.org/wiki/Calico_cat).
> rare shooters with a female protagonist
It's not that rare, is it? Off-hand, and very mainstream; Perfect Dark, Mirrors Edge, Dishonored (don't remember if it's the first or second one), Metroid and more are all kind of "shooters" with female protagonist, although maybe Mirror's Edge is more just "first-person" than "shooter" to be 100% accurate.
Not to mention the large selection of "RPG + FPS" where you can be either man or woman.
---------
Seems the author also realize the thing with the pattern and likely gender of the cat:
> After all, I do need to give the protagonist his fair share. [image] (Yes, I know it's a female, but call it convention rooted in dialect.)
They're definitely rare. Mirror's Edge is almost 20 years old. Reaching back that far for an example just reinforces how rare it is.
If you tally all the FPS releases in a given year, a supermajority are going to have male protagonists.
> one of the rare shooters with a female protagonist
No, this isn't a Perfect Dark game
A lot of boomer shooters nowadays have a female protagonist, e.g. Selaco[0], Supplice[1], The Citadel[2] and its sequel[3], Zortch[4] (and its upcoming sequel[5]), Nighmare Reaper[6], COVEN[7], Viscerafest[8], Hedon[9], etc. If anything i'd say that nowadays there are way more boomer shooters with female protagonists than not :-P (combining the tags "boomer shooter" with "female protagonist" on Steam search gives 143 results, though that includes games where you can either choose your character's gender or you play as a woman for a part of the game even if you play as a man for most of it).
[0] https://store.steampowered.com/app/1592280/Selaco/
[1] https://store.steampowered.com/app/1693280/Supplice/
[2] https://store.steampowered.com/app/1378290/The_Citadel/
[3] https://store.steampowered.com/app/3371240/Beyond_Citadel/
[4] https://store.steampowered.com/app/2443360/Zortch/
[5] https://store.steampowered.com/app/3807500/Zortch_2/
[6] https://store.steampowered.com/app/1051690/Nightmare_Reaper/
[7] https://store.steampowered.com/app/1785940/COVEN/
[8] https://store.steampowered.com/app/1406780/Viscerafest/
[9] https://store.steampowered.com/app/1072150/Hedon_Bloodrite/
This is terrific. I love reading about the creative process involved in a project like this, finding cool solutions to self-imposed boundaries.
I think the mix of highly rational reasoning and "it just feels right" is a killer combo too, it gives a rigorous basis for a lot of the decisions made, while also allowing for a strongly personal aesthetic to emerge. Very cool indeed.
As a fellow 3d-engine-with-foolishly-unreasonable-constraints developer, I love the detail in the explanations here and seeing the process you went through.
Much respect -- at first glance when I saw the animated gif I thought this was just a project making assets from scratch for an existing game engine (e.g. Doom or Wolf 3D) but then I realize it's creating all the game code from scratch too! (But using similar techniques from the old days). Amazing work.
For some reason I irrationally like the posterization effect that's created when something is darkened to almost zero.
I respect the amount of work that goes into projects like this; I can't wait to be able to play it.
I really loved that article. Creating games always seemed so daunting to me since I don't know a lot about how it's done, there are so many different processes involved. The solutions described here are so satisfyingly compact and so easy to understand given the simple constraints, and yet they produce an actual game that looks nice.
This is a wonderful deep dive into your project. I'm early days on creating pixel art style procedural art systems, and this gives me plenty to think about.
Everything is perfect here. The hero, the graphics, the title... <3
I love this! I have been working on a similar project, recreating the originale BBC Elite but with multiplayer networking. Though I have not limited myself as much (I use SDL).
With the title I was expecting some notes about DeluxePaint, but it was still a nice read nonetheless. Wish you much success with the game! :-)
This game looks great I really like the style it is inspiring.
The author seems to consider open-sourcing the engine, I would also be interested in the mentioned scripts for asset creation. Those scripts would make a great toolset for asset creation in this style.
As a side note, the visual style in the game reminds me a lot of Exhumed / Powerslave :-).
This is beautiful. I wish one day I'll have to time for a project like that. Looking forward to buying it on Steam.
Really cool. It's also something LLM's are ridiculously bad at, so you kinda have to do it properly.
Nice, i've used similar approach for the lighting in Post Apocalyptic Petra[0] though i did use per-pixel LUT offset calculation[1] because it uses a generic 3D triangle rasterizer (the levels are based on grids like in Tomb Raider but they're rendered as triangles). Later i added sprite support for another gamejam but i never ended up finishing it and the sprite support is very rudimentary (and unoptimized - i just noticed i'm doing the LUT lookup for every pixel when drawing shaded sprites which isn't necessary).
I did write a tool for generating the sprites from 3D models though[2]. It uses plain old OpenGL 1.1 to draw the sprite and grabs the framebuffer directly. It is drawn fullbright so i can paint the lighting directly on the sprite's texture (using a Krita plugin i wrote[3][4] - the model is something i threw together with Blender's default generated UV since i didn't care for the details).
I wonder if doing some sort of postprocessing (after rendering with with shading) like you do with your game would help with the finer details since i also found that rendering from 3D models to sprites creates very "mushy" results most of the time because of all the details getting lost. I notice the colors also become more saturated after postprocessing in your examples, is this after it finds the closest color in the palette or the result of the postprocess? I'd like to keep the overall hue+saturation of the model so maybe doing post-processing on a grayscale render to shade the shadows/dark areas but keep highlights as-is and then multiplying that with the fullbright image would produce results that wont shift the saturation.
[0] https://bad-sector.itch.io/post-apocalyptic-petra
[1] https://codeberg.org/badsector/PetraEngine/src/commit/14ca16...
[2] http://runtimeterror.com/pages/iv/images/95ddebc51e4dfa8a5af...
[3] http://runtimeterror.com/tools/kritaview3d/
[4] http://runtimeterror.com/pages/iv/images/535f0e09e590d8a1731...
>I notice the colors also become more saturated after postprocessing in your examples, is this after it finds the closest color in the palette or the result of the postprocess?
It's the result of the Blender compositor postprocessing, just keep in mind it falls apart once you go low enough in resolution (it's an image space thing after all), so I'm not sure if that helps your case.
EDIT: Also, your project is very cool!
I hope they leveraged Mode X :)
It'd be more interesting if you made a similar looking game using modern APIs imo
How so? Doing this with modern OpenGL would be much simpler than the software rasterizer solution.
I think I'm gonna have to do it anyway, because some players claim they get nausea when playing at such low resolution (320x240), and the only way to give them higher resolutions that perform reasonably is to have it hardware accelerated.
Renderer is abstracted away already, but the real difference would probably be occlusion culling... With raycasting, I get it for free, but if I'd go down the hardware accelerated path I'd have to pick something more clever.
Raycasting and software rendering in general tends to scale poorly with resolution, even with vectorization and all the bells and whistles of modern CPUs.
Sure, but it becomes a question of how far you can push things. Maybe you raytrace the whole thing. Maybe there's some fractal geometry going on. Maybe you use a fisheye lens projection. Maybe your levels are dynamically tesselated. Maybe you have to do a few fancy tricks to achieve equivalent texturing etc.
But ignoring the GPU you have on your system is boring
Unless you plan on rendering the level on some very retro hardware (think S3 Virge, maybe Voodoo 1) you can render the entire level in OpenGL with just zbuffer and alpha tested sprites and it'll run perfectly fine - if anything with such low polycount, chances are you're going to make the renderer slower by trying to do occlusion culling on any GPU released in the 21st century :-P. If you pack the geometry in a few vertex buffers (for each unique texture) even per-frame, you'll get four digit FPS in any relatively modern GPU.
As an example this[0] video shows the benchmark from Post Apocalyptic Petra running on my previous GPU (RX 5700 XT) which all it does is build a per-frame (client-side) vertex-buffer in OpenGL 1.1 (the engine was made for actual retro PCs running DOS and Win9x so it does some rudimentary occlusion culling but that mainly affects 90s hardware, not anything released since 2000 or so). If anything, the rendering has so little overhead that half of the framerate is "eaten" by the FPS counter overlay :-P.
[0] https://www.youtube.com/watch?v=64ysz5rXkzw
That's really cool.
Thinking about modern games, a single character model probably has more vertices than my entire level (and yours probably), so it's definitely reasonable to expect occlusion culling for such simple geometry might actually reduce performance rather than increase it.
Yeah, even this model[0] i made a few years ago for a game i wanted to make for the OG xbox (which has a GeForce3-like GPU) has ~2230 triangles and the entire first level of Post Apocalyptic Petra is ~5800 triangles, so you could say that even a turn of the century 3D character has more or less the same polycount as an entire 90s level (the game i wanted to make would have many characters on screen so i kept the polycount low but i've heard games having 5-6K character or more - e.g. Kingdom Under Fire had ~10K triangles for the main character and a game like Dead or Alive where there are few characters on screen had 15-20K triangles).
Meanwhile more current games have much higher polycounts, easily going above 100K triangles - e.g. Dante from DMC5, a ~7 year old game, apparently has ~190K triangles and that had to run on the more anemic PS4/XBone hardware :-P (though i'm not sure if it used the full 190K model there or some cut down version).
[0] http://runtimeterror.com/pages/iv/images/1073c7062db40837240...
I rendered some Source engine levels on a shitty laptop in 2012ish and they still rendered at perfectly acceptable FPS (30+) just by rendering all the geometry in the level in one shot.