Adding a custom shader generator

Modern graphics cards and graphics APIs like Direct3D or OpenGL allow developers to program, and therefore customize, the behavior of parts of the graphics pipeline using shader programs, written in a C-like programming language. These shader programs define how vertices are being transformed, which textures are used for retrieving color values, and which final color pixels we have on the screen.

Luckily, Panda3D adds a very nice abstraction layer on top of this shader system, which is called the shader generator. As soon as you call the setShaderAuto() method on a node in the scene graph, the shader generator kicks in and pieces together the right shaders, depending on the render state (textures, colors, lights, and so on.) of your objects.

Sometimes the built-in shader generator and the code it creates may not suit your needs. Therefore, this recipe will explore and show how to add a custom shader generator to Panda3D.

Getting ready

This recipe requires you to modify and build the source code of Panda3D. Please take your time to review Building Panda3D from source code to get set before proceeding with the following steps. You can find this recipe in Chapter 1.

How to do it...

Complete the following steps to create a custom shader generator:

  1. From the top level directory of your unpacked Panda3D source tree, navigate to the subfolder pandasrcpgraphnodes.
  2. Create two new files called customShaderGenerator.h and customShaderGenerator.cxx.
  3. Open customShaderGenerator.h and add the following code:
    #ifndef CUSTOMSHADERGENERATOR_H
    #define CUSTOMSHADERGENERATOR_H
    #include "shaderGenerator.h"
    class EXPCL_PANDA_PGRAPHNODES CustomShaderGenerator : public ShaderGenerator {
    PUBLISHED:
    CustomShaderGenerator(PT(GraphicsStateGuardianBase) gsg, PT(GraphicsOutputBase) host);
    virtual ~CustomShaderGenerator();
    virtual CPT(RenderAttrib) synthesize_shader(const RenderState *rs);
    public:
    static TypeHandle get_class_type() {
    return _type_handle;
    }
    static void init_type() {
    ShaderGenerator::init_type();
    register_type(_type_handle, "CustomShaderGenerator",
    ShaderGenerator::get_class_type());
    }
    virtual TypeHandle get_type() const {
    return get_class_type();
    }
    virtual TypeHandle force_init_type() {init_type(); return get_class_type();}
    private:
    static TypeHandle _type_handle;
    };
    #endif
    
  4. Open the file customShaderGenerator.cxx and add these lines of code:
    #include "customShaderGenerator.h"
    TypeHandle CustomShaderGenerator::_type_handle;
    CustomShaderGenerator::CustomShaderGenerator(PT(GraphicsStateGuardianBase) gsg, PT(GraphicsOutputBase) host) :
    ShaderGenerator(gsg, host) {
    }
    CustomShaderGenerator::~CustomShaderGenerator() {
    }
    CPT(RenderAttrib) CustomShaderGenerator::
    synthesize_shader(const RenderState *rs) {
    }
    

    The last line of customShaderGenerator.cxx has to be blank for the code to compile properly!

  5. Open shaderGenerator.cxx and copy and paste the method body of synthesize_shader() to the synthesize_shader() method in customShaderGenerator.cxx.
  6. In customShaderGenerator.cxx, replace all occurrences of saturate(dot(l_eye_normal.xyz, lvec.xyz)) with saturate(0.5 * dot(l_eye_normal.xyz, lvec.xyz) + 0.5).
  7. Open the file lightLensNode.h. Find the following code lines and add the highlighted code:
    friend class GraphicsStateGuardian;
    friend class ShaderGenerator;
    friend class CustomShaderGenerator;
    
  8. In the file pgraphnodes_composite2.cxx, find the line that reads #include "shaderGenerator.cxx" and add the line #include "customShaderGenerator.cxx" below it.
  9. Open config_pgraphnodes.cxx. Below the line #include "shaderGenerator.h", add the line #include "customShaderGenerator.h". Also find this line of code: ShaderGenerator::init_type();. Add a new line below, containing CustomShaderGenerator::init_type();.
  10. Go to the pandasrcdxgsg9 subdirectory of the source tree.
  11. In the file dxGraphicsStateGuardian9.cxx, add the line #include "customShaderGenerator.h" below the other includes. Also find and replace the following code line with the highlighted one:
    _shader_generator = new ShaderGenerator(this, _scene_setup->get_display_region()->get_window());
    _shader_generator = new CustomShaderGenerator(this, _scene_setup->get_display_region()->get_window());
    
  12. Repeat step 11 for the file glGraphicsStateGuardian_src.cxx in the pandasrcglstuff subdirectory.
  13. Proceed through the steps of Building Panda3D from source code to compile your custom build of Panda3D.

How it works...

We begin implementing our custom shader generator by defining the interface of our new CustomShaderGenerator. We derive this class from the default shader generator and declare our own constructor, destructor, and synthesize_shader() implementations. The code of the synthesize_shader() method will later be handling the generation of the shader code.

Don't be irritated by the PUBLISHED: line and the TypeHandle code. This stuff is needed internally to register the class and methods with Python.

We then proceed to add method implementations to customShaderGenerator.cxx. The constructor calls its base constructor and the destructor remains empty. The real magic happens within the synthesize_shader() method, which we base upon the code of the original code to remain compatible with the existing render states. Unfortunately, the shader generator system is not written in a very modular way, which means we need to copy the method body of the original implementation of the method.

For the purpose of this recipe, we then change the standard Lambert lighting equation slightly by moving the range of possible results from [-1, 1] to [0, 1], making lit scenes appear brighter, as shown in the following comparison with the standard implementation to the left and our custom lighting to the right:

How it works...

In step 7, we declare our CustomShaderGenerator class to be a friend of LightLensNode, because our base class needs to access some private and protected members of this class when the shader is put together.

The last steps before we can compile our custom version of Panda3D are necessary to add the new class to the build system and register it with the Python API. The most important steps in this closing part are 11 and 12, where we replace the instantiation of the standard ShaderGenerator class with our CustomShaderGenerator.

There's more...

This recipe only made very slight changes to the original implementation of the shader generator. For more extensive changes to this system, you might consider taking a close look at the ShaderGenerator class and its analyze_renderstate() method, which operates on instances of RenderState to determine which shader parts are then needed in synthesize_shader() to produce the proper shader permutation.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset