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

Multiple shader programs in main program

本文关键字:程序 主程序      更新时间:2023-10-16

我在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 风格(我会有两个继承自抽象 GLAbbrev 类的类(不要问我为什么这么称呼它;我想不出另一个名字了!其中将有两个实现的类,因此:

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

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

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

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

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

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

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

这可能是有问题的,但它现在会凑合着做。如您所见,这是我准备采用的一种设计方法,但不确定它是否是最有效的。其他图形程序员的意见是什么?着色器程序是否应该分布在各自的类中以执行一件事?或者应该有一个着色器程序,其顶点、碎片和几何着色器中包含所有代码;还是应该只有一个从 GLAbbrev 继承的类,但具有渲染所需输出所需的所有着色器程序?

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

现在的完成方式是将阴影贴图和其他贴图

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

我建议在youtube上查看thebennybox,尤其是他的3D游戏引擎系列,它会对你有很大帮助,尽管前半部分是java,他后来切换到C ++,所有的C ++代码都可以在github上找到。

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

本尼盒:https://www.youtube.com/user/thebennybox/featured

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

附言。调用 render 的更好方法是为呈现函数提供一个着色器参数,该参数被绑定,然后呈现,最后取消绑定。例如。

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

如果你是老派的,你可以用一个笨重的大着色器做所有事情,是的。然而,许多人更喜欢所谓的deferred渲染。这是一种策略,您可以通过该策略将最终帧划分为多个图层,然后以特定方式组合这些图层。在此处阅读更多相关信息。

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

希望对您有所帮助!