2013-07-03 51 views
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渲染源代码,以便合并新的着色器。

有什么办法让着色器对象完全通用?喜欢如果我只是想改变着色器对象而不担心游戏源重新编译?任何方式使渲染器不受制服和属性等的影响?即使我们需要将数据传递给制服,那么最好的地方是什么?模型类?模型类是否意识到着色器特定的制服和属性?

继节目演员类:

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

模拟控制器类: 类ModelController { 类IShader *着色器; int textureId; vec4 tint; float alpha; struct Vertex * vertexArray; };

Shader类只包含着色器对象,编译和链接子程序等

在游戏逻辑类我实际渲染对象:

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

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

请注意,即使演员延伸ISceneNode ,我还没有开始实施SceneGraph。我会尽快解决此问题。

任何想法如何改善?相关设计模式等?

回答

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); 
    // ... 
} 
+0

那么在实际情况下,属性和制服会发生变化。此外,在一个典型的游戏中,可能会有10-15个着色器。所以这是15个扩展类,只适用于RendererGLES20。如果我想将游戏移植到Wii(固定管道)或XNA(directX)上,该怎么办?您可以制作一个C++ DLL和C#接口,但是您必须重写整个渲染器类和其他15个扩展渲染器。此外,每次添加一个新的着色器时,我都必须扩展一个新的渲染器,如果我决定放弃某些着色器,则必须删除相关的类。不是一个好主意! – fakhir

+0

我想知道我们是否可以使渲染器完全不知道着色器的内部细节。 – fakhir

+0

好吧,就像datenwolf所说的那样,OpengGL的新版本牺牲了程序员的便利性,以便提高性能和灵活性,所以没有办法使它变得通用。这只是分享一些渲染代码和成员变量的一种方式。 – Simon

4

着色器的全部要点是特定。很长一段时间,通用API和状态机的通货膨胀毒化了OpenGL(请看OpenGL-1.5 glTexEnv)。现代OpenGL(v3及更高版本)的全部要点是摆脱这种通用性,以便着色器可以轻松地适应手头的任务。不要认为着色器是独立于使用它们的渲染器代码的东西。实际上着色器和客户端渲染器代码是互补的,通常是一致开发的。

+0

在上面的渲染器代码中,如何在不更改渲染器源**的情况下将其与另一个着色器**交换?例如,我需要将默认着色器更改为将着色器渲染10秒,然后将具有可能不同制服和属性的投影着色器应用于接下来的20秒,然后返回默认着色器? – fakhir

+2

@ user2370784:呃,你没有。无论如何,bloom着色器需要不同的渲染器(源代码)。在你的想法中获得这个:GLSL着色器源代码是你的渲染器源代码的一部分。如果你改变GLSL着色器,你需要一个不同的渲染器。 GLSL着色器不是你应该随意丢弃的模块。例如,一个涟漪着色器需要完全不同的输入比一个绽放着色器;不仅仅是不同的制服,还有不同的顶点属性。GLSL和渲染器代码齐头并进。 – datenwolf

+0

嗯,这很有趣。我认为着色器是可拆卸的组件,比如纹理(或多或少)。那么下面@Simon给出的解决方案是最好的?根据特定的着色器继承一堆渲染器? – fakhir

0

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

也就是说,理论上,你可以换出着色器的代码,你想,只要它采用全同uniforminout参数什么都不会真的能分辨出来。也就是说,这可能不是一个好主意。

如果你想要这样的灵活性,你可能想看看介绍一些排序中介脚本层。您的引擎可以为您提供所需的所有信息,脚本层可以提供此脚本层,并且可以负责制定要设置的制服以及设置的内容。这不是一件微不足道的事情,但是你所要做的并不是那么简单。

+0

嗯,我不是在谈论着色器代码本身的概括(即:用ifs/defs,switch/cases等来扩大它)。我正在着眼于使着色器对象层(实际上管理着色器)更加高效和抽象。如果我想要将bloom着色器加载5秒然后返回默认着色器,该怎么办?在不改变渲染器代码的情况下,我无法在上述源中做到这一点。 – fakhir

+0

从它的声音中,你并不完全理解你将如何管理着色器。它们在一个程序中被链接在一起,所以如果你想使用一个不同的程序,你只需要'使用'它,然后'使用'下一个。如果你想改变一个程序中的一个着色器,你将不得不重新链接整个程序。这只是OpenGL工作方式的一个难以“限制”。 – thecoshman

+0

我意识到这一点。考虑以下内容:默认着色器将MVP矩阵与纹理采样器一起统一。如果我想说出涟漪效应,我不仅要通过MVP和采样器,还要通过delta时间(或其他变量),以便它可以正确计算涟漪。我也想把涟漪的直径作为制服发送。现在这些额外的制服在默认着色器中无法使用。所以你不能简单地在上面提到的情况下做“glUseProgram”!你将不得不改变通过uniforms/attribs到着色器的代码。 – fakhir

0

我不自称是专家(我在学习使用OpenGL作为你完全相同的阶段),但这里是我想尝试:

也许你可以做一个抽象RenderEffect类并将其中的一个(列表)传递给RendererGLES20 :: render()。从中你可以派生类如RippleEffect和BloomEffect。 RenderEffect对象将包含渲染器对着色器进行必要的ogl状态调整所需的所有信息。 这将基本上是一个容器类的:

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

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

最后,您可以制作所有这些数据驱动的并指定文本文件中的所有信息。这只不过是创建一个类似于所提出的思科系统的脚本系统而已。

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