[ ]




Brian - July 25th, 2010 - A Look Into the Iron Heinrich Material System

Though all of Iron Heinrich's gameplay takes place on a 2D plane, the entire rendering system is 3D - think Viewtiful Joe or Shadow Complex. This allows us a visual style that isn't possible with 2D, and it also affords us lighting, shadows, camera tricks, and other neat things. It is, however, much more complex.

There are plenty of points of complexity, but in this post I'm going to focus on the material system. The material system is designed to allow the artist, Nate, to setup the visual style of characters and levels.

Requirements
The goal of the material system are as follows:
(1) Completely data driven. If Nate wants to change a shader or a shader parameter, he shouldn't have to talk to the programmers at all.
(2) It must support setting shader parameters that are hand-specified by the artist as well as parameters that the engine delivers.
(3) It must support multiple render passes to make things like full-screen post-fx simple and, again, data driven.

What it will not support:
(1) Dynamic shader generation. There's plenty of work that could go into writing uber-shaders or fragment based shaders, but our shader requirement is minimal (we hope), so for now Nate will be driving those all by hand.
(2) Rapid iteration - Nate will have to restart the application to see his material changes. We may be able to shoe-horn this feature in, but it's not in the design.

Materials, Material Passes, and Render Passes
There are three primary players in the system, which I'm going to overview briefly and then talk about in more complexity.

Materials are the top-level item, and each model has its own Material - for right now only a single Material, though that may change later. At their core, Materials are just a collection of Material Passes.

Material Passes are where most of the data gets specified. These specify the shader effect that gets used along with any parameters.

Render Passes represent each pass of the renderer. During a Render Pass, the Material Pass of the same name is evaluated. Render Passes themselves control the render state, the render target, and the current camera that gets used.

Now let's talk about each component with a bit more depth.

Materials
There's not much more to say about Materials. They are a collection of Material Passes, which do all the heavy lifting.

Material Passes
Material Passes do the bulk of the work. During the evaluation of a Render Pass, Material Passes of the same name as the Render Pass are selected to be executed - only if they exist. If a model doesn't have a Material Pass for the current Render Pass, it won't be rendered.

The Material Pass specifies two primary things:
(1) The effect (shaders) that will be used.
(2) The parameters that will get passed into the shader.

#2 requires more elaboration, since Material parameters take on a lot of forms. In general, there are two kinds of Material parameters:

The first kind is a hand-specified value. This could be a uniform float, the file name of an input texture, or a matrix of some kind. They're typed key-value pairs, where the key is the name of the shader parameter (the uniform) and the value is, well, the value.

The second kind is an engine-specified value, what I call an 'auto' parameter. This could be a dynamically generated texture, the current camera matrices, or the name of a light. These are just names of parameters that the engine handles, and when one is seen the renderer retrieves the appropriate value and passes it on.

Render Passes
A Render Pass is a single run at rendering the scene. During a Render Pass, the scene collects all the Material Passes of the same name for rendering, sets up the render state/render target, and renders all the models that have the appropriate Material Passes attached to them.

Render Passes keep track of the following:
(1) The render state. These are various parameters that are used to do things like turn on/off culling, change blend modes, etc.
(2) The render target. This is a named target that can be rendered to and then used later as a parameter specified in a Material Pass.
(3) The size of the render target. We don't want to always draw to screen-sized render targets, since sometimes that's a waste of memory.
(4) The camera. Each Render Pass can render from a different camera. This is necessary for doing things like good water rendering.

Use Case #1
That's a big wall of text and no doubt hard to grasp, so I'm going to provide a couple use cases so you can see how this all plays out.

This first use case will be rendering a scene to a color buffer and then applying a post effect to do a blur. It's not terribly hard:

We have two Render Passes with the following parameters:
Render Pass RP1:
Render State: Default
Render Target: TextureA
Camera: Default Scene Camera

Render Pass RP2:
Render State: Default
Render Target: Screen
Camera: Orthographic Screen-Sized Camera

At this point, the entire scene is filled with normal entities. These entities all have very generic Materials, where each Material has a single Material Pass - named RP1, which designates that generic rendering will occur during the first render pass.

There is also a special item in the scene - a full-screen quad with a Material possessing a Material Pass named RP2, indicating it will only be rendered during the second Render Pass. This Material Pass has some special parameters:
Texture: TextureA
Effect: BlurEffect

Thus, after the entire scene is rendered, the quad will be rendered to blur TextureA (and since it is part of RP2, the result of that blur will go to the screen).

Use Case #2
This will be a toon effect and has the following Render Passes:

Render Pass RP1:
Render State: Default
Render Target: TextureA - a color texture
Camera: Default Scene Camera

Render Pass RP2:
Render State: Default
Render Target: TextureB - a normal texture
Camera: Default Scene Camera

Render Pass RP3:
Render State: Default
Render Target: Screen
Camera: Orthographic Screen-Sized Camera

The Materials are largely the same as the first use-case. Even the full-screen quad is nearly identical, though it gets passed in both TextureA and TextureB and has a different effect.

Conclusions
The system has room for improvement. A shader fragment system would be nice but is a bit beyond our needs. It also necessitates writing Materials just to throw in test objects, which can be cumbersome (we have a 'test' Material to slap on for those instances). Binding Material Pass Names and Render Pass Names together is perhaps a bit of a mistake but works for our needs. Documentation also needs to be in place to remember all the different effect parameters and such.

Flaws aside, the system has been treating us pretty nicely. Nate can autonomously add complicated shader effects and change the rendering system at his whim. Later, it shouldn't be hard to write tools to make the process even smoother.

I hope you don't mind, we've reinstated your license to kill.

Leave a Comment

Spam Protection by WP-SpamFree