主程序中的多个着色器程序

Multiple shader programs in main program

我使用着色器在 OpenGL 中编程。我知道每个着色器程序只能有一种类型的着色器;例如顶点,片段,几何等。所以,这是我所关心的问题,我的解决方案是解决它:

在任何游戏中,都会有纹理和光照、环境映射、阴影和各种时髦的功能;但是这些都放在一个着色器程序中吗?或者它们分布在各种着色器程序中?我的猜测是第二个,所以如果我有:

`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`

那么,这是否适用于我的 C++ 主程序?两组(着色器程序)着色器各自做自己的事情。

因为我不想明显地把它们放在 main.cpp 中(我正在做这种 OOP 风格)我会有两个 classes 继承自一个抽象的 GLAbbrev class(不要问我为什么这么称呼它;我想不出另一个名字!)其中将有两个已实现的 classes,因此:

`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`

因此,每个 class 处理一个着色器程序并相应地渲染结果。然后,在我的应用程序 class 中,我有一个指向 GLAbbrev 的指针向量,它管理 GLAbbrev。 (这不是个好主意;它真的应该由另一个 class 管理,但请耐心等待)

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

然后我简单地将所有函数作为一个调用,例如:

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

以及更新等。关于 glViewport 和拥有相机,我认为在抽象 class 中实现这些是合乎逻辑的。因此,调整大小函数将在 GLAbbrev 中,并且 class 也将有一个指向 Camera 的受保护指针,因为我有多个版本的相机。因此,我可以在应用程序中声明 Camera 并将其传递给 glAbbrevs,例如:

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

这可能会有问题,但暂时可以解决。如您所见,这是我准备采用的一种设计方法,但不确定它是否是最有效的方法。其他图形程序员的意见是什么?着色器程序是否应该分布在各自的 classes 中来做一件事?或者应该有一个着色器程序,所有代码都在它们的顶点、片段和几何着色器中;或者应该只有一个 class 继承自 GLAbbrev,但具有呈现所需输出所需的所有着色器程序?

今天制作精良的游戏都使用多个着色器,每个着色器都有不同的目的和不同的实现。例如,您可以为天空盒使用一个着色器,为树木使用另一个着色器,这很好用,但它仍然不是最好的实现,在我们到达那里之前,我想澄清一下,使用这种方法有两种类型的着色器,一种绘制到屏幕(灯光等)和绘制到帧缓冲区(阴影贴图等)的。

现在的方法是将阴影贴图和其他贴图渲染到帧缓冲区(基本上是内存中可编辑的纹理。)渲染完所有贴图后,有一个大着色器可以处理光照和添加阴影贴图,所有这些,但问题是着色器变得非常大,因此开发人员创建自定义着色器读取器。他们从文件中读取着色器,并在读取时找到自己的自定义关键字(例如,他们可以使用诸如 'import' 或 '#include' 之类的关键字,因为它是预处理器。)和用 action 替换它们,因此可能有一个关键字“#include”,然后是一个文件,所以它将是“#include res/shaders/lights.fs”,然后他们将用 [ 中的着色器代码替换该行=30=],当然他们也会检查 lights.fs 中的关键字,所以在一天结束时,他们最终得到一个由许多小着色器组成的大型着色器,并使用该大型着色器处理所有帧缓冲区之前制作的(这是通过简单地将它们作为 sampler2D 传递来完成的。)以及所有的灯光和奇特的效果。

我建议查看 youtube 上的 thebennybox,尤其是他的 3D 游戏引擎系列,这将对您有很大帮助,虽然前半部分在 java 他后来切换到 c++,所有的 c++ 代码都可以在 github.

上找到

播放列表:https://www.youtube.com/playlist?list=PLEETnX-uPtBXP_B2yupUKlflXBznWIlL5

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

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

PS。更好的调用render的方法是给render函数一个shader参数,先绑定再渲染,最后解绑定。例如

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

如果你是守旧派,你可能会用一个又大又笨重的着色器来做所有事情,是的。 然而,许多人更喜欢所谓的 deferred 渲染。这是一种策略,您可以通过该策略将最终帧划分为多个层,然后以特定方式组合这些层。 Read more about it here.

我个人更喜欢延迟的方式,因为它提供了更简洁的方法、更少的意大利面条式代码和简洁的设计。

希望对您有所帮助!