I've been trying to craft a new material system for a shooter I'm working on, primarily addressing the redundancy and inflexibility of most existing systems. I'm looking for critiques on the system to see if there are any major downfalls that I'm not preparing myself for, or any improvements that could be made.
I've been working with a few concepts in order to facilitate this, mainly going off of a fairly typical material-and-FX setup, but making a few changes to it:
The first is material templating. Instead of declaring materials explicitly, material properties are usually generated using templates. When a material is requested, a template script is looked up by its name first, and if that fails, a default template is checked for in each directory down to root. A template script declares a couple things:
- What textures need to be loaded for a material (usually based on the material's name)
- What profile to use for it (see the next part)
- Additional information such as texture coordinate modification effects
Note that textures do not need to be explicitly declared in a template to be used, the renderer is capable of making special-case textures available to the pixel shader stage simply by checking if the sampler is declared.
The second concept is dynamic technique generation using profiles, which play a similar role to FX files, but are a good deal more flexible. Rather than being a fixed set of state declarations for various hardware and situations, profiles are scripts that generate techniques on the fly and have access to scene and surface parameters from the renderer.
Compiled shaders are also looked up (or in the case of GLSL, recompiled) by their preprocessor definitions, which can also be adjusted via the profile script.
Internally, a surface is assigned a profile tree, which is generated using the current scene and parameters, including the textures from the template. Texture dependencies are allowed to not resolve, so a material can be declared with a gloss layer, for example, and if none is found, the profile will be aware of it.
Profile trees are cached based on the static parameters (including the texture availability for the surface), and represent a tree of results from the dynamic parameter checks. For example, checking if a model is a static model with precomputed lighting is a static check, while checking if there are any lights in the vicinity of the model is a dynamic check.
The tree is checked each time a surface is rendered, and if a set of techniques is not available for the current scene parameters, the profile is run again to generate a new set of techniques and insert it into the tree. Since script execution is slow, this ensures that the script is only actually run when scene parameter changes make it necessary.
In order to ensure that the static list will never change for a profile tree, attempting to check a static parameter after checking a dynamic one throws an error.
There are a couple key features of this:
- Artists only need to deal with templates and knowing what general category of surface a material falls in to.
- Multi-layer and complex surfaces can still be declared in the templates.
- Redundant material and technique declaration is mostly eliminated.
- Handling of special cases and fallbacks is moved from the engine code to the profiles.