The genius of binary space partitioning in Doom (2019)
twobithistory.org
twobithistory.org
None of these techniques is relevant anymore given that all the hardware has Z buffers, obviating the need to explicitly order the polygons during the rendering process. But at the time (mid 90s) it was arguably the key problem 3D game developers needed to solve. (The other was camera control; for Crash Andy Gavin did that.)
A key insight is that sorting polygons correctly is inherently O(N^2), not O(N lg N) as most would initially assume. This is because polygon overlap is not a transitive property (A in front of B and B in front of C does NOT imply A in front of C, due to cyclic overlap.) This means you can't use O(N lg N) sorting, which in turn means sorting 1000 polygons requires a million comparisons -- infeasible for hardware at the time.
This is why many games from that era (3DO, PS1, etc) suffer from polygons that flicker back and forth, in front of and behind each other: most games used bucket sorting, which is O(N) but only approximate, and not stable frame to frame.
The handful of games that did something more clever to enable correct polygon sorting (Doom, Crash and I'm sure a few others) looked much better as a result.
Finally, just to screw with other developers, I generated a giant file of random data to fill up the Crash 1 CD and labeled it "bsptree.dat". I feel a bit guilty about that given that everyone has to download it when installing the game from the internet, even though it is completely useless to the game.
You can’t mean that all the polygons in a game world are now sent to the GPU, entirely relying on viewport culling and Z buffer to remove the ones out of view? I’m not an expert but I’m sure that’s not true - doesn’t Source and latest iD Tech use BSP right now for example?
The Source Engine still uses has a pre-built BSP for surface visibility. Source 2 has replaced this with a world-aligned visibility octree [1].
[0] The common solution these days is a low-resolution depth buffer rasterized on the CPU with SIMD, because doing it on the GPU would have noticeable latency... you've probably played a game where you turn into a twisty hallway and the walls/object in there only show up after a few frames... that's GPU occlusion culling at work.
[1] https://developer.valvesoftware.com/wiki/Half-Life:_Alyx_Wor...
Game engines can of course still do their own coarse-grained occlusion culling, in order to reduce the amount of geometry that needs to be processed per frame. And there can still be a benefit to approximately sorting objects: if you draw objects in approximate front-to-back order, then a shader can do "early depth testing" and skip the expensive shading computations for pixels that are occluded by already-drawn polygons.
The current Source 2 does not use BSP anymore.
If you're not an expert why are you sure it's not true. Most games render a lightweight pass to do all rasterization to a g-buffer, then do the expensive shading later. This separates visibility from shading. If fragments are already occluded by the z-buffer they can be skipped as soon as they can test their z value against the buffer.
As other comments say, including the original comment author, game engines actually do still rely on their own space partitioning to reduce draw calls. Source 2 just does it quad rather than binary. Source is still used in actively developed games and is still BSP, so it’s not true that the techniques are not relevant.
This article contains a basic implementation of such idea in Vulkan - https://vkguide.dev/docs/gpudriven/gpu_driven_engines/
Wonderful! THIS is the kind of silly nitty gritty detail I want to hear more about - particularly the whys and wherefores of the decision, largely-unconsidered as it may well have been. Puts me in mind of the semi-affectionate rivalry between different demo/crack houses in the eighties and nineties, engaging in largely unseen conflict, all submarine-like :) And, if you're reading this - know that this thoroughly un-tech nerd has read all of your Crash Bandicoot breakdowns, no matter how arcane they might seem to me :)
In your opinion, What is the key problem 3d game developers need to solve in 2022?
But, I am starting to implement vehicles, first time adding assets that are multiple tiles in size. It would be really nice if I could create one asset for the vehicles, but the sorting on screen.y will not work for 3D rectangles, so I am breaking the up into multiple tiles...
Do you think BSP trees will work with thousands of moving assets? i.e., I would have to recreate the BSP tree every frame.
frame 0: open door
frame 10: still can't see past edge of door
frame 11: door opens enough to see big new area
between 10 and 11 it turns out there's a huge difference, even though they're only two frames apart.Which gets into one of the major tradeoffs in rendering engines, do you try to minimize the average effort per frame or the worst case effort per frame.
This is the first time of hearing BSP, and I read most of the OP's article to have a very basic understanding how it works.
Since this is a tree, reordering N elements would be approach N^2 complexity, would it not? (edit: I assumed you would have to find each node from the root, which could very well be a bad premise).
But this is one of those cases where asymptotic complexity seems a bit too reductive. One of the "n" in the complexity is really a fraction of the n.
That said, if I understand your use case correctly, you might not benefit much from having a binary subdivision of space for this. It sounds like what you need is more of a plain ordered list. One of the ways to implement that is as a binary tree, but an array that is kept ordered may be fast enough due to locality etc.
If you imagine two long rectangles, one in each of your two hands, and pretend they are two cars passing each other in an isometric world, you will see that pretty soon, one car's screen.y position will be below the other before it's clear of the vehicle which should actually be occluding part of it.
The ordering is as follows: I'm assuming the isometric rendering of a map as a 45 degrees tilted square, and I'm only considering tile ordering just for simplicity but it should generalize fine. The uppermost tile is where you want to start rendering. From there, you render following the two 45 degree diagonals until you are done (so you don't only look at the y axis). Once this is done, you restart the process from the tile just below the uppermost corner, and so on. This ordering makes sure that all rectangular objects that are aligned with the 45 degree diagonals are rendered correctly.
Now you need an additional trick to render rectangular objects that are transversal to those diagonals correctly. What you do is you keep track of the boundaries of all such objects, so that the rendering loop described above can tell when it encounters one. Once it encounters it, it pauses rendering the current diagonal and considers it temporarily complete. The diagonal on the other side still needs to be rendered fully though --- or at least as far as possible with the same stopping condition. The next rendering pass will likely at some point re-encounter the same transversal object, just at a further point. Stop again, start the next diagonal. Once the rendering encounters the lowest and last part of the transversal object, then that object can be rendered, and the first stopped diagonal can be resumed (and after this resume all the paused diagonals in order).
This should always give you the correct order to render everything without errors. Let me know if this made sense, otherwise I can try to clarify.
What I think you’re seeing in Warped is what we did with Crash 1 and 2 as well: I had to sort the foreground object polygons into the pre-sorted background polygons. That was modified bucket sorting, but there were manually tuned adjustments to make it work and stable between frames.
Not true if you consider transparent objects. Rendering with order-independent transparency is still a hot topic without a clearly winning solution on GPU.
Web browsers have lots of semi-transparent rectangles, which can be transformed under "preserve3d" context. This is a classic case of effective BSP that is actual. (background: implementing BSP in WebRender a few years ago).
Well ok but I don't get this:
> This means you can't use O(N lg N) sorting, which in turn means sorting 1000 polygons requires a million comparisons -- infeasible for hardware at the time
ISTM you CAN'T sort it full stop because of cycles - so what kind of sort (never mind O(N^2), any sort) can order cycles? They can't.
I remember the js13k game jam winner this year used BSPs. Linked below.
Your point about flickering just made me wonder if ordering problems were exacerbated by lack of floating point hardware; we already know that this is the reason for the Playstation's wobbly (charming) graphics, but I imagine that it may have also contributed to errors in the ordering process, since the positional errors would include Z.
The ordering issues arise from how ordering is actually implemented. When applying perspective transformation to a polygon's vertices, the geometry coprocessor can optionally calculate and return the average value of the Z coordinates after projection [1]. This value is then scaled, rounded and used as an index into what's known as the "ordering table", which is basically a set of command buffers which are executed sequentially by the GPU (implemented in hardware as a linked list) [2]. The number of ordering table buckets is usually limited to 256 or 512 to save RAM, however that also limits the Z index resolution to 8-9 bits and thus results in polygons close to each other being drawn in more or less random order; hence the glitchy overlapping polygons that would occasionally appear in games with highly detailed models.
[1] https://psx-spx.consoledev.net/geometrytransformationengineg...
[2] http://lameguy64.net/tutorials/pstutorials/chapter1/2-graphi...
A key insight for binary space partitioning is that it also solves the problem above my making sure this never happens. It slices polygons for that specific reason.
That isn't to deny that Carmack was brilliant. But him using BSP isn't some masterstroke of genius in itself.
They did use normal vectors to determine visibility and may have done simple back to front within objects using that, but the playfield is a grid and mostly rendered back to front.
Paul is essentially correct. It's mostly back to front, with some assumptions. I can confirm quite a number of tricks were used, not entirely sure they qualify as a full BSP (although it does involve precompiled mesh optimizations) but then again I'm no BSP expert (just an I Robot expert).
The rasterizer draws a list of polygons (full polys, not just triangles) which must be fully computed before sending to the rasterizer. This "display list" is drawn in order. There is no Z buffer, so everything draws on top of what was already there. So it's important that the display list have the polygons somewhat sorted. Also the polygons are drawn as is, which means rotation/projection is done before being written to the display list.
The camera was fixed (can't yaw) as to simplify math. This means you are always looking straight ahead in Z axis, which allows for all sorts of optimizations. It also makes it trivial to sort objects back to front, simply by checking z coordinate.
Next, the hardware only supports 16 moveable "3D mesh" objects that can be drawn per frame. With an object count this low it's trivial to sort back to front without consuming many CPU cycles. 6809 index instructions make this part a breeze.
The game playfield is "built on the fly", essentially mirroring a 16x16 (16x24? 16x32?) playfield tile array in RAM (each byte encodes tile height), and building each cube face polygon on the fly.
Because of the fixed camera, no real sorting or culling is necessary when creating the display list at the highest level. You simply draw from back of the screen (furthest playfield row) to front (closest row). This process never has to change, it's a linear traversal of playfield array memory, and it always works as long as the camera doesn't yaw. Just draw each row on top of the last one. Guaranteed to not cause problems.
To draw a row (16 tiles, 15 visible) all of the front/top/side polygons are computed and written to the display list. Again, because Z axis is fixed it makes creating the polygon coordinates trivial (you always see front of cube, no culling needed). Once that's done you draw any 3D meshes that are in the row (objects sitting on the playfield cells). Then you move to the next row, and the next, and then you're done.
So at this high level, no real culling is done... things essentially get drawn in order of depth, which always works as long as you don't yaw the camera.
Now... where things get interesting is the 3D mesh objects themselves are stored in a sort of "pre-computed" way to optimize surface removal (not sure if this was done by hand, or by an automated/compile process). It's not so much a tree structure, as it's actually done through specialized instructions. I suppose the jumping/branching is the equivalent of tree traversal.
Basically the meshes are held in ROM, and are really just specialized subroutines, written as a series of simple instructions for the Mathbox. To keep this writeup simple, treat the Mathbox as being able to execute two instructions:
1) write a polygon to the display list
2) "relative jump to instruction" if normal vector dot product points towards/away from camera
A 3D mesh object might be coded in ROM like this Mathbox3DMeshObject:
if normal vector dot product < 0, jump to X ; pointing away from camera
write poly 1 to display list ; forward facing polygons
write poly 2 to display list
...
Jump to Y
X:
write poly 10 to display list ; back facing polygons
write poly 11 to display list
...
Y:
if normal vector dot product < 0, jump to Z ; sideway facing?
write poly 20 to display list
write poly 21 to display list
...
Z:
write poly 99 to display list ; always drawn
...
END
A mesh object could code any number of jumps, to cover side facing objects, corner cases, etc. Note I'm glossing over the fact that the mathbox would also multiply the coordinates by the current rotation matrix, project to X/Y, perform shading, etc. before emitting to the display list.As code it's not a real tree structure, although executing the code behaves a lot like traversing a tree. I guess this is sort of a BSP? Can anyone chime in here?
It was pretty clever how it worked out. The mathbox executes the instructions, and creates draw instructions in order. Polygons get skipped by virtue of the fact you're periodically checking normal vectors, and skipping around to different instructions as needed.
Edited: clarity, typos
Thanks John!
The issue to me is that a tree is a data structure, but the meshes in I, Robot are encoded as literal subroutines of machine instructions. There is no tree to traverse, the Mathbox simply executes the instructions as it encounters them.
These aren't even "interpreted" (fake) instructions for a software VM (like Sweet16). They're literal instructions for the custom Mathbox microcode. The Mathbox executes the instructions, and outputs polygons to the display list as told.
It's a lot like a compiler optimization that unrolls a loop inline... Can you even call it a loop once it's unrolled? In this case, it's as if a BSP was created, then turned into literal instructions for a custom machine. Many times the polygons were duplicated in the code (in-lining polys works faster than branching back). It blurs the line between code and data.
I'm really blown away by the sophistication of the I, Robot Mathbox... The microcode was exceedingly complex, much more than just some fast matrix math (like Battlezone etc). It executed instructions to compute dot products, which would potentially result in X/Y/Z points being multiplied by a rotation matrix, projected, and written as polygons to the display list, for later rasterization by a different processor altogether.
Anyhow it's fun to revisit this stuff :)
These aren't even "interpreted" (fake)
instructions for a software VM (like Sweet16).
They're literal instructions for the custom
Mathbox microcode. The Mathbox executes
the instructions, and outputs polygons to
the display list as told.
Wow, that's amazing, I love that. Thank you so much for explaining this.FYI I've ported the emulator to C#, it runs in even higher resolutions than before. [1]
Here is one more interesting thing about the display that Dave told me. DRAM was expensive, and at the time the I, Robot game was much more expensive than most of the arcade machines they put out. To save on DRAM, they used fake double horizontal resolution.
This is a memory from a conversation 30 years ago, but there is 3b per pixel to specify color, and one bit to indicate if the edge should be pushed out half a pixel. That is, when rasterizing the polygons, they saved one fractional X bit in the pixel. In the interior of polygons it had no effect, but when generating video, if pixel at offset (x+0) and pixel at offset (x+1) had a different color index, the fraction bit from pixel (x+0) was used in the video timing generator to place where the color transition would happen, doubling the apparent horizontal resolution. When more than one edge crossed a pixel the information isn't that useful, but such pixels are a tiny fraction of all edge pixels, so overall it was a big win.
I don't think it ever got implemented all the way through. If it works as described, it should cause the right edges of polygons to appear with higher resolution than the left edges, particularly on black (blank) background. I've got an I, Robot in my living room and have looked for this visual difference and can not see it. Doodle City even provides the opportunity to move and rotate objects, so even with direct control of object and orientation I can't see it.
Apparently there were other things that never got finished with the game too.
I agree that if it's there, its hard to see (I found some actual arcade footage online). I wonder if that's due to issues with the lower res monitors? Would be interesting to ground the 1/2 pixel signal pin and see if that has a noticeable effect on the screen visuals.
When I was reverse engineering the thing, I seem to remember there being a bit related to color/shading that I couldn't figure out what it did (I could see that it was being used).
The system had a 64 color palette. The software broke that up into 8 sub-palettes to give you for 8 main colors with 8 shades each (0-7 first color, 8-15 second color, etc.)
Polygons could call out colors directly using full palette index. Most polygons "faked" shading, they really just specified different shades of the same color for effect. For example, the dodecahedron / meteors in the space waves aren't real-time shaded, just colored to look that way.
However the hardware could do shading, which cost an expensive dot product. So it was used sparingly. To do shading, the polygon used a 3 bit color "index" to call out one of the 8 sub-pallets. Then you add an offset of 0-7 based on the dot product of the normal vector to apply shading (I used 8 x dot product to get you an offset from 0 to 7.9999).
I recall there being an extra bit there (4 bits instead of 3) but couldn't figure out what that last bit was for (it seemed like a "1/2 bit" to me). It looked like it was being used, but didn't make sense, so I stripped it off, and it didn't seem to affect anything so I forgot about it... till now. If I'm remembering correctly, this might literally be a "1/2 step" to add between shades? That can't be right can it?
Another way I read what you wrote is that it could be considered an "anti-aliasing" bit to do some alpha blending at the edge of a polygon to fake higher resolution? Maybe that's a better way to read it?
My memory is probably not 100% here... Would love to hear more.
I don't recall the actual resolution, but say it was 256 pixels per scan line. If the color for pixel 100 was index 5 and for pixel 101 it was index 3, the straight forward way would be to send the right RGB phase for whatever index color was throughout the interval pixel 100 and then send the right RGB phase for whatever was the right color for pixel 101. The half bit modified the timing to delay the transition from pixel 100 to pixel 101, somewhat doubling the horizontal resolution. It made polygon edges less chunky.
Or another way to conceptualize it: each row had 512 pixels, but memory for only 256 pixels. By default each memory location would be used to fill in the color for pixels X and X+1. But the half bit would modify it to say whether the transition should be shifted out one more pixel. If the transition was from red to blue, the default way would be to generate four pixels out (R, R, B, B), but if the half bit was set, the video generator would output (R, R, R, B).
Yeah it totally jibes with what you said above.
The rasterizer outputs what looks to be 10 bits of horizontal position (for the pixel being written I'm assuming). Since screen is only 256 pixels wide, this would imply that there are fractional horizontal bits here. The rasterizer keeps track of fractional bits to compute sloping (jaggies)... so the fractional bit implies the pixel being written fills up more than 1/2 of the next pixel.
Video memory is 7-bits per pixel. 6 bits are for 64 color palette index. The fractional 1/2 pixel bit (after some AND/OR/XORing) is stored as the 7th bit of the pixel. Indicates its a "wide" pixel.
Later, when video memory is being read/scanned to the monitor, the buffer holding the 6-bit color to output is sometimes latched a half clock cycle later. The half clock cycle delay is the 7th bit stored in the video memory, the 1/2 bit.
So yeah.. you're essentially delaying the latching of the next color if the pixel is "wider". It makes sense to think of the additional bit as a width bit. This is a crazy way of doubling resolution... instead of needing double the screen buffer memory, you add 1 bit to indicate the pixel is wider. The caveat is you really don't have true double resolution, you can't have a single pixel at half resolution. You only get pixels of normal width, or 1.5 pixel width. Still that's amazing!
It should be possible to see this artifacting on a real game. Since the alphanumerics overlay is scanned at normal pixel size, if there is a half pixel in the bitmap, the overlay should sometimes not appear aligned to the background. Paul, you might be able to see this on your machine. If you play around in doodle city, you can move polygons where they're behind the alpha overlay, slight movements might show the half pixel in relation to the overlay.
And only on the right side of a polygon. If video RAM is cleared to 0-black, there would be no "wide" background pixels, only wide polygon pixels and they're extended to the right only.
I will look at Doodle City and try to move things under the text overlay.
Also, what is that big chip in the linked schematic doing? HPOS6-15? PADD0-PADD10. It has a 5MHz clock (the dot clock) but that FF has a different clock which might be 10MHz?
Yes that chip has a different clock, as its writing pixels to the frame buffer. The monitor always displays the other buffer not being written to, so the rasterizer does not need to be sync'd to the monitor pixel clock (which has overscan areas etc.), therefore the rasterizer can fill pixels at a different speed. My guess is that they couldn't run the ICY faster than the 5MHz, because I'm sure they wanted to run it as fast as possible!
Essentially the chip takes the display list, which is a bunch of instructions for DOTs / VECTORs / POLYGONs, and draws them to the frame buffer pixel by pixel. Starts at the top of the display list (address zero), goes instruction by instruction filling pixels until list end is reached. Note the START and HALT signals coming from the main 6809 CPU telling the chip there is work to do. When you "HIT A BLACK HOLE" everything is halted and the CPU tries to recover things.
Commands generally look like this (this is a major oversimplification so don't take this as 1:1 with the real hardware)
CMD_TYPE COLOR XLEFT XRIGHT YTOP SLOPELEFT SLOPERIGHT NUMSCANLINES
Where CMD_TYPE is [dot/vector/polygon], COLOR is the palette index, and coordinates are in 16-bit with fixed precision slopes.The chip fills pixels from left poly edge to right edge, when it reaches the end it adjusts the left/right edges and adds 1 to the scanline, eventually stopping when all scanlines are drawn.
As I said I know the internals are floating around, and I know of at least 1 project attempting to duplicate the ICY on a daughterboard. ICYs are 40 years old and are starting to die and there is no replacement.
I can read research and incorporate it into my code, but it takes me a while, and I usually have multiple dead-ends where it isn't until after I've tried using a technique with real data that I realize it has weaknesses the researchers didn't point out and which can't easily be fixed. I can crank out quick code, but it isn't going to be advanced or optimized. I struggle finding the right balance of how to trade between these three things, and yet Carmack and iD were able to squeeze in all three at once.
> usually have multiple dead-ends where it isn't until after I've tried using a technique with real data that I realize it has weaknesses the researchers didn't point out and which can't easily be fixed
If you listen to John Romero himself telling the story of id and the fast iteration, it was "due to having, basically, 10 years of intense game development experience prior":
https://youtu.be/E2MIpi8pIvY?t=543
("it's also due to the first principle we had - no prototypes")
A theme I tend to see is that a lot of folks with super output appear to others like icebergs - a LOT of effort that nobody can see, and a tiny peak at which everyone marvels.
Copies are available here: https://www.bluesnews.com/abrash/
The author references the Michael Abrash book, and said it was from "the late 90s."
But IIRC, Abrash was writing about BSP in Doctor Dobbs Journal in the early 90s.
IE: Abrash inspired Carmack, not the other way around.
In 1992 I was really keen on learning to make arcade style games on the PC, and I studied those Abrash articles like crazy.
wow 1983
What Carmack had was the ability to read research papers and translate them into working code. I can do that too, but it does seem to be a less common skill in the software world.
Edit: clarify
Most importantly: without a GPU.
At the time, all of the really "flashy" arcade games used GPUs that cost around $1500 in today's dollars. Abrash pulled off this stunt with no GPU.
I'd argue that the entire reason the Xbox is called "Xbox" is because of "Mode X", invented by Abrash.
And DirectX has nothing to do with ModeX; it's a family of APIs: DirectDraw, Direct3D, DirectSound, etc.
And he had to read actual papers, too. I could see him making copies at the library. No Google or StackOverflow back then.
What Carmack had was the ability to read
research papers and translate them into
working code. I can do that too, but it
does seem to be a less common skill in
the software world.
Well, that's the argument for Carmack being merely very very very good and not a "genius."Here's the argument for him:
There were a lot of exceptionally talented people working in the games industry, and for four+ generations of PC hardware (the Keen, Wolf3D, Doom, and Quake eras) Carmack's engines were years ahead of everybody else's and had an absolutely massive impact on the industry.
Of course, the term "genius" is nebulous and it can mean whatever we want it to mean.
However, describing him as merely a guy who implemented other peoples' algorithms may be true in a literal sense but really misses the big picture IMO.
I can't visualize why it's true
To start off with, we have a basic geometric fact: if you have a surface that divides space into separate regions (this can be a closed surface like a sphere, or an infinite surface like a plane), a line of sight can't cross between the two partitions of space without passing through the surface. If the surface is convex, a line of sight can only cross it once. (That's one definition of convexity.)
Now, suppose that you have a lot of objects, and none of them are on both sides of the boundary. You can ensure that condition by splitting any object that crosses the boundary in two. No matter where the line of sight begins, and no matter what direction it moves in, the boundary then offers a definite, albeit partial ordering of events: objects on the same side of the boundary may intersect, then the line will intersect the boundary, then the objects on the other side may intersect.
If you find an intersection with an opaque object on the same side of the boundary, you never need to check any objects on the other side, because the order of events above. That is a big benefit when about half of the objects in front of you are on your side of the boundary: if you are right in front of the boundary, or if there's nothing on the other side of it, it doesn't help a lot.
Checking all of the objects on one side of the boundary is the same kind of problem as checking all the objects, and that means you can speed those up too by having sub-boundaries for the sub-problems. That gives you the recursion and makes it a tree. If the reasoning based on boundaries continues until there is only one object in a sub-problem, you don't need to sort lists based on distance - meaning that the whole ordering problem can be solved just with partition trees.
Choosing a good tree is a difficult problem because, unlike the fact that the ordering problem can always be solved, the amount that an individual boundary division helps depends on where you and the objects are in relation to it. Planes are generally used instead of other things like spheres because splitting a polygon along the edge of a sphere doesn't result in another polygon, while splitting it along a plane does.
Every node in the tree is associated with an infinite plane that divides space in half. Stuff on one side of the plane is in the left subtree; stuff on the other side of the plane is in the other subtree.
The viewer is on one side of the plane or the other (or maybe on it, oops).
We know that stuff on the other side of the plane cannot occlude the view of stuff on the viewer's side of the plane. So for instance if we're doing back-to-front rendering, we would draw the material from the other side of the plane first, then stuff from this side. (Subject to the content being rotated into the correct view and clipped to the view frustum.)
There is no polygon in the tree that intersects/straddles these dividing planes, which is the point. When the BSP is constructed, whenever these planes cut through some input polygon, it is cut into two pieces that get assigned to different subtrees. There are then situations in which those pieces will not get drawn at the same time though they belong to the same logical surface.
The planes which subdivide space in half are independent of each other; they go every which way. So if we have nodes like
a
b c
d e f g
it is possibly the case that plane b cuts through the polygon sets f and g, and maybe some of them even straddle plane b. None of that is relevant to them because the b subset is on the opposite side of common ancestor a, the b plane subdivision is concerned only separating d from e. Binary space partitioning is not actually a mathematical partition (exhaustive division into non-overlapping parts) of the space. The set of poygons gets partititoned, of course; not the space itself.It was very useful to be able to use one plane test to discard a whole bunch of geometry, eg: a surface plus its detail, or everything visible through a window.
This still left the problem of sorting between objects - mostly a depth sort was just fine. Special cases like weapons on hardpoints, or vehicles in hangars could be handled by having some sort of 'call submodel' for spaces within the parent model. Beyond that, just dial up the hostile firepower until players stop complaining about rendering artefacts.
Games these days just depend on raw hardware and optimization takes a back seat. Games like Plague Tale Requiem, while look fantastic, are equally bad at the engineering part. How is an RTX 3080 the minimum recommended spec for 1080@60fps, is beyond me. Sadly, people these days do not care about software optimization and those who do are bombarded with comments like "Get better hardware".
I took a 3D computer graphics course in university in the early 2000s, and the final project was to make a game. IIRC we could use as many open source libraries + assets as we wanted, but we had to write the renderer.
Our professor was a big Buffy the Vampire Slayer fan, so we made a Buffy game. My team thought we'd have some easy points for that. When we presented our plan, he says "I know you know I'm a Buffy fan, so this better be good"... Ooops. Time to get to work.
We used the Quake map format, which is BSP, and our renderer worked pretty well. We used Quake 2 character models, which worked pretty well on its own. I think it was because someone had made some vampire models for it.
When we integrated the two, the performance was terrible. Like a slideshow. After a brief panic, we realized that the Z axis was inverted between the two modules we'd written, and the guy that put them together was inverting the model every frame. After inverting it once when the model was loaded, they worked pretty well together.
We added a simple hitbox (a rectangular prism at the maximum points of the model), and had a fun little game after that!
I have had a bird's eye exposure to shader-based OpenGL but don't feel I have a good intuitive understanding of how the GPU operates.
Alternatively, get the Black Books for Wolfenstein and Doom, and read them first.
It gives a lot of insight in what motivated and inspired the people of iD as well as provides some technical details of how they accomplished things.
It's a very easy read.
The Doom precomputed z-buffer hack is described in this article, but I wouldn't call it genius. The Quake BSP was much better, but neither genius as a lot of people used it before to their advantage. It was a common technique, just not in games. Even we used it in our VR application years before Quake.
My feeling is that Carmack is really good at speeding up important parts of video game performance to achieve qualitatively different gaming experience.
Sometimes I wonder if Carmack was born 30 years later, when computing resources make such advances less needed, he would still be as successful? Perhaps he would go on to improve performance of other problems eg virtual reality and deep learning…..
https://en.wikipedia.org/wiki/John_Carmack
Of course being famous does open doors, but there's no reason to doubt he'd break into VR in the counterfactual. He did break into game development in reality after all.
That's arguable.
Someone has to improve the graphics engines at Unity and Unreal, with enormous reach.
There's a big potential market in porting what only runs in dedicated VR headsets and expensive gaming computers and consoles to cheaper phone-in-a-helmet VR headsets and notebook computers. Then to cheap phones and netbooks.
These days octtrees more or less replaced BSP trees i guess. They are easier to handle and work better with more polygons.
Subtly amusing considering carmack is not on favour of academia. But everything he uses is made possible by it.
But if you then turn 90 degrees, then suddenly a bunch of those z-axis items are side by side, and order is based on the x-axis of my original perspective. So I don't understand how a binary structure accounts for that perspective shift.
Edit: For clarification, my understanding is that there's a single tree built per-level in advance. If I'm mistaken and it's re-building the tree 30 times per second, the lack of axis concerns makes sense, but the article seemed to indicate that that's computationally prohibitive.
Have you tried reading https://en.wikipedia.org/wiki/Binary_space_partitioning ? It shows how a BSP is built and traversed for rendering.
It started with the legendary square root function. Which lost its legendary status once it turned out Carmack wasn't the author.
Now it is about standard BSP technique from Computer graphics text books from that era.
There is also hype about Carmack solving AGI by 2030, when he is yet to publish a single widely cited AI paper.
EDIT:
I am guilty of reading the first couple of paragraphs and assuming this was Carmack worship. My bad! Leaving the original comment, mostly as is.
Treating anyone like a rockstar is a bad idea, but Carmacks and Wozniaks deserve the praise more than the Zuckerbergs and Jobs' out there.
https://www.beyond3d.com/content/articles/8/
https://web.archive.org/web/20120920120948/http://blog.quent...
It's similar with Linus. He didn't invented monolithic kernels, UNIX, distributed version control. He "just" wrote good implementations.
Turns out brilliant ideas are overrated in programming, what matters is execution and daily grind.
You also have to be good at finding the brilliant ideas that are worth using.
Then you have to implement it.