The first object I render isn't writing to the stencil buffer

I’ve been trying to learn what a stencil buffer is in OpenGL. To do this, I’ve used the tutorial at https://learnopengl.com/#!Advanced-OpenGL/Stencil-testing.
I’ve adjusted the code to fit the scene I’m using.

What I’m trying to do is outline every box in the scene.

This code works well for every object that I draw except the first one, which receives no outline.
I’m fairly certain that this is because the first object I draw isn’t writing to the stencil buffer.

Why is this?

Here is my code:


#include <iostream>

// GLEW
#define GLEW_STATIC
#include <GL/glew.h>

// GLFW
#include <GLFW/glfw3.h>

// Other Libs
#include <SOIL/SOIL.h>
// GLM Mathematics
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>

#include <glm/gtx/string_cast.hpp>

// Other includes
#include "../shader.hpp"
#include "../camera.hpp"
#include "cube.hpp"

using glm::vec3;

// Function prototypes
void keyCallback(GLFWwindow* window, int key, int scancode, int action, int mode);
void doMovement();
void mouseCallback(GLFWwindow* window, double xpos, double ypos);


// Window dimensions
const GLuint WIDTH = 800, HEIGHT = 600;

// Camera stuff
Camera cam(vec3(0.0f, 0.0f, 3.0f));

static GLfloat deltaTime = 0.0f;	// Time between current frame and last frame
static GLfloat lastFrame = 0.0f;  	// Time of last frame

static GLfloat lastMouseX = 400, lastMouseY = 300;

bool keys[1024];

static vec3 lightPos = {-1.2f, 1.0f, 0.5f};

// The MAIN function, from here we start the application and run the game loop
int main() {
    // Init GLFW
    glfwInit();
    // Set all the required options for GLFW
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
    glfwWindowHint(GLFW_RESIZABLE, GL_FALSE);

    // Create a GLFWwindow object that we can use for GLFW's functions
    GLFWwindow* window = glfwCreateWindow(WIDTH, HEIGHT, "LearnOpenGL Lighting", nullptr, nullptr);
    glfwMakeContextCurrent(window);

    // Set the required callback functions
    glfwSetKeyCallback(window, keyCallback);

    // Set mouse input up
    glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);
    glfwSetCursorPosCallback(window, mouseCallback);
    // Set this to true so GLEW knows to use a modern approach to retrieving function pointers and extensions
    glewExperimental = GL_TRUE;
    // Initialize GLEW to setup the OpenGL Function pointers
    glewInit();

    // Define the viewport dimensions
    glViewport(0, 0, WIDTH, HEIGHT);

    // Setup OpenGL options
    glEnable(GL_DEPTH_TEST);
    glEnable(GL_STENCIL_TEST);
    glStencilFunc(GL_NOTEQUAL, 1, 0xFF);
    glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);

    // Build and compile our shader program
    Shader lightShader("vertex.glsl", "lightFragment.glsl");
    Shader boxShader("vertex.glsl", "boxFragment.glsl");
    Shader singleShader("vertex.glsl", "singleColorFragment.glsl");

    // Load texture

    GLuint texture;

    glGenTextures(1, &texture);
    glBindTexture(GL_TEXTURE_2D, texture); // All upcoming GL_TEXTURE_2D operations now have effect on our texture object
    // Set our texture parameters
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);	// Set texture wrapping to GL_REPEAT
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
    // Set texture filtering
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    // Load, create texture and generate mipmaps
    int width, height;
    unsigned char* image = SOIL_load_image("container.png", &width, &height, 0, SOIL_LOAD_RGB);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, image);
    glGenerateMipmap(GL_TEXTURE_2D);
    SOIL_free_image_data(image);
    glBindTexture(GL_TEXTURE_2D, 0); // Unbind texture when done, so we won't accidentily mess up our texture.


    GLuint specTexture;
    glGenTextures(1, &specTexture);
    glBindTexture(GL_TEXTURE_2D, specTexture); // All upcoming GL_TEXTURE_2D operations now have effect on our texture object
    // Set our texture parameters
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);	// Set texture wrapping to GL_REPEAT
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
    // Set texture filtering
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    // Load, create texture and generate mipmaps
    image = SOIL_load_image("container_specular.png", &width, &height, 0, SOIL_LOAD_RGB);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, image);
    glGenerateMipmap(GL_TEXTURE_2D);
    SOIL_free_image_data(image);
    glBindTexture(GL_TEXTURE_2D, 0); // Unbind texture when done, so we won't accidentily mess up our texture.


    // Declare the lamp
    Cube lamp;

    // Declare the other cube
    Cube box;

    glm::mat4 projection;
    projection = glm::perspective(glm::radians(45.0f), (GLfloat)WIDTH / (GLfloat)HEIGHT, 0.1f, 100.0f);

    vec3 lightColor = {1.0f, 1.0f, 1.0f};

    boxShader.use();
    boxShader.setUniform("material.diffuse",  0);
    boxShader.setUniform("material.specular", 1);

    vec3 cubePositions[] = {
        vec3( 0.0f,  0.0f,  0.0f),
        vec3( 2.0f,  5.0f, -15.0f),
        vec3(-1.5f, -2.2f, -2.5f),
        vec3(-3.8f, -2.0f, -12.3f),
        vec3( 2.4f, -0.4f, -3.5f),
        vec3(-1.7f,  3.0f, -7.5f),
        vec3( 1.3f, -2.0f, -2.5f),
        vec3( 1.5f,  2.0f, -2.5f),
        vec3( 1.5f,  0.2f, -1.5f),
        vec3(-1.3f,  1.0f, -1.5f)
    };


    // Game loop
    while (!glfwWindowShouldClose(window)) {
        // Check if any events have been activiated (key pressed, mouse moved etc.) and call corresponding response functions
        glfwPollEvents();
        doMovement();


        glEnable(GL_DEPTH_TEST);
        glStencilOp(GL_KEEP, GL_REPLACE, GL_REPLACE);
        // Render
        // Clear the colorbuffer
        glClearColor(0.1f, 0.1f, 0.1f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);

        // Use shader
        lightShader.use();

        glStencilMask(0x00); // Disable writing to the stencil buffer

        mat4 model = glm::translate(lightPos) * glm::scale(vec3(0.1f, 0.1f, 0.1f));
        mat4 view = cam.getViewMatrix();
        lightShader.setUniform("model", model);
        lightShader.setUniform("view", view);
        lightShader.setUniform("projection", projection);

        lightShader.setUniform("Color", lightColor);

        lamp.draw();
        lightShader.setUniform("model", glm::translate(mat4{}, vec3(0.0f, -1.5f, -3.0f)) * glm::scale(vec3(0.1f, 0.1f, 0.1f)));

        lamp.draw();


        glStencilFunc(GL_ALWAYS, 1, 0xFF); // All fragments should update the stencil buffer
        glStencilMask(0xFF); // Enable writing to the stencil buffer

        lightPos.z = sin(glfwGetTime());
        lightPos.x = cos(glfwGetTime());

        boxShader.use();

        //model = glm::rotate(model, (GLfloat)glm::radians(glfwGetTime() * 50.0f), vec3(0.5f, 1.0f, 0.0f));
        boxShader.setUniform("view", view);
        boxShader.setUniform("projection", projection);

        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, texture);

        for (GLushort i = 0; i < 10; i++) {
            model = {};
            model = glm::translate(model, cubePositions[i]);

            GLfloat angle = 20.0f * i;
            model = glm::rotate(model, angle, glm::vec3(0.0f, 0.7f, 0.2f));

            boxShader.setUniform("model", model);

            box.draw();
        }

        // Render pass 2

        glStencilFunc(GL_NOTEQUAL, 1, 0xFF);
        glStencilMask(0x00); // Disable writing to the stencil buffer
        singleShader.use();

        singleShader.setUniform("view", view);
        singleShader.setUniform("projection", projection);

        for (GLushort i = 0; i < 10; i++) {
            model = glm::translate(model, cubePositions[i]);

            model = glm::scale(model, vec3(1.1f, 1.1f, 1.1f));

            GLfloat angle = 20.0f * i;
            model = glm::rotate(model, angle, glm::vec3(0.0f, 0.7f, 0.2f));

            singleShader.setUniform("model", model);

            box.draw();
            model = {};
        }

        glStencilMask(0xFF);

        // Swap the screen buffers
        glfwSwapBuffers(window);

    }
    std::cerr << glGetError();
    // Terminate GLFW, clearing any resources allocated by GLFW.
    glfwTerminate();
    return 0;
}

// Is called whenever a key is pressed/released via GLFW
//                                           scancode            mode
void keyCallback(GLFWwindow* window, int key, int , int action, int ) {

    if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS)
        glfwSetWindowShouldClose(window, GL_TRUE);

    if(key == GLFW_KEY_I && action == GLFW_PRESS)
        std::cout << glm::to_string(cam.getViewMatrix()) << std::endl;

    if (key >= 0 && key < 1024)
    {
        if (action == GLFW_PRESS)
            keys[key] = true;
        else if (action == GLFW_RELEASE)
            keys[key] = false;
    }

}

void mouseCallback(GLFWwindow *, double mouseX, double mouseY) {
    GLfloat xOffset = mouseX - lastMouseX;
    GLfloat yOffset = lastMouseY - mouseY; // Reversed since y-coordinates range from bottom to top
    lastMouseX = mouseX;
    lastMouseY = mouseY;

    const GLfloat sensitivity = 0.003f;
    xOffset *= sensitivity;
    yOffset *= sensitivity;

    cam.look(xOffset, yOffset);
}

void doMovement() {
    GLfloat currentFrame = glfwGetTime();
    deltaTime = currentFrame - lastFrame;
    lastFrame = currentFrame;

    if(keys[GLFW_KEY_W])
        cam.move(CAM_FORWARD, deltaTime);
    if(keys[GLFW_KEY_S])
        cam.move(CAM_BACKWARD, deltaTime);
    if(keys[GLFW_KEY_A])
        cam.move(CAM_LEFT, deltaTime);
    if(keys[GLFW_KEY_D])
        cam.move(CAM_RIGHT, deltaTime);
}

You’re not really showing us the GL calls for drawing your objects here.

Looks to me like you disable stencil writes for your first object (glStencilMask(0)) and then enable them later (glStencilMask(0xFF)). You’ve also got stencil test enabled for your first object, but then you force the test to GL_ALWAYS. Finally, you’re not providing GLFW any hints as to whether and how many bits of stencil and depth you need here ().

A suggestion: If you can, disable stencil test, enable stencil writes, and just get stencil written with the right values first. Verify that they’re right. Then go back and enable stencil test and use the stencil for other operations.

I checked that too. But according to this, it seems glfw defaults to use stencil.

Sorry. I just realized how bad my question was. I had some extra unnecessary code in there. Here is the revised code with the same problem.

main.cpp:


#include <iostream>

// GLEW
#define GLEW_STATIC
#include <GL/glew.h>

// GLFW
#include <GLFW/glfw3.h>

// Other Libs
#include <SOIL/SOIL.h>
// GLM Mathematics
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>

#include <glm/gtx/string_cast.hpp>

// Other includes
#include "../shader.hpp"
#include "../camera.hpp"
#include "cube.hpp"

using glm::vec3;

// Function prototypes
void keyCallback(GLFWwindow* window, int key, int scancode, int action, int mode);
void doMovement();
void mouseCallback(GLFWwindow* window, double xpos, double ypos);


// Window dimensions
const GLuint WIDTH = 800, HEIGHT = 600;

// Camera stuff
Camera cam(vec3(0.0f, 0.0f, 3.0f));

static GLfloat deltaTime = 0.0f;	// Time between current frame and last frame
static GLfloat lastFrame = 0.0f;  	// Time of last frame

static GLfloat lastMouseX = 400, lastMouseY = 300;

bool keys[1024];

static vec3 lightPos = {-1.2f, 1.0f, 0.5f};

// The MAIN function, from here we start the application and run the game loop
int main() {
    // Init GLFW
    glfwInit();
    // Set all the required options for GLFW
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
    glfwWindowHint(GLFW_RESIZABLE, GL_FALSE);

    // Create a GLFWwindow object that we can use for GLFW's functions
    GLFWwindow* window = glfwCreateWindow(WIDTH, HEIGHT, "LearnOpenGL Lighting", nullptr, nullptr);
    glfwMakeContextCurrent(window);

    // Set the required callback functions
    glfwSetKeyCallback(window, keyCallback);

    // Set mouse input up
    glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);
    glfwSetCursorPosCallback(window, mouseCallback);
    // Set this to true so GLEW knows to use a modern approach to retrieving function pointers and extensions
    glewExperimental = GL_TRUE;
    // Initialize GLEW to setup the OpenGL Function pointers
    glewInit();

    // Define the viewport dimensions
    glViewport(0, 0, WIDTH, HEIGHT);

    // Setup OpenGL options
    glEnable(GL_DEPTH_TEST);
    glEnable(GL_STENCIL_TEST);
    glStencilFunc(GL_NOTEQUAL, 1, 0xFF);
    glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);

    // Build and compile our shader program
    Shader boxShader("vertex.glsl", "boxFragment.glsl");
    Shader singleShader("vertex.glsl", "singleColorFragment.glsl");

    // Load texture

    GLuint texture;

    glGenTextures(1, &texture);
    glBindTexture(GL_TEXTURE_2D, texture); // All upcoming GL_TEXTURE_2D operations now have effect on our texture object
    // Set our texture parameters
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);	// Set texture wrapping to GL_REPEAT
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
    // Set texture filtering
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    // Load, create texture and generate mipmaps
    int width, height;
    unsigned char* image = SOIL_load_image("container.png", &width, &height, 0, SOIL_LOAD_RGB);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, image);
    glGenerateMipmap(GL_TEXTURE_2D);
    SOIL_free_image_data(image);
    glBindTexture(GL_TEXTURE_2D, 0); // Unbind texture when done, so we won't accidentily mess up our texture.


    GLuint specTexture;
    glGenTextures(1, &specTexture);
    glBindTexture(GL_TEXTURE_2D, specTexture); // All upcoming GL_TEXTURE_2D operations now have effect on our texture object
    // Set our texture parameters
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);	// Set texture wrapping to GL_REPEAT
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
    // Set texture filtering
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    // Load, create texture and generate mipmaps
    image = SOIL_load_image("container_specular.png", &width, &height, 0, SOIL_LOAD_RGB);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, image);
    glGenerateMipmap(GL_TEXTURE_2D);
    SOIL_free_image_data(image);
    glBindTexture(GL_TEXTURE_2D, 0); // Unbind texture when done, so we won't accidentily mess up our texture.

    // Declare the other cube
    Cube box;

    glm::mat4 projection;
    projection = glm::perspective(glm::radians(45.0f), (GLfloat)WIDTH / (GLfloat)HEIGHT, 0.1f, 100.0f);


    boxShader.use();
    boxShader.setUniform("material.diffuse",  0);
    boxShader.setUniform("material.specular", 1);

    vec3 cubePositions[] = {
        vec3( 0.0f,  0.0f,  0.0f),
        vec3( 2.0f,  5.0f, -15.0f),
        vec3(-1.5f, -2.2f, -2.5f),
        vec3(-3.8f, -2.0f, -12.3f),
        vec3( 2.4f, -0.4f, -3.5f),
        vec3(-1.7f,  3.0f, -7.5f),
        vec3( 1.3f, -2.0f, -2.5f),
        vec3( 1.5f,  2.0f, -2.5f),
        vec3( 1.5f,  0.2f, -1.5f),
        vec3(-1.3f,  1.0f, -1.5f)
    };


    // Game loop
    while (!glfwWindowShouldClose(window)) {
        // Check if any events have been activiated (key pressed, mouse moved etc.) and call corresponding response functions
        glfwPollEvents();
        doMovement();


        glEnable(GL_DEPTH_TEST);
        glStencilOp(GL_KEEP, GL_REPLACE, GL_REPLACE);
        // Render
        // Clear the colorbuffer
        glClearColor(0.1f, 0.1f, 0.1f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);

        glStencilMask(0xFF); // Enable writing to the stencil buffer
        glStencilFunc(GL_ALWAYS, 1, 0xFF); // All fragments should update the stencil buffer

        lightPos.z = sin(glfwGetTime());
        lightPos.x = cos(glfwGetTime());

        boxShader.use();

        mat4 model;
        mat4 view = cam.getViewMatrix();
        //model = glm::rotate(model, (GLfloat)glm::radians(glfwGetTime() * 50.0f), vec3(0.5f, 1.0f, 0.0f));
        boxShader.setUniform("view", view);
        boxShader.setUniform("projection", projection);

        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, texture);

        for (GLushort i = 0; i < 10; i++) {
            model = {};
            model = glm::translate(model, cubePositions[i]);

            GLfloat angle = 20.0f * i;
            model = glm::rotate(model, angle, glm::vec3(0.0f, 0.7f, 0.2f));

            boxShader.setUniform("model", model);

            box.draw();
        }

        // Render pass 2

        glStencilFunc(GL_NOTEQUAL, 1, 0xFF);
        glStencilMask(0x00); // Disable writing to the stencil buffer
        singleShader.use();

        singleShader.setUniform("view", view);
        singleShader.setUniform("projection", projection);

        for (GLushort i = 0; i < 10; i++) {
            model = glm::translate(model, cubePositions[i]);

            model = glm::scale(model, vec3(1.1f, 1.1f, 1.1f));

            GLfloat angle = 20.0f * i;
            model = glm::rotate(model, angle, glm::vec3(0.0f, 0.7f, 0.2f));

            singleShader.setUniform("model", model);

            box.draw();
            model = {};
        }

        glStencilMask(0xFF);

        // Swap the screen buffers
        glfwSwapBuffers(window);

    }
    std::cerr << glGetError();
    // Terminate GLFW, clearing any resources allocated by GLFW.
    glfwTerminate();
    return 0;
}

// Is called whenever a key is pressed/released via GLFW
//                                           scancode            mode
void keyCallback(GLFWwindow* window, int key, int , int action, int ) {

    if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS)
        glfwSetWindowShouldClose(window, GL_TRUE);

    if(key == GLFW_KEY_I && action == GLFW_PRESS)
        std::cout << glm::to_string(cam.getViewMatrix()) << std::endl;

    if (key >= 0 && key < 1024)
    {
        if (action == GLFW_PRESS)
            keys[key] = true;
        else if (action == GLFW_RELEASE)
            keys[key] = false;
    }

}

void mouseCallback(GLFWwindow *, double mouseX, double mouseY) {
    GLfloat xOffset = mouseX - lastMouseX;
    GLfloat yOffset = lastMouseY - mouseY; // Reversed since y-coordinates range from bottom to top
    lastMouseX = mouseX;
    lastMouseY = mouseY;

    const GLfloat sensitivity = 0.003f;
    xOffset *= sensitivity;
    yOffset *= sensitivity;

    cam.look(xOffset, yOffset);
}

void doMovement() {
    GLfloat currentFrame = glfwGetTime();
    deltaTime = currentFrame - lastFrame;
    lastFrame = currentFrame;

    if(keys[GLFW_KEY_W])
        cam.move(CAM_FORWARD, deltaTime);
    if(keys[GLFW_KEY_S])
        cam.move(CAM_BACKWARD, deltaTime);
    if(keys[GLFW_KEY_A])
        cam.move(CAM_LEFT, deltaTime);
    if(keys[GLFW_KEY_D])
        cam.move(CAM_RIGHT, deltaTime);
}

Here is the draw function for my cubes:



void Cube::draw() {
    glBindVertexArray(Cube::VAO);
    glDrawArrays(GL_TRIANGLES, 0, 36);

    glBindVertexArray(0);
}