Developer Documentation
|
A renderer plugin class has to implement at least the OpenFlipper RenderInterface class. If the plugin wants to make use of the ACG shader pipeline which allows the renderer to have complete control over every draw call, the renderer also has to implement the ACG::IRenderer interface. The IRenderer interface already provides basic convenience functions, such as building and sorting the list of draw-calls to help getting started. In this tutorial we will focus on the implementation process of the toon renderer plugin, which makes use of the ACG::IRenderer interface and its convenience functions.
The toon renderer requires a set of modified shaders, that replace the default lighting with cel shading. A ShaderModifier is used to replace the lighting code provided by the ACG::ShaderGenerator with custom shader code. The new lighting functions are defined in the shader include file "celshading.glsl", which is loaded and inserted at run-time.
The file contains cel shading functions for directional, spot and point lights called: LitDirLight_Cel(), LitPointLight_Cel(), LitSpotLight_Cel. We include this file in the generated vertex and fragment shader with the modifyVertexIO and modifyFragmentIO functions of the ShaderModifier. Additionally a new uniform is added to the uniform list of each shader, which is related to the cel shading technique.
Next, we tell the ShaderGenerator that our modifier replaces the default lighting functions by implementing the corresponding function.
The ShaderGenerator then proceeds to call the modifyLightingCode() implementation of the modifier, which is supposed to generate the lighting shader code for each active scene light. To do this we check the type of the light, call the appropiate cel shading function and accumulate the lighting colors:
For a list of uniforms and variables generated by the ShaderGenerator refer to the "Dynamic shader assembly overview" documentation page.
The toon renderer not only performs cel shading, but also implements a post-processing step that outlines the silhouette of objects. A texture containing scene depth information is needed in this step, which can be initialized with a ShaderModifier. In fact, a single modifier is sufficient which can do both: cel shading and depth output.
The depth output texture is declared in modifyFragmentIO():
We still have to generate a code snippet that writes to the new fragment output channel. This shall be done at the end of the fragment shader, thus we implement the modifyFragmentEndCode() function:
A modifier has to be registered to the ShaderGenerator before it can be used. Once the modifier is registered, is has received a unique modifierID which is used to apply a modifier at run-time to a shader.
The following steps are necessary to register the modifer:
The modifier can now be used and applied to any shader generated by ShaderGenerator. A modified shader is generated by setting the usage parameter in either the ShaderProgGenerator or the ShaderCache:
The toon renderer supports multiple viewports by allocating a separate offscreen FBO for each viewer. The currently active viewport can be queried with the ViewerProperties descriptor, which is passed to the render() function. It is recommended that any render plugin treats viewport specific ressources such as FBOs in the same manner, if offscreen rendering is performed.
Gathering RenderObjects and executing draw calls is done with the help of convenience functions provided by the IRenderer interface:
Next, we render the scene into an FBO with the cel-shading modifier. The FBO has two color attachments: attachment0 contains an RGBA texture storing scene color, attachment1 contains an R32f texture storing scene depths. This information has to be passed to the shader program such that the fragment outputs are mapped to the correct attachments: If the output targets do not match the FBO setup, the shader has to be relinked.
RenderObjects, which represent a draw call with many opengl states, can be accesses via the inherited sortedObjects_ array. The function getNumRenderObjects() returns the total number of render-objects in the list. A draw call can be executed with the convenience function renderObject(), but can also be performed manually with the information contained in the RenderObject data.
In a second pass, a post processing shader is used to perform silhouettte outlining on the previously computed FBO. This pass renders into the input FBO, which in most cases is the back buffer. A simple call to the convenience function from IRenderer is enough to bind the input FBO:
After configuring the post processing shader, a screen aligned quad which covers the complete viewport is rendered. Since this is an often used technique in post-processing, ACG provides another convenience function for drawing screen quads with post projected coordinates:
The render() implementation ends with a call to another convenience function: finishRenderingPipeline(). This restores common OpenGL states to their default value.