This is one of a few tutorials in which no coding is done. However understanding topics covered here is essential to use nGENE Tech in a proper and efficient way. We will focus on the material system - system which lets you create and control visual quality of your application. Therefore you have to think of it as of one of the most important components of nGENE Tech engine.
Even though you have been using materials in all sections of the tutorial so far, you haven't learnt even the basic concepts lying behind them, nor how to create new materials. So it's high time to deal with them.
First of all you have to be aware of the existence of the hierarchy in the nGENE material system. It consists of:
- material manager,
- material libraries,
- render techniques,
- render passes,
- shaders and textures,
- shader constants.
Such a hierarchy makes material system more flexible and reusable. For instance one shader can be used across multiple materials and yet only one copy of it will exist at any given time (unless you specify differently). Also specifying different values for shader constants will make the same material look differently.
Now each of the hierarchy elements will be described briefly. For a thorough understanding of a material subsystem it is strongly advised to go through default.lib file and materialLib.xsd schema description to find out how different materials and techniques can be done. You can also read nGENE Tech class reference documentation to find out all methods for classes described in this tutorial.
It's a singleton class, i.e. only one instance of this class exists in nGENE Tech applications at any given moment. What it basically does is loading and unloading material libraries. By default, provided Framework loads only one library, namely "default.lib". It contains the most popular materials and those supported by nGENE Tech out-of-the-box. However you can load additional ones anytime you want to.
To create new material library from a file simply call:
FileName is a file name on a disk, Name is unique name identifying library in nGENE.
You can also create empty material library and fill it on run-time:
This time only identifier is required.
Material manager keeps track of loaded libraries. Following functions can be used to get access to already loaded library:
MaterialLibrary* getLibrary(const wstring& _name) MaterialLibrary* getLibrary(uint _index) int getIndex(MaterialLibrary* _lib) const void removeLibrary(const wstring& _name) void removeLibrary(uint _index) void removeLibrary(MaterialLibrary* _lib)
The first function returns registered library with a given name, the second one with a given index. Index can be obtained by calling next function, namely getIndex() and passing pointer to library as a parameter. Next functions are used to unload specified material library.
Material library is simply a set of materials. Libraries by default are stored in .xml files what makes any changes in them very fast and easy.
How you will organize materials into libraries is completely up to you. Whether you will use one per game level or one for a whole game world. Library itself shouldn't be associated with .xml files as not only they can be loaded from different file formats (provided you will code parsing method) but they can be created purely in memory on run-time. However, you will use .xml pretty frequently, especially at the beginning so here goes complete example file:
Material is an entity you assign to a surface to achieve specific visual effect eg. normal mapping or cel-shading. There are also so called post-processing materials, which are applied to the scene image rather than to single objects. This way you can achieved effects like bloom or SSAO. Post-processing is thoroughly covered in tutorial 2.3 so for now you should be aware that differences are visible rather from the way they are applied than material structure itself.
Each material is described by a set of parameters. By proper setting them, you can achieve transparency, create special post-processing effects or create bump mapping techniques. Actually there are no limitations to what you can do. Underneath there is a complete list of parameters. You don't have to specify all of them but only ones you need. Type of the parameter is specified in square brackets, additional specifications are listed in a description:
- name [string] - name of the material. It is required and should be unique in any library.
- alpha_test [bool] - specifies whether alpha test will be used for this material
- alpha_test_ref [int] - reference value for alpha test to test against
- src_blend [int] - specifies source blending mode when transparent is set to true. List of possible values is available here.
- dest_blend [int] - specifies destination blending mode when transparent is set to true. List of possible values is available here.
- order [Orders] - specifies whether material is applied prior to scene rendering, after it or whether it is regular object material. List of values is available here.
- transparent [bool] - specifies whether transparency ie. alpha blending is used by this material
- lightable [bool] - specifies whether this material is affected by lights
- two_sided [bool] - specifies whether this is two sided material
- z_bias [float] - value used to offset z values to avoid z-fighting issue
- z_write [bool] - specifies whether material is render to the depth buffer as well as to colour buffer
- cast_shadow [bool] - specifies whether material casts shadows
- emissive_factor [float] - strength of emission
- ambient [string] - ambient colour in format r;g;b;a
- diffuse [string] - diffuse colour in format r;g;b;a
- specular [string] - specular colour in format r;g;b;a
- fill_mode [FillModes] - specifies mode of rendering a material (eg. wireframe). List of possible values is available here.
Each of the material can work differently under some circumstances. For instance when the object is far away from the camera it could use simple material to save GPU power. This is where techniques come into play. Only one of them can be active at a time for any given material.
You can set active technique using:
where _index is valid index between 0 and Material::getRenderTechniquesNum().
Render pass is a single pass in a computation process. The vast majority of effects will require one pass only but in some more complex cases (eg. HDR or SSAO) multiple passes might be required. Using multiple passes instead of one is often caused by the instruction limits in pre-SM 4.0 capable graphics adapters. Also sometimes it might be impossible to run all calculations in a single pass eg. in case of scene luminance calculation.
Each render pass can have many shaders, however maximum one of each type (vertex, geometry and pixel) can be used for each pass.
Note that render pass is represented by 'pass' tag in the .xml format.
Render pass also have some parameters you can set. They are listed below. Type of the parameter is specified in square brackets, additional specifications are listed in a description:
- name [string] - name of the pass. It is required attribute.
- order [Orders] - specifies whether pass is applied prior to scene rendering, after it or whether it is regular object pass. List of values is available here.
Shader is a program typically written in some high level shading language (eg. HLSL which is currently supported). Some time ago shaders were implemented using GPU assembly language. Through shaders you can make use of your GPU. Materials without shaders are nothing really.
There are several types of shaders so far:
- vertex shader - processing vertices (vertex_shader tag)
- geometry shaders - processing geometry primitives and allowing to generate new primitives on-the-fly (geometry_shader tag)
- pixel shaders - processing pixels (pixel_shader tag)
What you have to note is that not all shaders will run on all GPUs. There are several shader models each having different capabilities and limits. Therefore shaders requiring Shader Model 3.0 will not run on SM 3.0 incapable graphic adapter.
Shaders in nGENE have a number of parameters:
- name [string] - name of the pass. It is optional attribute. If not specified explicitly, it has the same value as file attribute.
- file [string] - filename containing shader data. It is required attribute. If you want to load same shader several times you have to specify different values of 'name' attribute.
- profile [VSProfiles/GSProfiles/PSProfiles] - profile of the shader program. Possible values depend on the type of the shader and they are the same as in DirectX SDK.
- function [string] - entry point of the shader. If not specified, it is automatically set to 'main'. If such function is not found in the shader, an error arise.
- defines [string] - ';'-delimeted string containing macros. You can use these macros to control your shaders behaviour.
Textures are essentialy bitmaps loaded from files on disk, dynamic textures accessed and modified on-the-fly and render targets which store data about rendered scene. They store important information for shaders whether it is diffuse colour, normal vectors or information about height of each pixel of the terrain. Typical texture might look like this:
Default DirectX 9.0c renderer makes use of deferred shading technique and therefore there are many textures related to it (storing diffuse colour, normal mapping and position data).
Possible parameters for textures are listed below. Type of the parameter is specified in square brackets, additional specifications are listed in a description:
- name [string] - identifier of the texture in nGENE. It is required parameters.
- sampler [int] - it is id of texture unit to which this texture will be assigned. In DirectX 9.0 it is often value from 1 to 8. It is required attribute.
- file [string] - name of the file if texture is loaded from disk. Should be skipped if texture is a render target.
- filter_min [FilterTypes] - filter to be applied for minification. Possible values are listed here.
- filter_mag [FilterTypes] - filter to be applied for magnification. Possible values are listed here.
- filter_mip [FilterTypes] - filter to be applied for mip-mapping. Possible values are listed here.
- addressing_mode [Addressings] - addressing mode. Possible values are listed here.
- format [TextureFormats] - file format. It is especially important when creating render target or dynamic texture. In case of textures loaded from disk you will probably omit this parameter to load texture in a format it is stored in. Possible values are listed here.
- usage [TextureUsages] - this format specifies whether texture is regular texture, dynamic one or render target.
- height_ratio [string] - this value describes height of the texture. If it is in format "32px" than its height will be 32 pixels. If it will set as "0.25" its height will be fourth of the screen height.
- width_ratio [string] - this value describes width of the texture. If it is in format "32px" than its width will be 32 pixels. If it will set as "0.25" its width will be fourth of the screen width.
- bind_as_texture [bool] - this attribute is important only for render targets. If it is set to true it will be treated for a given render pass as regular texture and thus it can be sampled in shaders.
- clear_target [bool] - this attribute is important only for render targets. If is set to true, colour information will be cleared when binding this texture.
- clear_depth [bool] - this attribute is important only for render targets. If is set to true, depth information will be cleared when binding this texture.
Many shaders are flexible by exposing their global constants. By setting different values for different materials you can use the very same shader and get different result for each material using it.
As always, shader constants can be controlled by some parameters. One of the most important is dynamic. It specifies whether constant should be bound for each single object or once per shader instance. For instance world matrix should be dynamic because each object have a different one whereas camera position should be static because in most cases it won't during frame rendering (rather frame preprocessing). Why it is so important? Imagine you have 100 objects with the same material assigned. Material have 1 dynamic and 3 static constants. So there are 1 * 100 + 3 = 103 constants bindings per frame. If all constants were made dynamic, it would result in 4 * 100 bindings! All operations of setting constants, changing shaders, textures and so on are pretty costly in terms of performance (it takes some CPU time to "pass" data to GPU).
Here is full list:
- name [string] - name of the constant. It is required attribute.
- type [string] - data type of the constant. List of supported types is listed in bin/media/shaderconfig.txt but you can easily add more types there.
- semantic [string] - name of the semantic. You can think of semantic as of function being applied to constant to compute its value. For example "World" semantic returns world matrix of currently processed object. If semantic is specified you don not need to use type attibute as it will be overriden. List of semantics is specified in bin/media/shaderconfig.txt. You can add new semantics easily using eg. Lua scripts.
- dynamic [bool] - described in detail at the beginning of this section.