Poriferous
Poriferous

Reputation: 1582

Multiple shader programs in main program

I program in OpenGL with shaders. I understand that each shader program can only have one of each type of shader; e.g. vertex, fragment, geometry etc. So, this is the concern I have and what my solution is to address it:

In any game, there would be texturing & lighting, environment mapping, shadows and all sorts of funky features; but are these all put into just one shader program? Or are they spread across various shader programs? My guess is the second, and so if I had:

`ShaderProgram *TextLightProgram;
   -> Compiles and links TextLight.vert
   -> Compiles and links TextLight.frag`
   -> Handles uniforms pertaining to said program

`ShaderProgram *SkyboxProgram;
   -> Compiles and links Skybox.vert
   -> Compiles and links Skybox.frag
   -> Handles uniforms pertaining to said program`

Then, is this valid for use in my main C++ program? Two sets (shader programs) of shaders each doing their own thing.

Because I don't want to obviously put these in main.cpp (I am doing this OOP-style) I would have two classes that inherit from an abstract GLAbbrev class (don't ask why I called it that; I couldn't think of another name!) wherein there would be two implemented classes, thus:

`GLAbbrev -> abstract class for handling ShaderPrograms

 GLAbbrev_TextLight 
    -> Inherits from GLAbbrev
    -> Implements functions and has ShaderProgram *TextLightProgram`

 GLAbbrev_Skybox
    -> Inherits from GLAbbrev
    -> Implements functions and has ShaderProgram *Skybox`

Thus, each class handles one shader program and renders the result accordingly. Then, in my Application class I have a vector of pointers to GLAbbrev which manages the GLAbbrevs. (Not a good idea; it should really be managed by another class, but bear with me here)

Application::init()
{
    glAbbrevs.push_back(new GLAbbrev_TextLight());
    glAbbrevs.push_back(new GLAbbrev_Skybox());
}

And then I simply call all the functions as one, such as:

Application::render()
{
    for (auto i : glAbbrevs)
        i.render();
}

As well as updating etc. With regards to glViewport and having a camera, I decided that it would be logical to have these in the abstract class already implemented. Thus, a resize function would be in GLAbbrev, and the class will also have a protected pointer to a Camera, because I have multiple versions of cameras. Thus, I can just declare Camera in the Application and pass that into glAbbrevs like:

for (auto i : glAbbrevs)
    i.setCamera(camera);

It could be problematic, but it will make-do for now. As you can see, this is a design approach I am prepared to take but am unsure whether it is the most efficient available. What is the opinion of other graphics programmers? Should shader programs be spread across their respective classes to do one thing? OR should there be ONE shader program with all the code inside their vertex, frag and geometry shaders; or should there just be one class that inherits from GLAbbrev, but has all the shader programs required to render a desired output?

Upvotes: 2

Views: 498

Answers (2)

Dimo Markov
Dimo Markov

Reputation: 440

If you're old-school, you might do everything with a big clunky shader, yes. Many people however, prefer the so called deferred rendering. It's a tactic by which you can divide the final frame on multiple layers, which you later combine in a specific way. Read more about it here.

I personally prefer the deferred way, because it gives a more concise methodology, less spaghetti code, and clean design.

Hope that helps!

Upvotes: 0

user2693587
user2693587

Reputation:

Every Well made game today uses multiple shaders each with a different purpose and a different implementation. For exmaple you might use a shader for a skybox and another shader for trees and this works great but it still isn't the best implementation, before we get there I would like to clarify that with this method there are two types of shaders, ones that draw to the screen (lights etc.) and ones that draw to framebuffers (shadows maps etc.).

The way it is done these days is by rendering shadows maps and other maps to framebuffers (basically editable textures in memory.) after all the maps have been rendered there is one big shader that handles lighting and adding shadows map and all that, but the problem is that the shader become very big so developers create custom shader readers. They read the shader from a file and while reading it they find their own custom keywords (Eg. they could have keywords such as 'import' or '#include' more likley '#include' scince it is a preprocessor.) and replace them with the action , so the might have a keywork '#include' and then a file, so it would be '#include res/shaders/lights.fs' and then they would replace that line with the the shader code in lights.fs, ofcourse they would also check for keyword in lights.fs, so at the end of the day they end up with one large shader made up of many little shaders and with that large shader they process all the framebuffers that was made before (this is done by simple passing them in as sampler2D.) and all the lights and fancy effects.

I reccomend checking out thebennybox on youtube, especially his 3D game engine series it will help you a lot with this, though the first half is in java he later switches to c++ and all the c++ code can be found on github.

Playlist: https://www.youtube.com/playlist?list=PLEETnX-uPtBXP_B2yupUKlflXBznWIlL5

Thebennybox: https://www.youtube.com/user/thebennybox/featured

Github: https://github.com/BennyQBD/3DEngineCpp

PS. A better way to call render is to give the render function a shader parameter that is bound and then rendered and finally unbound. Eg.

void render(Shader s) {
   shader.bind();
   this.mesh.render(); //Render here
   shader.unbind();
}

Upvotes: 1

Related Questions