1

TL/DR: How do you specify a (default) texture unit for unused samplers in a sampler array?

I have the following scene in my OpenGL 4.1 application:

scene

As you can see, there are 2 lights. Each light has its own shadow map. So my fragment shader looks like this:

#define MAX_LIGHTS 16

uniform samplerCube uShadowMaps[MAX_LIGHTS];

// sample the shadow map of the light
float Shadow(int light) {
    vec3 direction = Position - uLightPositions[light];
    float sampledDepth = 1.0;

    switch (light) {
        case 0: sampledDepth = texture(uShadowMaps[0], direction).r; break;
        case 1: sampledDepth = texture(uShadowMaps[1], direction).r; break;
    }

    // ... the rest of the calculations ...
}

This code works because there are exactly 2 lights and each binds it's own shadow map. The code every light runs looks somewhat like this:

// make the name of the uniform
std::stringstream shadowMapName;
shadowMapName << "uShadowMaps[" << index << "]";

// bind the shadow map texture and bind the uniform
glActiveTexture(GL_TEXTURE3 + index);  // index is the light number, 3 because there are 3 other textures already bound
glBindTexture(GL_TEXTURE_CUBE_MAP, shadowMapTexture);
auto position = glGetUniformLocation(shaderProgram, shadowMapName.str().c_str());
glUniform1i(position, index + 3);

However, I want to have multiple lights in the scene eventually, so I want to write my shaders so they work for more lights. I added the next switch case in the shader like this:

// sample the shadow map for the light
float Shadow(int light) {
    // ...

    switch (light) {
        case 0: sampledDepth = texture(uShadowMaps[0], direction).r; break;
        case 1: sampledDepth = texture(uShadowMaps[1], direction).r; break;
        case 2: sampledDepth = texture(uShadowMaps[2], direction).r; break;
    }

    // ...
}

This, unfortunately, results in a black window.

black window

I assume this is because there's nothing bound to mShaderMaps[2]. And sure enough, if I add another light to the scene, it renders again.

So my question is: How do you specify a (default) texture unit for unused samplers?


Note: why switch? This does not work:

// sample the shadow map for the light
float Shadow(int light) {
    // ...

    sampledDepth = texture(uShadowMaps[light], direction).r;

    // ...
}

Because light is not a dynamically uniform expression. See GLSL, Array of textures of differing size.


EDIT

The Shadow function is called like this:

// calculate diffuse color for the fragment
for (auto i = 0; i < min(uLightCount, MAX_LIGHTS); ++i) {
    // ... calculate diffuse for the light

    diffuseColor += diffuse * Shadow(i);

}

// ... some more stuff ...

// calculate specular color for the fragment
for (auto i = 0; i < min(uLightCount, MAX_LIGHTS); ++i) {
    // ... calculate specular for the light

    specularColor += specular * Shadow(i);

}
Community
  • 1
  • 1
martindzejky
  • 389
  • 1
  • 3
  • 15
  • Couldn't you just pass a uniform telling how many lights there are and skip not-existing ones? – BDL Nov 03 '16 at 23:22
  • I do that. The `Shadow` function is called in a for loop for every light in the scene like this: `for (int i = 0; i < min(uLightCount, MAX_LIGHTS); ++i) { // ... calculate color and call the Shadow }` where `uLightCount` is a uniform equal to the number of lights. – martindzejky Nov 03 '16 at 23:28
  • For some reason, it doesn't matter that the switch `case 2:` is never called. As soon as it's in the code, I get black window. – martindzejky Nov 03 '16 at 23:31
  • 1
    @chuckeles: "*Because light is not a dynamically uniform expression.*" Is that really true? Is the value you're passing selected based on a shader stage input or a texture access or something? Just because the `Shadow` function doesn't declare it to be `const` doesn't mean it isn't dynamically uniform. Dynamically uniform is about the value the expression evaluates to. – Nicol Bolas Nov 04 '16 at 01:08
  • See the updated question. I think it should be dynamically uniform expression because it's in the `for` loop, which uses dynamically uniform variables (upper limit and the offset). But this is the only way I got it working. – martindzejky Nov 04 '16 at 07:42

1 Answers1

0

Well, it turns out it is really easy. I just create an empty shadow map and bind it to every shadow map in the array, each to its own texture unit. The code looks like this:

auto empty = ShadowMap::CreateEmpty(); // make an empty shadow map

for (auto i = 0; i < 12; ++i) { // 12 is the shadow map array size
    std::stringstream shadowMapName;
    shadowMapName << "uShadowMaps[" << i << "]";

    glActiveTexture(GL_TEXTURE3 + i);  // bind to a separate texture unit
    glBindTexture(GL_TEXTURE_CUBE_MAP, empty->GetId());
    auto position = glGetUniformLocation(shaderProgram, shadowMapName.str().c_str());
    glUniform1i(position, i + 3);
}

The reason I couldn't get it working before was that I was binding a simple texture to unused shadow maps. But the bound shadow maps use cube maps, so I was binding a different texture types to the array, which does not work.

martindzejky
  • 389
  • 1
  • 3
  • 15