Radium Engine
1.5.20
|
A Ra::Engine::Data::Material is a way to control the appearance of an object when rendering. A Ra::Engine::Data::Material defines the Bidirectional Scattering Distribution function (BSDF) to be applied on an object.
A material is associated to the render geometry of an object (a Ra::Engine::Rendering::RenderObject) through a Ra::Engine::Rendering::RenderTechnique.
A Ra::Engine::Rendering::RenderTechnique describes how to use a Ra::Engine::Data::Material to render an object in OpenGL and consists in a set of named Ra::Engine::Data::ShaderConfiguration that encompass the OpenGL representation of a Material (glsl code and associated data) with, at least, vertex and fragment shaders. The name of each Ra::Engine::Data::ShaderConfiguration corresponds to the way a Ra::Engine::Rendering::Renderer manages its rendering loop.
A Ra::Engine::Data::ShaderConfiguration is associated with a Ra::Engine::Data::ShaderProgram bound by the Ra::Engine::Rendering::Renderer when rendering an object.
The Radium Engine exposes some predefined materials, the Radium Material Library, with render techniques corresponding to the Ra::Engine::Rendering::ForwardRenderer default renderer.
The Radium Material Library defines two default material :
The Radium Material Library can be used as this by any Radium Application or can be extended by an application or a Radium Plugin by implementing the corresponding interfaces as described in the Extending the Radium Material Library.
For each material of the library, a default Ra::Engine::Rendering::RenderTechnique, corresponding to the standard usage of the material by the Ra::engine::ForwardRenderer and an optional Ra::Engine::Data::EngineMaterialConverters::ConverterFunction, used when loading files to convert file representation of a Material to Radium representation of this material, are made available through the dedicated factories Ra::Engine::Rendering::EngineRenderTechniques and Ra::Engine::Data::EngineMaterialConverters.
See the Material registration into the Engine section of this documentation to learn more about these factories.
A simple usage of material is demonstrated in the HelloRadium Application.
When building scene to render, a Ra::Engine::Scene::Component must be added to a system as described into the Radium Engine programmer manual. A component holds one or several Ra::Engine::Rendering::RenderObject that will be drawn when rendering.
To define a Ra::Engine::Rendering::RenderObject and add it to the component, the geometry of a 3D object (a Ra::Engine::Mesh) must be associated with a Ra::Engine::Rendering::RenderTechnique that links to the required Ra::Engine::Data::Material.
To do that, the following steps must be done :
Note that this way of using the Radium Material Library is very related to the default Radium rendering capabilities exposed by the Radium forward renderer. See the Render technique management documentation to learn how to create your own Ra::Engine::Rendering::RenderTechnique, potentially without associated material.
If one wants to render objects without BSDF computation but with a specific color computation for the fragment, follow the guidelines from the dedicated section of this documentation.
The Radium Material Library could be extended to handle several Bidirectional Scattering Distribution function.
In order to make these extensions available to each Radium developer, the Radium Material Library defines several interfaces and factories for material management.
A material is defined by two programming interfaces. The Ra::Engine::Data::Material that defines the C++ interface made available for applications and plugins and a GLSL interface that allows shader reuse and composition for OpenGL rendering.
The C++ interface is implemented in a NameOfTheMaterial.hpp/.cpp
source file.
The GLSL interface is composed of several parts :
NameOfBSDF.glsl
file that will be included in every fragment shaders that need the implementation of the bsdf.The Ra::Engine::Data::Material interface defines the internal abstract representation of a Material. This interface defines all the methods required to parametrized the OpenGL pipeline for rendering and will be used mainly by the Ra::Engine::Rendering::RenderTechnique and the Ra::Engine::Rendering::Renderer classes.
When implementing this interface, and in order to make the material available to all applications or plugins, two static methods to register and unregister the material into the Radium Material Library must also be developed. These method will populate the Radium Material Library factories with specific helper functions to use the material in the default Radium forward renderer. Mainly, the registerMaterial()
method will record an helper function to build a material-related Ra::Engine::Rendering::RenderTechnique dedicated to the Radium forward renderer.
See the Render technique management for documentation on how to build such an helper function.
The MaterialTextureSet template ease a material's texture management. First define an enum with the managed texture semantic you want to have, by convention declare this enum in Ra::Engine::Data::TextureSemantics, named as your Material class. e.g. for BlinnPhongMaterial:
Then to set a texture according to the semantic:
Material which implement the Ra::Engine::Data::ParameterSetEditingInterface might be modified at runtime.
This interface exposes the method Ra::Engine::Data::ParameterSetEditingInterface::getParametersMetadata which return a json-formatted parameter set informations containing type, constraints and documentation on each editable parameter.
When implementing this interface, you need to return a correctly specified JSON.
The "editable" property specifies whether the boolean is editable, for instance it is not editable when it is linked with the presence of a texture.
When a parameter is referenced as an enum, the editing gui will present a combobox with the string representation of the enumaration values. Enumeration strings, could be defined in two ways. Either by associating a RenderParameter::EnumConverter with the material's parameter set or by defining the strings through the "values" field of the json object describing the enum. If the former is used, the "values" will not be used, even if present in the json description.
This is an unbounded number :
A number can have a minimum and/or a maximum :
A number can alternatively have an array of ranges where it is defined :
In this instance the number is ether 0 or greater than or equal to 1.0.
Being able to compose shaders in a specific renderer while taking profit of Radium Material Library (either included in the base engine or defined in plugins) require a clean definition of appearance computation process and the definition of a glsl interface.
In order to compute the appearance of an object, and according to the OpenGL/GLSL approach of rendering, several aspects might be taken into account and might be integrated into the interface definition to make a material renderer-agnostic.
The user or the implementer could rely on default implementation, provided by the Radium Material Library of most of these functionalities and concentrate its effort in developing the required functionalities for its material/renderer.
In order to compute the appearance of an object, one need to rely on parameters defined directly on the geometry of the object. Such parameters (position, normal, tangent, ...) are passed to the shader systems as vertex attributes.
In order to keep the appearance computation agnostic on the way vertex attribs are named or accessed, we must propose an abstract interface. But, and this is particular to these attributes, one can access to the attributes himself, on the vertex, or to the attributes interpolated by the rasterizer, on the fragment. Accessing the Attribute directly on the vertex (i.e. on a vertex shader) does not necessitate an interface as each shader must define its attributes and as the Mesh API allows to communicate between C++ and GLSL.
Note that the attributes accessed through the Vertex attrib interface must be defined in world space. Even if not necessarily efficient (some transformations might be computed twice), this will ensure more simple lighting computation. This might be changed in future version of Radium.
Each glsl component that needs to access to fragment-interpolated attributes must do that through this interface. The default declaration and implementation of this fragment interface is given in the file Shaders/Materials/VertexAttribInterface.frag.glsl
. This interface might be included in any fragment shader that want to use it by #include "VertexAttribInterface.frag.glsl"
.
It relies on some standard vertex attribute that have to be set by the vertex shader with respect to the following out binding :
The default implementation of the fragment interface is robust to inactive attributes, i.e. attributes that are not set by the vertex shader.
If one wants to implement its own vertex attribute interface, in order, e.g., to take advantage of some application specific data layout, the following declaration of the glsl functions exported by the interface must be done. The implementation of the interface could then be developed in the appropriate glsl file. Note that not all the functions must be pre-declared and the programmer could restrict himself to the only functions he needs :
Note also that if a function is not needed by a shader, there is no need to implement its interface.
Defining the micro-geometry procedurally or by using textures allows to de-correlates the geometric sampling from the appearance parameters sampling. The best example of procedural micro-geometry is normal mapping.
For a practical introduction to this kind of approach with opengl, the reader could refer to the Learn opengl tutorial.
For a more indepth presentation of these kind of techniques for realtime rendering, we encourage the reader to refer to Real-Time Rendering, Fourth Edition, by Tomas Akenine-Möller, Eric Haines, Naty Hoffman, Angelo Pesce, Michał Iwanicki and Sébastien Hillaire.
The microgeometry could also define which fragment is transparent. So, in order to be able to compute or discard transparent fragments, one need to define a toDiscard
function.
The interface (to be implemented in the file name_of_the_BSDF.glsl
) is then
Implementing or using the GLSL BSDF interface is based on the fact that the method Ra::Engine::Data::Material::getMaterialName() must return a string that contains the name_of_the_BSDF
implemented in a file named name_of_the_BSDF.glsl
. This file is preloaded at material registration into a glNamedString
to allow inclusion by others.
In order to be composable by Radium applications and renderers, this glsl file must only contains the implementation of the BSDF interface, with no void main(){...}
, the implementation of the micro-geometry interface and must only access to vertex attribs by using the vertex attribute interface or so on ...
This file must contain an inclusion guard :
The BSDF interface consists in the following
Some materials are not only reflective, hence implementing the BSDF interface, but also can be emissive. To allow a renderer to access the emissivity of a material the following GLSL function must defined in the same GLSL file than the BSDF and microgeometry interface :
When implementing the GLSL interface of a Material, the user can rely on several glsl components defined by the Engine. Glsl components are helper functions and data structure that could be used to develop specific shaders.
To make this common component available to users, and also each user defined component that want to be available to others, the Radium Engine defines a material component registration system that allows to populate and extend the Radium Material Library.
The registration and glsl component access system is made of 3 parts
a fourth part, optional, could be defined to convert a Ra::Core::Asset:MaterialData (file representation of a material) to a Ra::Engine::Data::Material.
Relying on already developed GLSL component require that this component could be included in the client code. In GLSL, this is done using the preprocessor directive #include </ComponentPath/ComponentName.glsl>
. As specified by the ARB_shading_language_include specification, included files must be preloaded in a GlNamedString object. The Ra::Engine::Data::ShaderManager provide a way to populate the GLNamedString ecosystem.
To register a GLSL component, from a file named SharedComponent.glsl
, so that it could be included in others GLSL files by #include </SharedComponent.glsl>
, one just need to do the following
The use of GLSL component to render object requires the building of a render-task specific OpenGL Program that link together the GLSL component and shaders addressing stages of the OpenGL Pipeline. According to the OpenGL specification, A vertex shader and a fragment shader stage are mandatory whereas tesselation end geometry shader are optional and depends only of the way one want to configure its pipeline for rendering.
To describe the OpenGL program configuration, and make it reusable, the configuration must be registered in the Ra::Engine::Data::ShaderConfigurationFactory. To do that, for each reusable configuration, one just need to do the following
once registered, a shader configuration could be fetched from the factory by its name :
A Ra::Engine::Rendering::RenderTechnique describes which Ra::Engine::Data::ShaderConfiguration a renderer will use for each of its rendering passes. Such a render technique could encompass a Ra::Engine::Data::Material but its meaning is larger than just computing the BSDF. In order to make a GLSL component that compute the appearance of a 3D object usable by a the default Radium renderer, one must define which shader configuration to use for each pass of the renderer. A Render technique will be used to configure the rendering of a geometry and the association between the geometry and the render technique is made in a Ra::Engine::Rendering::RenderObject. Making a GLSL component available for the Ra::Engine::Rendering::ForwardRenderer default renderer in Radium, on must define which Ra::Engine::Data::ShaderConfiguration to use for the passes Ra::Engine::Rendering::DefaultRenderingPasses::LIGHTING_OPAQUE, Ra::Engine::Rendering::DefaultRenderingPasses::Z_PREPASS and, if the appearance might be transparent, Ra::Engine::Rendering::DefaultRenderingPasses::LIGHTING_TRANSPARENT.
To do that, the Default render technique, the one that wil be used by Ra::Engine::Rendering::ForwardRenderer must be registered into the Ra::Engine::Rendering::EngineRenderTechniques factory. This is done according to the following
once registered, the render technique could then be associated with any render object using the following principle :
The Radium Material Library and related components are mainly designed to manage Materials as a representation of a Bidirectional Scattering Distribution function (BSDF).
When rendering, it is sometime useful to compute the final color of an object that do not rely on a bsdf but just on a specific color for each geometry fragment.
To define a custom fragment's color computation shader and use it with application provided parameters, the following steps are required :
Here is an example snippet.
Then the draw call of renderObject
uses the myConfig
as shader configuration. Before rendering, the method updateGL
on the parameterProvider
instance is called so that the shader's uniforms values are updated according the one stored in parameterProvider
.
Shader programs are managed through their ShaderConfiguration
, which contains the shader objects (vertex, fragment, ... shader) and the shader properties (not used for now though).
Unless you are sure you have access to an OpenGL context (which means you are in Ra::Gui::Viewer::startRendering()
or its callees), you must only manipulate the materials' render technique's shader configuration.
Basically, a shader configuration is a simple container with a name, and shader objects names.
There are two valid ways to build a shader configuration :
The second way is similar, but enables adding directly vertex and fragment shader through the constructor
If you know you will have to access the same shader configuration in multiple locations of your plugin(s), you can add it to the ShaderConfigurationFactory
and then access it easier :
Using first or second adder will work only if the name is not empty. Otherwise it will warn and return.
You also have to add your configuration to the factory if you want to be able to change a render object to this shader from the UI.