|
Here's a description of my rendering layer:
I use a material system, with an interface that exposes a list
of typed parameters. The renderer acts as a material instance
factory, and there's a separate XML DOM parser-let that can
populate a material with property values based on text input.
The renderer also acts as a vertex- and index buffer allocator;
the users of the renderer can specify two usage patterns:
"changed very seldom" (for static geometry) and "probably changed
every frame" (for particle systems, CPU skinned geometry, etc).
Last, the renderer can take "transform state" which turns out to
be either a modelview matrix, or a chain of modelview matrices
(for posed skeletons).
The actual renderer call for rendering and state management is
ultra simple:
virtual void begin() = 0;
virtual void drawMeshWithMaterialAndState(
I_VertexBuffer * vb, I_IndexBuffer * ib, I_Material * m,
Matrix4 const * transform, size_t transformCount ) = 0;
virtual void present() = 0;
That's it! That's the only way you can render stuff in my
renderer. There are a bunch of separate classes that make certain
kinds of rendering easier, like there's something which
aggregates 2D quads (for UI) and, at the bottom, issues one or
more buffers to the renderer based on how many materials are
used.
There are global functions to allocate a C_DX9Renderer or a
C_OGLRenderer to get it all working; these global functions just
return the abstract I_Renderer interface, so the specific
implementation is hidden inside each subsystem. Each renderer
implementation sorts by material or vertex buffer or distance to
camera or whatever based on what works best for that
implementation. Gross transparency far-to-near sorting, and
occlusion near-to-far sorting, goes here, inside present().
Having to pre-declare the materials you are going to use (with
some tuneable variables such as alpha and color, or shader
uniforms) is a major performance and code clarity win compared to
systems that want to bang every little bit of the renderer state.
I'm working on offscreen render target support; it seems I'll
have to add another function to my renderer interface to allocate
a sub-render-target, and pass the specific render target to
begin(), although it's not all worked out yet.
Note that camera, culling, and a bunch of similar higher-order
functions go outside of the renderer; you can use a portal
system, a scene graph, an octree, or a plain linked list of
everything; that doesn't matter to the renderer!
|