我正在尝试使用OpenGL ES 2.0(现在的iOS)制作2D游戏引擎。我在Objective C中编写了Application层,在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 textureId; vec4色调; 浮动alpha; struct Vertex * vertexArray; };
Shader类只包含着色器对象,编译和链接子例程等。
在Game Logic课程中,我实际上是渲染对象:
void GameLogic::update(float dt)
{
IRenderer * renderer = g_application->GetRenderer();
Actor * a = GetActor(id);
renderer->render(a->model);
}
请注意,即使Actor扩展了ISceneNode,我还没有开始实现SceneGraph。我会在解决这个问题后立即这样做。
任何想法如何改善这一点?相关设计模式等?
答案 0 :(得分:4)
着色器的重点是特定。通用API和状态机的膨胀使OpenGL长时间中毒(只需看看OpenGL-1.5 glTexEnv)。现代OpenGL(v3及更高版本)的重点在于摆脱这种通用性,因此着色器可以轻松地为手头的任务量身定制。不要将着色器视为与使用它们的渲染器代码分开的东西。事实上,着色器和客户端渲染器代码是互补的,通常是一致开发的。
答案 1 :(得分:0)
正如datenwolf所说,着色器不应该是通用的。
也就是说,从理论上讲,您只需按照自己的意愿更换着色器代码,只要它使用所有相同的uniform
,in
和out
参数,就不会真正实现能说出差异。也就是说,这根本不是一个好主意。
如果您想要这样的灵活性,您可能想要介绍一些排序中介脚本层。您的引擎可以为此脚本层提供您想要的所有信息,脚本层可以负责计算要设置的制服以及设置它们的内容。这不是一件微不足道的事情,但是你要做的事情并不容易。
答案 2 :(得分: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);
// ...
}
答案 3 :(得分:0)
我并不是自称是专家(我正处于学习使用OpenGL的同一阶段)但是我会尝试这样做:
也许你可以创建一个抽象的 RenderEffect 类,并将这些类的(列表)传递给RendererGLES20 :: render()。从中你可以派生出类似RippleEffect和BloomEffect的类。 RenderEffect对象将包含渲染器为特定于着色器进行必要的ogl状态调整所需的所有信息。 它本质上是一个容器类:
派生类可以声明一个构造函数,其参数与该特定效果所需的信息相匹配,并将其存储在其父类定义的列表中。因此,RippleEffect的构造函数可能具有delta t和diameter的参数,当它被调用时,它会为每个创建并存储UniformInfo对象。
最后,您可以完成所有数据驱动并指定文本文件中的所有信息。这只是创建像thecoshman提议的脚本系统的一小步。
PS: UniformInfo对象和VertexAttribInfo对象将存储或引用不同类型的值。为了解决这个问题,你可以为每种类型制作不同的类,但我建议只存储一个void *或类似的东西。另一个选择是使用模板,但我对objective-c一无所知,所以我不知道这是否可行。