1

我正在尝试使用 OpenGL ES 2.0(目前为 iOS)制作 2D 游戏引擎。我用 Objective C 编写了应用程序层,并用 C++ 编写了一个单独的自包含 RendererGLES20。在渲染器之外不进行任何 GL 特定调用。它运行良好。

但是我在使用着色器时遇到了一些设计问题。每个着色器都有自己独特的属性和制服,需要在主绘制调用之前设置(在本例中为 glDrawArrays)。例如,为了绘制一些几何图形,我会这样做:

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(.......);
}

如您所见,此代码非常严格。如果我要使用另一个着色器,比如涟漪效应,我将需要传递额外的制服、顶点属性等。换句话说,我必须更改 RendererGLES20 渲染源代码才能合并新的着色器。

有没有办法让着色器对象完全通用?比如如果我只想更改着色器对象而不担心重新编译游戏源怎么办?有什么方法可以使渲染器与制服和属性等无关?即使我们需要将数据传递给制服,最好的地方是什么?模型课?模型类是否知道着色器特定的制服和属性?

下面显示了 Actor 类:

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

模型控制器类:class ModelController { class IShader * shader; int 纹理标识;vec4 色调;浮动阿尔法;结构顶点 * 顶点数组;};

着色器类只包含着色器对象、编译和链接子例程等。

在 Game Logic 类中,我实际上是在渲染对象:

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

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

请注意,尽管 Actor 扩展了 ISceneNode,但我还没有开始实现 SceneGraph。一旦我解决了这个问题,我就会这样做。

任何想法如何改善这一点?相关的设计模式等?

4

4 回答 4

4

着色器的全部意义在于具体化。通用 API 和状态机的膨胀长期毒害了 OpenGL(只需查看 OpenGL-1.5 glTexEnv)。现代 OpenGL(v3 及更高版本)的全部意义在于摆脱这种通用性,以便可以轻松地为手头的任务定制着色器。不要认为着色器与使用它们的渲染器代码是分开的。事实上,着色器和客户端渲染器代码是互补的,通常是统一开发的。

于 2013-07-03T11:37:59.370 回答
0

正如 datenwolf 所说,着色器不应该是通用的。

也就是说,理论上,您可以根据需要更换着色器代码,只要它使用所有相同的参数uniformin并且out没有什么能真正分辨出区别。也就是说,这可能根本不是一个好主意。

如果您想要这种灵活性,您可能希望考虑引入某种中间脚本层。您的引擎可以为该脚本层提供您想要的所有信息,并且脚本层可以负责确定要设置什么制服以及将它们设置为什么。这不是一件微不足道的事情,但是你想要做的事情并不容易。

于 2013-07-03T12:13:46.780 回答
0

当没有属性或制服更改时,您可以更改着色器而无需重新编译您的 c++ 代码。您可以通过从文本文件加载着色器代码glShaderSource然后对其进行编译,或者通过加载预编译的二进制文件来执行此操作glShaderBinary

要获得对不同渲染方法的某种封装,您可以拥有从 RendererGLES20 继承的不同渲染器。这样,只有渲染器需要知道着色器需要哪些属性和制服。

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);
    // ...
}
于 2013-07-03T13:02:39.287 回答
0

我并没有声称自己是专家(我与您处于学习使用 OpenGL 的完全相同的阶段)但这是我会尝试的:

也许您可以创建一个抽象的RenderEffect类并将这些(列表)传递给 RendererGLES20::render()。从中可以派生出 RippleEffect 和 BloomEffect 等类。RenderEffect 对象将包含您的渲染器所需的所有信息,以针对您的着色器进行必要的 ogl 状态调整。它本质上是一个容器类:

  • 包含统一名称/id、转置布尔值和(指向)值的“ UniformInfo ”对象的列表。可以查询类型和组件计数,因此无需在此处存储。
  • ' VertexAttribInfo ' 对象列表,其中包含 glVertexAttribPointer*() 的所有必要信息,就像 UniformInfo 一样。同样,可以查询组件计数等一些内容。

派生类可以声明一个构造函数,其参数与该特定效果所需的信息相匹配,并将其存储在其父类定义的列表中。因此,RippleEffect 的构造函数可能具有 delta t 和直径的参数,当它被调用时,它会为每个参数创建并存储一个 UniformInfo 对象。

最后,您可以使所有这些数据驱动并在文本文件中指定所有信息。与创建像thecoshman 提议的脚本系统相比,这只是一步之遥。

PS: UniformInfo 对象和 VertexAttribInfo 对象会存储或引用不同类型的值。为了解决这个问题,你可以为每种类型创建不同的类,但我建议只存储一个 void* 或类似的。另一种选择是使用模板,但我对objective-c一无所知,所以我不知道这是否可能。

于 2015-10-29T15:06:39.277 回答