Domanda

I am trying to make a 2D game engine using OpenGL ES 2.0 (iOS for now). I've written Application layer in Objective C and a separate self contained RendererGLES20 in C++. No GL specific call is made outside the renderer. It is working perfectly.

But I have some design issues when using shaders. Each shader has its own unique attributes and uniforms that need to be set just before the main draw call (glDrawArrays in this case). For instance, in order to draw some geometry I would do:

void RendererGLES20::render(Model * model)
{
    // Set a bunch of uniforms
    glUniformMatrix4fv(.......);
    // Enable specific attributes, can be many
    glEnableVertexAttribArray(......);
    // Set a bunch of vertex attribute pointers:
    glVertexAttribPointer(positionSlot, 2, GL_FLOAT, GL_FALSE, stride, m->pCoords);

    // Now actually Draw the geometry
    glDrawArrays(GL_TRIANGLES, 0, m->vertexCount);

    // After drawing, disable any vertex attributes:
    glDisableVertexAttribArray(.......);
}

As you can see this code is extremely rigid. If I were to use another shader, say ripple effect, i would be needing to pass extra uniforms, vertex attribs etc. In other words I would have to change the RendererGLES20 render source code just to incorporate the new shader.

Is there any way to make the shader object totally generic? Like What if I just want to change the shader object and not worry about game source re-compiling? Any way to make the renderer agnostic of uniforms and attributes etc?. Even though we need to pass data to uniforms, what is the best place to do that? Model class? Is the model class aware of shader specific uniforms and attributes?

Following shows Actor class:

class Actor : public ISceneNode
{
    ModelController * model;
    AIController * AI;
};

Model controller class: class ModelController { class IShader * shader; int textureId; vec4 tint; float alpha; struct Vertex * vertexArray; };

Shader class just contains the shader object, compiling and linking sub-routines etc.

In Game Logic class I am actually rendering the object:

void GameLogic::update(float dt)
{
    IRenderer * renderer = g_application->GetRenderer();

    Actor * a = GetActor(id);
    renderer->render(a->model);
}

Please note that even though Actor extends ISceneNode, I haven't started implementing SceneGraph yet. I will do that as soon as I resolve this issue.

Any ideas how to improve this? Related design patterns etc?

È stato utile?

Soluzione 2

When no attributes or uniforms change then you can change the shader without having to recompile your c++ code. You do this by either loading you shader code from a text file with glShaderSource and then compiling it, or by loading a precompiled binary file with glShaderBinary.

To get some sort of encapsulation of the different rendering methods you can have different renderers that inherit from RendererGLES20. That way only the renderer needs to know what attributes and uniforms the shader needs.

class RendererGLES20
{
public:
    virtual void render(Model * model) = 0;

    // ...
}

class RippleRenderer : public RendererGLES20
{
    virtual void render(Model * model);
    // ...
}

class BlinnPhongRenderer : public RendererGLES20
{
    virtual void render(Model * model);
    // ...
}

class CartoonRenderer : public RendererGLES20
{
    virtual void render(Model * model);
    // ...
}

Altri suggerimenti

The whole point of shaders is to be specific. Generic APIs and inflation of the state machine poisoned OpenGL for a long time (just look at the OpenGL-1.5 glTexEnv). The whole point of modern OpenGL (v3 and beyond) was to get rid of this genericity, so that shaders could be tailored for the task at hand with easy. Don't think shaders to be something separate from the renderer code that uses them. In fact shaders and client side renderer code are complementary and are usually developed in unison.

As datenwolf says, shaders are not supposed to be generic.

That said, in theory, you can just swap out shaders code as you wish, as long as it uses all the same uniform, in and out parameters nothing will really be able to tell the difference. That said, this is probably not a good idea at all.

If you want such flexibility, you probably want to look at introducing some sort intermediary scripting layer. Your engine can provide this scripting layer all the information you want it to, and the script layer can take care of working out what uniforms to set and what to set them to. It is not a trivial thing do, but then what you are trying to do is not exactly easy.

I'm not claiming to be an expert (I'm in the exact same stage of learning to use OpenGL as you) but here is what I would try:

Maybe you can make an abstract RenderEffect class and pass a (list of) of these to RendererGLES20::render(). From it you would derive classes such as RippleEffect and BloomEffect. A RenderEffect object would contain all the information your renderer needs to make the necessary ogl state adjustments specific to your shader. It would essentially be a container class of:

  • A list of 'UniformInfo' objects containing uniform name/id, transpose bool and (a pointer to) the value. Type and component count can be queried so no need to store it here.
  • A list of 'VertexAttribInfo' objects containing all necessary info for glVertexAttribPointer*() just like UniformInfo. Again, some things like component count can be queried.

Derived classes can declare a constructor with arguments that match the info needed for that specific effect and would store it in the lists defined by their parent class. So the constructor of RippleEffect might have parameters for delta t and diameter and when it is called it creates and stores a UniformInfo object for each.

Finally, you can make all of this data-driven and specify all of the info in a text file. That would be just one step short of creating a scripting-system like thecoshman proposed.

PS: UniformInfo objects and VertexAttribInfo objects would store or reference values of different types. To deal with this you could make different classes for each type but I recommend storing just a void* or the likes. Another option is using templates but I don't know anything about objective-c so I don't know if that is possible.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top