【问题标题】:Multiple subroutine types defined in the same fragment shader does not work correctly using GLSL Shaders使用 GLSL 着色器在同一片段着色器中定义的多个子例程类型无法正常工作
【发布时间】:2013-08-28 21:46:07
【问题描述】:

我正在开发一个使用 GLSL 着色器的程序。我用 2 种不同的方法编写了 2 种不同的方法来计算 ADS(环境 + 漫反射 + 高光)着色。为了正确完成这项工作,我使用子例程来使用一种或另一种方法来计算 ADS 着色。

这是片段着色器代码:

subroutine vec3 LightShadingEffectType(int idx, vec3 normal, vec3 lightDir);
subroutine uniform LightShadingEffectType LightShadingEffect;

subroutine (LightShadingEffectType)
vec3 Basic_ADS_Shading(int idx, vec3 normal, vec3 lightDir)
{
    vec3 reflectDir = reflect(-lightDir, normal);
    vec3 viewDir = normalize(-Position.xyz);

    vec3 Ambient = LightInfos[idx].La * MaterialInfos.Ka;
    vec3 Diffuse = LightInfos[idx].Ld * MaterialInfos.Kd * max(dot(lightDir, normal), 0.0f);
    vec3 Specular = LightInfos[idx].Ls * MaterialInfos.Ks * pow(max(dot(reflectDir, viewDir), 0.0f), MaterialInfos.Shininess);
    vec3 Emissive = MaterialInfos.Ke;

    return (Ambient + Diffuse + Specular + Emissive);
}

subroutine (LightShadingEffectType)
vec3 Phong_ADS_Shading(int idx, vec3 normal, vec3 lightDir)
{
    vec3 v = normalize(vec3(-Position));
    vec3 h = normalize(v + lightDir);

    vec3 Ambient = LightInfos[idx].La * MaterialInfos.Ka;
    vec3 Diffuse = LightInfos[idx].Ld * MaterialInfos.Kd * max(dot(lightDir, normal), 0.0f);
    vec3 Specular = LightInfos[idx].Ls * MaterialInfos.Ks * pow(max(dot(h, normal), 0.0f), MaterialInfos.Shininess);
    vec3 Emissive = MaterialInfos.Ke;

    return (Ambient + Diffuse + Specular + Emissive);
}

还有 C++ 代码:

type::uint32 basic_ads_idx = glGetSubroutineIndex(program->getHandle(), GL_FRAGMENT_SHADER, "Basic_ADS_Shading");
type::uint32 phong_ads_idx = glGetSubroutineIndex(program->getHandle(), GL_FRAGMENT_SHADER, "Phong_ADS_Shading");

glUniformSubroutinesuiv(GL_FRAGMENT_SHADER, 1, &phong_ads_idx);

到这里显示是正确的。在这种情况下,我选择执行第二个子程序定义(Phong_ADS_Shading 调用)。

但是我想在我的程序中声明另一个子程序类型来管理纹理(签名不一样)。这是片段着色器代码的另一部分(在同一个着色器中):

subroutine vec4 TexturedShadingType();
subroutine uniform TexturedShadingType TexturedShading;

subroutine (TexturedShadingType)
vec4 Textured_Shading(void)
{
    vec4 TexColor = texture(Tex1, TexCoords);
    return (vec4(getLightIntensity(), 1.0f) * TexColor);
}

subroutine (TexturedShadingType)
vec4 Untextured_Shading(void)
{
    return (vec4(getLightIntensity(), 1.0f));
}

所以,最后,我的 C++ 代码如下:

type::uint32 basic_ads_idx = glGetSubroutineIndex(program->getHandle(), GL_FRAGMENT_SHADER, "Basic_ADS_Shading");
type::uint32 phong_ads_idx = glGetSubroutineIndex(program->getHandle(), GL_FRAGMENT_SHADER, "Phong_ADS_Shading");

glUniformSubroutinesuiv(GL_FRAGMENT_SHADER, 1, &phong_ads_idx);

type::uint32 textured_idx = glGetSubroutineIndex(program->getHandle(), GL_FRAGMENT_SHADER, "Textured_Shading");
type::uint32 untextured_idx = glGetSubroutineIndex(program->getHandle(), GL_FRAGMENT_SHADER, "Untextured_Shading");

glUniformSubroutinesuiv(GL_FRAGMENT_SHADER, 1, &untextured_idx);

这是每个子程序索引的值:

std::cout << phong_idx << ", " << blinn_phong_idx << ", " << textured_idx << ", " << untextured_idx << std::endl;

-> 1, 0, 4294967295, 4294967295

前两个值似乎是正确的,但其他两个值是正确的。

现在是整个片段着色器代码,以便更好地理解我的问题:

#version 400

in vec3 Position;
in vec3 Normal;
in vec2 TexCoords;

layout (location = 0) out vec4 FragColor;

uniform sampler2D Tex1;
uniform int lightCount;

struct PointLight
{
    vec4 Position;
    vec3 La, Ld, Ls;
    float Kc, Kl, Kq;
    vec3 direction;
    float exponent;
    float cutoff;
    int type;
};

struct Material
{
    vec3 Ka, Kd, Ks, Ke;
    float Shininess;
};

uniform PointLight LightInfos[10];
uniform Material MaterialInfos;

subroutine vec3 LightShadingEffectType(int idx, vec3 normal, vec3 lightDir);
subroutine uniform LightShadingEffectType LightShadingEffect;

subroutine (LightShadingEffectType)
vec3 Phong_Shading(int idx, vec3 normal, vec3 lightDir)
{
    vec3 reflectDir = reflect(-lightDir, normal);
    vec3 viewDir = normalize(-Position.xyz);

    vec3 Ambient = LightInfos[idx].La * MaterialInfos.Ka;
    vec3 Diffuse = LightInfos[idx].Ld * MaterialInfos.Kd * max(dot(lightDir, normal), 0.0f);
    vec3 Specular = LightInfos[idx].Ls * MaterialInfos.Ks * pow(max(dot(reflectDir, viewDir), 0.0f), MaterialInfos.Shininess);
    vec3 Emissive = MaterialInfos.Ke;

    return (Ambient + Diffuse + Specular + Emissive);
}

subroutine (LightShadingEffectType)
vec3 Blinn_Phong_Shading(int idx, vec3 normal, vec3 lightDir)
{
    vec3 v = normalize(vec3(-Position));
    vec3 h = normalize(v + lightDir);

    vec3 Ambient = LightInfos[idx].La * MaterialInfos.Ka;
    vec3 Diffuse = LightInfos[idx].Ld * MaterialInfos.Kd * max(dot(lightDir, normal), 0.0f);
    vec3 Specular = LightInfos[idx].Ls * MaterialInfos.Ks * pow(max(dot(h, normal), 0.0f), MaterialInfos.Shininess);
    vec3 Emissive = MaterialInfos.Ke;

    return (Ambient + Diffuse + Specular + Emissive);
}

float getLightAttenuation(vec3 lightDir, PointLight light)
{
    float lightAtt = 0.0f;
    float dist = 0.0f;

    dist = length(lightDir);
    lightAtt = 1.0f / (light.Kc + (light.Kl * dist) + (light.Kq * pow(dist, 2)));
    return (lightAtt);
}

float getSpotFactor(vec3 lightDir, vec3 spotDir, PointLight light)
{
    return (pow(dot(-lightDir, spotDir), light.exponent));
}

vec3 Spot_ADS_Shading(float lightAtt, vec3 tnorm, vec3 lightDirNorm, int idx)
{
    vec3 LightIntensity = vec3(0.0f);
    vec3 spotDirNorm = normalize(LightInfos[idx].direction);
    float angle = acos(dot(-lightDirNorm, spotDirNorm));
    float cutoff = radians(clamp(LightInfos[idx].cutoff, 0.0f, 90.0f));

    if (angle < cutoff)
    {
        float spotFactor = getSpotFactor(lightDirNorm, spotDirNorm, LightInfos[idx]);
        LightIntensity = lightAtt * spotFactor * LightShadingEffect(idx, -tnorm, lightDirNorm);
    }
    else
    {
        LightIntensity = LightShadingEffect(idx, -tnorm, lightDirNorm) * MaterialInfos.Ka;
    }
    return (LightIntensity);
}

vec3 Point_ADS_Shading(float lightAtt, vec3 tnorm, vec3 lightDirNorm, int idx)
{
    return (lightAtt * LightShadingEffect(idx, tnorm, lightDirNorm));
}

vec3 getLightIntensity(void)
{
    vec3 LightIntensity = vec3(0.0f);

    for (int idx = 0; idx < lightCount; idx++)
    {
        vec3 tnorm = (gl_FrontFacing ? -normalize(Normal) : normalize(Normal));
        vec3 lightDir = vec3(LightInfos[idx].Position) - Position;
        vec3 lightDirNorm = normalize(lightDir);
        float lightAtt = getLightAttenuation(lightDir, LightInfos[idx]);

        if (LightInfos[idx].type == 1)
        {
            LightIntensity += Spot_ADS_Shading(lightAtt, tnorm, lightDirNorm, idx);
        }
        else
        {
            LightIntensity += Point_ADS_Shading(lightAtt, -tnorm, lightDirNorm, idx);
        }
    }
    return (LightIntensity);
}

subroutine vec4 TexturedShadingType();
subroutine uniform TexturedShadingType TexturedShading;

subroutine (TexturedShadingType)
vec4 Textured_Shading(void)
{
    vec4 TexColor = texture(Tex1, TexCoords);
    return (vec4(getLightIntensity(), 1.0f) * TexColor);
}

subroutine (TexturedShadingType)
vec4 Untextured_Shading(void)
{
    return (vec4(getLightIntensity(), 1.0f));
}

void main(void)
{
    FragColor = TexturedShading();
}

问题是我的几何图形呈现为黑色。我认为这两个统一子程序之间存在冲突。我迷路了。有谁可以帮助我吗?

【问题讨论】:

    标签: opengl glsl shader fragment-shader


    【解决方案1】:

    glUniformSubroutines 为着色器阶段设置所有子例程,而不仅仅是其中一个。

    请看,当 OpenGL 链接您的程序时,它会获取所有子例程统一体并从中构建一个数组。每个制服都有这个数组的索引。如果你想知道数组中某个特定子例程统一的索引是什么,你需要调用glGetSubroutineIndex。或者,假设您有 4.3/ARB_explicit_uniform_locations(诚然 AMD 的速度相当慢),您可以直接使用 the layout(location = #) layout qualifier 进行设置。这样,您就不必查询它了。

    一旦您知道每个子例程统一引用的索引,您就可以通过一次调用将阶段的所有子例程统一设置为glUniformSubroutines。您构建了一个短数组,其中数组中的每个索引都包含您要使用的子例程函数的索引。


    但我想在四个子程序中每次只选择两个子程序。

    您可能有 4 个子例程,但您只有 两个 子例程 uniform 变量。这些制服代表用户在要调用的着色器中设置特定函数。

    此外,两种制服都使用不同的类型,因此它们不能从 4 个子例程中进行选择。每个子程序统一只能从该特定类型使用的特定子程序中进行选择。这是在您使用subroutine(SubroutineType) 声明子例程函数时定义的。每种类型都有自己的一组可以使用的函数。因此,每个制服只能从为该制服声明的子例程类型中设置的那些特定函数中进行选择。

    所以你不能在 4 个子程序中进行选择;每个制服只能在您为每个子程序类型设置的功能中进行选择。每个制服只能在您声明子例程类型的两个函数之间进行选择。

    如果我只有一次 glUniformSubroutinesuiv 调用,我该如何选择它们?

    通过传递一个数组。有一个原因为什么第三个参数是一个指针,为什么第二个参数是数组中的条目数。

    您的片段着色器有两个子例程统一值。因此,数组中有 2 个元素,每个元素代表特定子程序制服的子程序索引。您可以通过创建一个数组、在其中设置两个子例程索引并将该数组传递给函数来选择它们。

    我认为这两个统一子程序之间存在冲突。

    不,问题(除了前面提到的数组问题)是您没有使用其他子例程。虽然子常规制服在大多数方面都与常规制服不同,但它们在这一点上就像常规制服。如果您不使用它们,驱动程序可以优化它们(以及它们所依赖的任何东西)。

    您的片段着色器可能不会在任何地方使用TexturedShading。而且由于没有其他子例程统一声明 TexturedShadingType 统一,编译器意识到这两个函数从不用作子例程。所以它优化了它们。因此,您为它们获得的索引是GL_INVALID_INDEX

    【讨论】:

    • 感谢您的完整回答。但我想在四个子程序中每次只选择两个子程序。如果我只有一次 glUniformSubroutinesuiv 调用,我该如何选择它们?另外,调用 glUniformSubroutinesuiv(GL_FRAGMENT_SHADER, 1, &blinn_phong_idx) 在我的着色器中选择了正确的子例程(所以这种调用可以在整个索引数组中选择一个索引,对吧?)。只是因为我添加了两个附加子程序。如您所见,我已使用每个子例程索引值的日志更新了我的帖子。你怎么看?提前致谢。
    • 非常感谢您提供这个非常完整的答案,您真好。对不起,我说错了。我想说每种类型中的 1 个子程序。现在,当您在更新的最后一部分中说“您的片段着色器可能不使用 TexturedShading”时,事实并非如此。我使用它(就像我使用'LightShadingEffect')。我更新了我的帖子,但这次我添加了片段着色器代码的全部内容。但也许问题来自我在“父”子程序中调用子程序(间接)。有可能做到这一点吗?或者子程序调用必须分开?再次感谢。
    猜你喜欢
    • 2013-07-30
    • 2016-05-08
    • 2017-04-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-05
    • 2014-07-26
    • 2021-04-21
    相关资源
    最近更新 更多