5

在我的游戏中,我想为每种情况创建单独的 GLSL 着色器。例如,如果我有 3 个模型charactershiny sword并且blury ghost我想设置renderShader, animationShaderandlightingShader到 the character, then renderShader, lightingShaderand specularShaderto shiny sword, 最后我想设置renderShader, lightingShaderandblurShaderblury ghost.

应该将顶点的renderShader位置乘以投影、世界和其他矩阵,它的片段着色器应该简单地将纹理设置为模型。

animationShader应该通过给定的骨骼变换来变换顶点。

lightingShader应该做光照,specularLighting应该做镜面光照。

blurShader应该做模糊效果。

现在首先我如何在不同的着色器上进行多个顶点变换?因为animationShader应该计算顶点的动画位置,然后renderShader应该得到该位置并通过一些矩阵对其进行转换。

其次,如何更改不同着色器上片段的颜色?

基本思想是我希望能够为每个 sutuations/效果使用不同的着色器,但我不知道如何实现它。

我需要知道我应该如何在 opengl 中使用这些着色器,以及我应该如何使用 GLSL,以便所有着色器能够相互完成,并且着色器不会关心是否使用了另一个着色器。

4

3 回答 3

4

您所要求的绝对不是微不足道的,对于您描述的相对有限数量的“着色器”类型来说,这可能是极端的矫枉过正。

做你想做的需要开发你自己的着色语言。它可能是#defineGLSL 的高度 d 版本,但您编写的着色器不会是纯 GLSL。它们将具有专门的钩子,并以可以预期代码流入其他代码的方式编写。

您需要有自己的方式来指定语言的输入和输出。当您想将着色器连接在一起时,您必须说明谁的输出到哪个着色器的输入。一些输入可能来自实际的着色器阶段输入,而另一些则来自其他着色器。由着色器写入的一些输出将是实际的着色器阶段输出,而其他输出将馈送到其他着色器。

因此,需要来自另一个着色器的输入的着色器必须在另一个着色器之后执行。您的系统必须计算出依赖关系图。

一旦确定了特定着色器序列的所有输入和输出,就必须获取所有这些着色器文本文件并将它们编译为 GLSL,视情况而定。显然,这是一个不平凡的过程。

您的着色器语言可能如下所示:

INPUT vec4 modelSpacePosition;
OUTPUT vec4 clipSpacePosition;

uniform mat4 modelToClipMatrix;

void main()
{
  clipSpacePosition = modelToClipMatrix * modelSpacePosition;
}

您的“编译器”将需要对此进行文本转换,将引用modelSpacePosition转换为实际的顶点着色器输入或由另一个着色器写入的变量,视情况而定。同样,如果clipSpacePosition要写入 to gl_Position,则需要将 to 的所有用途都clipSpacePosition转换gl_Position。此外,您将需要删除显式输出声明。

简而言之,这将是很多工作。

如果您打算这样做,我强烈建议您避免尝试合并顶点着色器和片段着色器的概念。保持此着色器系统在明确定义的着色器阶段内工作。因此,您的“lightingShader”需要是顶点着色器或片段着色器。如果它是一个片段着色器,那么输入它的顶点着色器中的一个着色器将需要以某种方式提供法线,或者您需要片段着色器组件通过某种机制来计算法线。

于 2013-04-18T11:04:57.717 回答
3

对于着色器阶段的每个组合,您都必须创建一个单独的着色器程序。为了节省工作和冗余,您将使用一些缓存结构为每个请求的组合只创建一次程序,并在请求时重复使用它。

与着色器阶段类似。然而,着色器阶段不能从多个编译单元链接(然而,这是 OpenGL 开发中的持续努力,OpenGL-4 的可分离着色器是那里的垫脚石)。但是您可以从多个来源编译着色器。因此,您可以将每种所需效果的函数编写到单独的源中,然后在编译时将它们组合起来。并再次使用缓存结构将源模块组合映射到着色器对象。

因评论而更新

假设您想要一些模块化。为此,我们可以利用 glShaderSource 接受多个源字符串的事实,它只是连接起来。您编写了许多着色器模块。一个做每个顶点的照明计算

uniform vec3 light_positions[N_LIGHT_SOURCES];
out vec3 light_directions[N_LIGHT_SOURCES];
out vec3 light_halfdirections[N_LIGHT_SOURCES];

void illum_calculation()
{
    for(int i = 0; i < N_LIGHT_SOURCES; i++) {
        light_directions[i] = ...;
        light_halfdirections[i] = ...;
    }
}

你把它放进去illum_calculation.vs.glslmod(文件名和扩展名是任意的)。接下来你有一个做骨骼动画的小模块

uniform vec4 armature_pose[N_ARMATURE_BONES];
uniform vec3 armature_bones[N_ARMATURE_BONES];

in vec3 vertex_position;

void skeletal_animation()
{
    /* ...*/
}

把这个放进illum_skeletal_anim.vs.glslmod. 然后你有一些常见的标题

#version 330
uniform ...;
in ...;

和一些包含主函数的通用尾部,它调用所有不同的阶段

void main() {
    skeletal_animation();
    illum_calculation();
}

等等。现在您可以将所有这些模块以正确的顺序加载到单个着色器阶段。您可以对所有着色器阶段执行相同的操作。片段着色器很特别,因为它可以同时写入多个帧缓冲区目标(在足够大的 OpenGL 版本中)。从技术上讲,您可以在各个阶段之间传递很多变化。因此,您可以为每个帧缓冲区目标在着色器阶段之间传递一组自己的变量。然而,几何形状和转换后的顶点位置对所有这些都是通用的。

于 2013-04-18T10:21:09.157 回答
1

您必须为要渲染的每个模型提供不同的着色器程序。您可以使用 glUseProgram 函数在不同的着色器组合之间切换。所以在渲染你的角色或闪亮的剑或任何你必须初始化适当的着色器属性和制服之前。

所以这只是游戏代码设计的问题,因为您需要为着色器提供所有统一属性,例如光照信息、纹理样本,并且您必须启用着色器的所有必要顶点属性才能分配位置、颜色等。

这些属性在着色器之间可能不同,并且您的客户端模型可以具有不同类型的顶点属性结构。

这意味着您的代码模型直接影响分配的着色器并依赖于它。

如果您想在不同的着色器程序之间共享通用代码,例如,IlluminateDiffuse,您必须外包此函数并将其提供给您的着色器,只需将表示该函数的字符串文字插入到您的着色器代码中,这只不过是一个字符串文字。因此,您可以通过对着色器代码的字符串操作来实现一种模块化或包含行为。

在任何情况下,着色器编译器都会告诉你出了什么问题。

此致

于 2013-11-23T17:03:34.347 回答