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);
}