DOOM-Style Renderer

Saturday, December 4th, 2010

I’m gonna start this blog off with something I did about a year ago. It’s a DOOM-style “3D” renderer in Flash! It uses textures from the original Wolfenstein… ah, memories.

Still in “early tech demo” stage, but I might make it into an actual game at some point.

My first attempt was to use a “Wolfenstein” raycasting rendering system that draws vertical wall strips, however that turned out to be too expensive and slow in Flash. After a little digging I came across the Flash 10 drawTriangles method which was pretty quick and worked well.

So here’s a quick rundown of how the “DOOM” style rendering works ( I put that in quotes, because it’s not quite the same as DOOM due to the drawTriangles rendering method ):

First things first, I create a 2D top-down representation of the level. This is simply a collection of 2D vectors that form a few convex sectors. There’s an editor mode in the SWF where you can draw and save levels using a 2D grid, but it’s VERY programmery, so it’s currently not accessible. Each sector has it’s own floor and ceiling height. In the demo level there are 3 sectors ( you can think of them as rooms ). The big room you start out in, the long hallway and the small room at the other end of the hallway. You can adjust the floor and ceiling heights in a room by standing in it and using G / SHIFT-G and the UP / DOWN arrow keys. This logic could be used to create elevators and other tricks like rising and falling water.

When the game is running, each 2D wall endpoint is transformed into the player’s view space and some basic culling is done to figure out exactly what walls are possibly visible. You can see this if you press ‘M’ to view the debug map and look at the green colored walls. Note, that at this point, it’s totally possible to have a wall that’s within the player’s view cone, but obscured by a closer wall. The next step takes care of that issue. You’ll also notice that there are “walls” where the sectors meet. These are normal walls, but are handled specially by the renderer to only draw the bottom or top “steps” if they’re visible. The rest of wall isn’t rendered obviously. These are more complicated, but just require a little more handling in the renderer to draw them properly.

Once the walls are determined, they’re then projected to screen space and the top and bottom of each wall end point is calculated. This data is then used to clip each wall against the others so we don’t have any overlap. After all the math is done, we should have a perfectly clipped set of wall segments that span from the left side of the screen to the right without any overlap. This clipping and culling is what makes the rendering so fast since there won’t be any pixels that are touched more than once. This is only possible given that the map is really just a collection of a few 2D vectors, making the clipping and projecting extremely simple compared to a true 3D scene.

Floor and ceiling sections are rendered by simply extending the tops and bottoms of each wall section straight up / down to the extents of the screen. There’s a lot more to it than that, but that’s basic concept.

A few more calculations are done to figure out the texture UVs for each wall, ceiling and floor section and then everything’s rendered using drawTriangles. The beauty of all of this is that once all of the math is done ( and it’s not a huge amount ), the rendering is just a few textured triangles that don’t overlap. It’s extremely fast!

“Lighting” is faked by rendering each triangle in the scene twice. Once for the texture and another time for a black, shaded triangle that gets darker the further it is away from the player. This obviously slows the renderer down as it’s now having to touch each pixel twice, but the effect is worth it. Press “F” to see what it looks like without it.

Sprites ( the barrels ) are simply points inside the 2D map. For each point, it’s again transformed into player view space and clipped against walls and other sprites. The sides are extended out from the point using the current perpendicular view vector in order to create a billboard that’s always facing the player. Once everything’s been clipped, they too are rendered using two drawTriangles calls and another two for shading using a single shade value depending on the distance from the player.

As far as input goes, the biggest challenge was getting the mouse to feel as good as possible given that Flash doesn’t have the ability to lock the mouse within the Flash region. Without this functionality, there’s no way to implement true FPS mouse aim controls since the player can’t continue to move the mouse in the same direction as it will just leave the Flash area. However, after a ton of tweaking and tests, I think what’s in there now is actually pretty good. It definitely takes a little bit to get used to, but after that it feels ok. The logic behind it is very simple: there is a square in the center of the screen ( you can see it by pressing ‘I’ ). When the mouse is inside that region, the player’s view is adjusted slightly but is static. As soon as the mouse leaves that region, the player’s view will start turning towards the mouse. The closer to the edge of the Flash region, the faster the player’s view will move. That’s it! Obviously it’d be nice to eventually have some options so that each player can tweak the input speed and what not to their liking.

So, that’s a very high level overview of how everything works. If you want more detail, checkout the research sites below… it’s a dump of all my “DOOM-Style Renderer Research” bookmarks. Or send me an e-mail.

Enjoy!

[swfobj src="http://www.regularkid.com/wf/WF.swf" width="600" height="337"]

Controls
( Click to activate mouse controls )

ASDW – Move
MOUSE - Look
SPACE - Jump
C - Crouch
F - Toggle shading ( fog )
M - Toggle debug mini-map
I – Toggle debug mouse input look region
G + UP/DOWN ARROW KEYS – Adjust ceiling height for current sector
SHIFT-G + UP/DOWN ARROW KEYS – Adjust floor height for current sector
SHIFT-F + UP/DOWN ARROW KEYS – Adjust fog distance

Research Sites:
Best tutorial on the web for understanding Wolfenstein rendering
drawTriangles tutorial
Another drawTriangles tutorial but includes info on perspective texture calculations
Article on Flash bitmap data and vector optimizations
Another Wolfenstein rendering how-to
DOOM / Hexen / Heretic Flash port using original source and Alchemy
Collection of some great old-school graphics tutorials including a section on raycasting

I also downloaded the original Wolfenstein and DOOM source code files, but can’t seem to find the link to them. If you can find them, they’re obviously a GREAT reference. Plus, it’s just plain old cool to dig through John Carmack’s code!


There are 11 comments in this article:

  1. Sunday, December 12th, 2010Doom duck hunting. | Ultimate Fishing and Hunting Blog says:

    [...] DOOM-Style Renderer – RegularKid [...]

  2. Friday, December 17th, 2010rachat de credit says:

    really an eye opener for me.

    - Robson

  3. Thursday, February 3rd, 2011Micheal MacLean says:

    Source, or is it top secret???

  4. Saturday, February 5th, 2011regularkid says:

    @Michael: Yes, I’d like to release the source for it eventually… but I’m still working on an actual game off and on and would like to wait until that’s complete to post the code. Thanks for the interest though! Do you have specific questions on how something works, or just want to browse the source?

  5. Saturday, February 5th, 2011Micheal MacLean says:

    Thanks for the reply! And I totally respect your decision to keep it close to the chest until you get your own project done.

    I have been messing about with raycasting using Adobe Alchemy (thought I am a C/C++ noob so while it’s been fun, it’s also rather frustrating sometimes). When I was searching for inspiration I came across your blog here and your use of drawTriangles blew my mind! So, to answer your question, I was curious about how you did the sector math without rays (portals, maybe?), as I am stuck in span-mode from all the raycasting research.

    I also get a sick pleasure out of reading source code too though… :)

  6. Monday, February 7th, 2011regularkid says:

    Ah, ok. Gotcha!

    So, currently the game transforms EVERY wall every frame into screen space and then does clipping / rendering. However, ideally it should work like this:

    1. Start with only the walls in the current sector the player is in ( put them into a “walls to transform” list ).
    2. Transform endpoint of each wall segment into screen space.
    3. If you hit a wall that is a sector connector and it’s screen transformation is visible to the player, add all of the walls from the sector it connects to into our “walls to transform” list.
    4. Continue this process until we run of our walls to transform.

    This way, you’re working outward from the player’s current sector and only transforming walls that could possibly be visible. There will obviously be some walls that don’t make the final cut, but this process should drastically cuts down on walls that you even consider visible. I say *should* because it also depends on the level layout… if you have a lot of sector connectors and the level is very “long hallway-ish”, chances are you’ll still have to touch a good majority of the wall segments anyways.

    Does that help explain things better? Let me know if it doesn’t and I’d be happy to draw programmer-ey pictures to go along with it, haha :)

  7. Monday, February 7th, 2011Micheal MacLean says:

    That totally makes sense! I didn’t think about clipping in screen space but your explanation opened my mind. I can see level layout playing a pretty big role in such rendering, but constraints like that tend to bring out the best in designers anyways :)

    Great stuff! Good luck in your project!

  8. Monday, February 7th, 2011regularkid says:

    Awesome! Glad it all makes sense! Good luck on your project as well, keep me posted on progress!

  9. Friday, March 18th, 2011Tor Coolguy says:

    Hi Regular Kid! Great flash demo!

    I only have one question, how did you figure out where the textureX to start drawing was when your left view frustum intersected with one of the walls?

  10. Monday, March 21st, 2011RegularKid says:

    @Tor Coolguy: Thanks! Glad you like it :)

    So, the way the frustum culling works is that it does a line intersection check with both lines that create the view frustum ( ie. the two sides of the triangle extending out from the player when looking from a top down perspective ). If a wall intersects with either side of the frustum, it adjusts either the left or right side end point of the wall for rendering purposes and adjusts the texture offset based on the new length of the wall. So, for example: If a wall was 100 units in length and gets clipped by the left side frustum line to a wall that is now 50 units, then the starting texture U coordinate is adjusted by 50. This works when the wall units match the UVs 1:1. If the textures are scaled bigger or smaller, you’ll have to factor that in as well of course.

    Hope that helps!

  11. Thursday, April 7th, 2011Tor Coolguy says:

    Thanks Regular Kid, that helps. ^^

Write a comment: