opengllegacyblendingstencil-buffer

Legacy OpenGL - Blending textures on water reflections produced with stencil test


I'm forced to use legacy OpenGL and no shaders for an academic project (yes..). I'm rendering a 3D world with mountains and water. My goal (and problem) is to draw the skydome as reflection on the water. The skydome itself has 2 textures (night and day) which are drawn blended based on an alpha value as follow:

void Renderer::drawSkydome()
{
   // Enable blending
   glEnable(GL_BLEND);

    // Disable depth testing
    glDisable(GL_DEPTH_TEST);
    glCullFace(GL_FRONT);
    
    // Bind the vertex array object for the skydome
    glBindVertexArray(instance->objects[SKYDOME].vao);
    
    glEnableClientState(GL_VERTEX_ARRAY);
    glEnableClientState(GL_TEXTURE_COORD_ARRAY);
    
    // Day texture
    glBindTexture(GL_TEXTURE_2D, instance->objects[SKYDOME].texture);
    glDrawElements(GL_TRIANGLES, instance->objects[SKYDOME].indices.size(), GL_UNSIGNED_INT, 0);
    
    glColor4f(1.0, 1.0, 1.0, alpha);
    
    // Night texture
    glBindTexture(GL_TEXTURE_2D, instance->objects[SKYDOME].blend_texture);
    glDrawElements(GL_TRIANGLES, instance->objects[SKYDOME].indices.size(), GL_UNSIGNED_INT, 0);
    
    glDisableClientState(GL_VERTEX_ARRAY);
    glDisableClientState(GL_TEXTURE_COORD_ARRAY);
    
    glColor4f(1.0, 1.0, 1.0, 1.0);
    
    // Unbind the vertex array object and texture
    glBindVertexArray(0);
    glBindTexture(GL_TEXTURE_2D, 0);
    
    // Re-enable depth testing
    glCullFace(GL_BACK);
    glEnable(GL_DEPTH_TEST);
    glDisable(GL_BLEND);

}

This just works fine. I then managed to draw objects reflections on water by using stencil test; specifically:

void Renderer::drawWater()
{
    glEnable(GL_BLEND);

    // Bind the water VAO
    glBindVertexArray(instance->objects[WATER].vao);
    
    // Enable two vertex arrays: co-ordinates and color.
    glEnableClientState(GL_VERTEX_ARRAY);
    glEnableClientState(GL_TEXTURE_COORD_ARRAY);
    glEnableClientState(GL_NORMAL_ARRAY);
    
    glEnable(GL_STENCIL_TEST); // Enable stencil testing
    glClearStencil(0); // Set clearing value for stencil buffer.
    
    glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
    glDepthMask(GL_FALSE);
    glDisable(GL_DEPTH_TEST);
    
    glStencilFunc(GL_ALWAYS, 1, 1);
    glStencilOp(GL_REPLACE, GL_REPLACE, GL_REPLACE); // In all cases replace the stencil tag
    
    glEnable(GL_PRIMITIVE_RESTART);                                                       
    glDrawElements(GL_TRIANGLE_STRIP, instance->objects[WATER].indices.size(), GL_UNSIGNED_INT, 0);
    glDisable(GL_PRIMITIVE_RESTART);
    
    // Enable writing of the frame and depth buffers - actually drawing now begins.
    glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
    
    glStencilFunc(GL_EQUAL, 1, 1);
    glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); // The stencil buffer itself is not updated.    
    
    glPushMatrix();
    
        glScalef(1.0, -1.0, 1.0);
        // Bind the vertex array object for the skydome
        glBindVertexArray(instance->objects[SKYDOME].vao);
        
        glEnableClientState(GL_VERTEX_ARRAY);
        glEnableClientState(GL_TEXTURE_COORD_ARRAY);
        
        glBindTexture(GL_TEXTURE_2D, instance->objects[SKYDOME].texture);
        glDrawElements(GL_TRIANGLES, instance->objects[SKYDOME].indices.size(), GL_UNSIGNED_INT, 0);
    
        glColor4f(1.0, 1.0, 1.0, alpha); 
    
        glBindTexture(GL_TEXTURE_2D, instance->objects[SKYDOME].blend_texture);
        glDrawElements(GL_TRIANGLES, instance->objects[SKYDOME].indices.size(), GL_UNSIGNED_INT, 0);
    
        glDisableClientState(GL_VERTEX_ARRAY);
        glDisableClie**ntState(GL_TEXTURE_COORD_ARRAY);
        
        // Unbind the vertex array object and texture
        glBindVertexArray(0);
        glBindTexture(GL_TEXTURE_2D, 0);
    glPopMatrix();

    glDepthMask(GL_TRUE);
    glEnable(GL_DEPTH_TEST);
    glDisable(GL_STENCIL_TEST); // Disable the stencil test
    
    // Bind the water texture
    glBindTexture(GL_TEXTURE_2D, instance->objects[WATER].texture);
    // Bind the water VAO
    glBindVertexArray(instance->objects[WATER].vao);
    
    glEnable(GL_PRIMITIVE_RESTART);                                                                 
    glDrawElements(GL_TRIANGLE_STRIP, instance->objects[WATER].indices.size(), GL_UNSIGNED_INT, 0);
    glDisable(GL_PRIMITIVE_RESTART);
    
    glDisableClientState(GL_VERTEX_ARRAY);
    glDisableClientState(GL_TEXTURE_COORD_ARRAY);
    glDisableClientState(GL_NORMAL_ARRAY);
    
    // Unbind the vertex array object and texture
    glBindVertexArray(0);
    glBindTexture(GL_TEXTURE_2D, 0);
    glDisable(GL_BLEND);
}

The problem is this, the same piece of code (the one in between push and pop) for rendering the skydome which produces fine blending on the actual skydome, does not work when drawing skydome reflection on the water; specifically it only renders the night texture, no matter the value of Alpha. In other words I can't manage to draw blended day/night textures as reflection on the water (water texture itself btw has 0.5 alpha, so I'm able to see how the water texture blends with the skydome). Is it due to some strange behaviour with the stencil test I'm not aware of?

Thank you in advance for the help.


Solution

  • Apparently the solution was disabling GL_LIGHTING while rendering the reflected skydome as follows:

        glPushMatrix();
            glDisable(GL_LIGHTING); // <-----------------------------
            glScalef(1.0, -1.0, 1.0);
            // Bind the vertex array object for the skydome
            glBindVertexArray(instance->objects[SKYDOME].vao);
            
            glEnableClientState(GL_VERTEX_ARRAY);
            glEnableClientState(GL_TEXTURE_COORD_ARRAY);
    
            glBindTexture(GL_TEXTURE_2D, instance->objects[SKYDOME].texture);
            glDrawElements(GL_TRIANGLES, instance->objects[SKYDOME].indices.size(), GL_UNSIGNED_INT, 0);
            glColor4f(1.0, 1.0, 1.0, alpha); 
            glBindTexture(GL_TEXTURE_2D, instance->objects[SKYDOME].blend_texture);
            glDrawElements(GL_TRIANGLES, instance->objects[SKYDOME].indices.size(), GL_UNSIGNED_INT, 0);
    
            glDisableClientState(GL_VERTEX_ARRAY);
            glDisableClientState(GL_TEXTURE_COORD_ARRAY);
            
            // Unbind the vertex array object and texture
            glBindVertexArray(0);
            glBindTexture(GL_TEXTURE_2D, 0);
            glEnable(GL_LIGHTING);
        glPopMatrix();
    

    Not sure how lighting was interfering with the blending, but adding that simple disable call solved my initial answer!. Feel free to provide more accurate explanations.