And now, a few notes about the Metareal Engine architecture. As mentioned earlier, of course it’s silly to write my own, let’s move on.
The library presently consists of two major components: Rendering and Volume Management. The Rendering side deals with lists of triangles, groupings of parts, geometry lists, and shader graphs. The Volume Management side deals with detecting intersections of volumes, and notifying listeners. Physics is built on top of volume management, as a special case of responding to intersections.
In this post, I’ll talk about one aspect of Rendering.
MeRenderWorld
The class MeRenderWorld is presently the highest level container. An application may use several, certainly, but this is the top container on the library side.
An MeRenderWorld manages:
- Lists of Parts (each part is a geometry list)
- Cached geometry lists, grouping parts by material, so each material uses a single glDraw
- References to materials (shader programs, for an OpenGL implementation)
- Backing buffers for rendering post processing
- List of render operations
The MeRenderWorld itself knows nothing about OpenGL; rather it deals with MeIMaterials and MeIPartRenderers and MeITextures and so on. For now there are only two implementations of each, one for OpenGL and one for FakeFL, which just records actions suitable for unit testing the library.
Often, one wants to describe a process by means of a document, perhaps in XML or similar. Describing the graph of draws and post-processing for a final scene frame is a perfect example of this. However, I have a strict policy of implementing code first, document second. Here I’ll show the code mechanism for building up a render graph.
The render graph is described as a strict, linear sequence of actions. It has to become one anyway (at least until glNext), so that’s the primitive. Here is an example setup:
MeRenderWorld *rw = this->renderWorld;
rw->addStepClear("", 0x123456);
rw->addStepDraw(this->materialSky, "");
rw->addStepDraw(this->materialOpaque, "");
Here we see that each frame will be created with three actions: a clear-to-color, and two material draws. Today this is, in fact, exactly two glDraw() calls, but that could change.
The empty strings are the names of frame buffers. The unnamed buffer, “”, is the default output buffer. This might be the screen or window, or be another buffer, depending later on the argument to rw->draw().
Here’s a more complex render sequence:
rw->addStepClear("b0", this->bgColor);
rw->addStepDraw(this->materialSky, "b0");
rw->addStepDraw(this->material, "b0");
addCopyingStep(rw, "b0", "b1");
rw->addStepSetTextureColor("b1", this->materialTransparent, "texture1");
rw->addStepDraw(this->materialTransparent, "b0");
addCopyingStep(rw, "b0", "");
In this sequence, the background is cleared and then drawn with a skybox. Then, the opaque parts of the geometry are drawn onto a buffer named “b0”. This buffer is created on-demand by MeRenderWorld, you just have to name it, and it exists.
That first render is provided as a texture argument to the transparent material, in a uniform named “texture1”, which is then drawn onto “b0” also.
Lastly, “b0” is copied to the output. (addCopyingStep() is a macro which instantiates a screen-sized rectangle textured with one buffer, drawing to the other).
Here’s a frame of the output:
And there’s a peek into the Metareal Engine’s rendering component.