PDA

View Full Version : The first object I render isn't writing to the stencil buffer



aparker314159
03-14-2017, 05:52 PM
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);
}

Dark Photon
03-15-2017, 05:07 AM
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.

Silence
03-15-2017, 05:50 AM
Finally, you're not providing GLFW any hints as to whether and how many bits of stencil and depth you need here ().

I checked that too. But according to this (http://www.glfw.org/docs/latest/window_guide.html#window_hints_fb), it seems glfw defaults to use stencil.

aparker314159
03-15-2017, 08:45 AM
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);
}