I want to discuss what belongs in the layer above Vulkan, but below the application.
For concreteness, here’s a video of the sort of things I need to render. This is my all Rust client for Second Life/Open Simulator. Think of this as a big-world MMO where all the content comes from the server. So all the hard scaling problems apply.
The current approach uses a “renderer”, but not a game engine. A renderer is a library that offers an API with in meshes, textures, material properties, and transforms, and produces a good looking scene by calling Vulkan. The renderer is not a game engine - it doesn’t own the scene graph, do networking, input, audio, or gameplay. The renderer handles keeping track of what’s on the screen, what’s in the GPU, frustum culling, lighting, and shadows - all that heavy bookkeeping you have to do to use Vulkan on a complex scene.
Examples of “renderers” include
- Three.js (widely used, but not super fast)
- Three.rs (abandoned)
- Rend3 (abandoned, but I support a fork)
- Renderling (in progress, not usable yet)
- Orbit (abandoned)
They all have rather similar functionality. Everybody seems to end up in about the same place. All of those connect to glTF content, for example. These lean towards Rust, and C++ programmers probably know of others in C++ land. Let me know, please.
The argument for using a renderer instead of a full game engine such as Unreal or Unity is that game engines want you to do everything their way, and if you have to connect to existing content or server APIs, there’s a big mismatch. If you have a renderer available, it’s reasonably easy to get glTF on the screen.
Now, this approach works fine until you hit a scaling wall. The scaling problems usually arise because the renderer needs some locality info from the scene graph. If you have shadow-casting lights, the renderer needs to know, for each light, roughly what objects can occlude that light. Otherwise, rendering cost is O(lights x objects), which scales very badly. A short ranged ceiling light in a room might be potentially occluded by only a few objects. We don’t want to make Vulkan and the GPU test every triangle in the town-sized scene against that.
So the renderer needs to be able to efficiently query the scene graph somehow. Options include:
- Calling back from the renderer API via a lambda or something to query the scene graph for questions such as “Here’s a volume (probably an AABB). Tell me all the objects with any part inside that volume”. That gives us the set needed for culling for a light.
- Caching similar information within the renderer, in a sort of dumbed down scene graph, like object centers and bounding spheres.
If we try to do occlusion culling (we’re in a room, let’s not render the whole town at the Vulkan level) some of the same problems appear.
There’s a school of thought that says general purpose renderers of this type should not exist. Rendering is the job of the application. But that means writing about half a game engine into an application.
Now, this is a general problem. It must have been solved by others. But I’m not finding any solutions that scale. Everybody seems to do My First Renderer and quits, or builds a whole game engine.