The intention of this post is to document and share some information on how some of the features in the renderer works for Strife: Veteran Edition. I am hoping that this may be useful for source port authors and programming hobbyists alike.
One of the most common issues seen in OpenGL source ports is that when rendering sprites, they intend to get ‘clipped’ by the surrounding floor/ceiling geometry because of the depth buffer. Since there’s no such thing as a depth buffer in the software renderer, sprites could be drawn on top of anything.
A typical solution would to simply nudge the sprite a few units above the ground. Since most sprites in Doom pokes only a few minor pixels below the ground, which wouldn’t be a problem to adjust them. Strife on the other hand, suffers this problem the most as most of the sprites are clipped halfway through, which looks really ugly and undesirable and nudging them wouldn’t be a good solution.
Below is a example:
My approach is to leverage frame buffer objects (FBOs). Before rendering the walls and floors, I bind the FBO and render all sprites from a single scene into a texture, which is then blended on top of the final scene, allowing them to be drawn on top of the floor. This however exposed another problem: they are now being drawn on top of everything, including walls!
So I had to come up with another way to fix this. I needed to find a way to properly clip the sprites by the walls but still let them draw on top of the floors and ceilings. I decided to take advantage of the depth buffer to accomplish this. So during BSP traversal, I store a list of visible linedefs (not segs) into a draw list. This also includes linedefs facing away from the player’s view. Culling is not accounted for during this step.
Before rendering walls and flats, I bind the FBO and disable color masking and culling and then I render the draw list containing the linedefs (I call this draw list ‘occlusion’ lines). This draws them ONLY into the depth buffer, after which color masking and culling are re-enabled and then sprites can be drawn afterwards. But there’s another problem, since masked/translucent walls are drawn last, the sprites in the FBO are now drawing on top of them. To fix this, I had to also draw the masked/translucent walls after rendering the sprites in the FBO. While this fixed it, it introduced some rendering glitches when viewing masked walls behind translucent geometry. Simply re-drawing the masked walls (after rendering the scene as normal but before blending the FBO) fixes this.
The process in rendering the occlusion lines is like this: when rendering a one-sided occlusion line, the top and bottom z-axis of the vertices (that makes up the geometry to render the wall) is extended into infinity, making sure that we fill the depth information on the screen. For upper/lower sidedefs, the upper z-axis is extended for upper sidedefs while the lower z-axis is extended for lower sidedefs.
However, this introduced yet another problem which, compared to software mode, still doesn’t look right:
So to fix that, we just simply not draw them if the player’s view z axis is higher/above the geometry used to create that occlusion line. Allowing us to finally mimic the way sprites are drawn in software mode:
View Z Axis above the lower sidedef geometry. Occluder is ignored
View Z Axis below the lower sidedef geometry. Occluder is drawn to the depth buffer
This technique is also used for skies as well. Linedefs that’s connected to skies are added to a draw list and are drawn into the depth buffer, with the top portion of the geometry extended into infinity.
That just about covers it. This isn’t the BEST way to do it and it results in A LOT of overdraw but this seems to be the best approach that doesn’t involve shaders or any advanced OpenGL feature. If there’s anything else that you guys might like to have documented or explained, then let me know!