Purpose of the Sample:
Demonstrates how to use the stencil buffer to mask draw calls. Over time, a portion of the man’s body will be rendered in wireframe. The effect is achieved like so.
- Draw Filled Man
- Write 0 to the stencil buffer for each fragment rendered to by the filled man.
- Render an invisible quad in front of the man.
- Write 1 to the stencil buffer for each fragment rendered to by the quad.
- Render the Wireframe Man
- Only display the wireframe man where the stencil buffer value is 1 which is wherever the quad was rendered.
Download RenderDoc Frame Capture
Captured with RenderDoc Version 1.2 (x86) (x64)
CGame.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
#ifndef __CGAME__ #define __CGAME__ #include <glad/glad.h> #include "shader_s.h" class Model; class CGame { public: CGame() {}; void Initialize(unsigned int screenWidth, unsigned int screenHeight); void Shutdown(); void Render(); void OnResize(int screenWidth, int screenHeight); private: void SetupGPUProgram(); void SetupModel(); Shader* m_pShaderPass0; Shader* m_pShaderPass1; Shader* m_pShaderStencilPass; Model* m_pManModel; Model* m_pStencilModel; unsigned int m_Width; unsigned int m_Height; }; #endif |
CGame.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 |
#include "CGame.h" #define STB_IMAGE_IMPLEMENTATION #include "stb_image/stb_image.h" #include "Model.h" #include "Mesh.h" #include "glm/mat4x4.hpp" #include "glm/vec3.hpp" #include "glm/glm.hpp" #include "glm/gtc/matrix_transform.hpp" #include "glm/gtc/type_ptr.hpp" #include <chrono> void CGame::Initialize(unsigned int screenWidth, unsigned int screenHeight) { m_Width = screenWidth; m_Height = screenHeight; SetupGPUProgram(); SetupModel(); glEnable(GL_DEPTH_TEST); glDepthFunc(GL_LEQUAL); glEnable(GL_STENCIL_TEST); } void CGame::Shutdown() { } void CGame::OnResize(int width, int height) { m_Width = width; m_Height = height; glViewport(0, 0, m_Width, m_Height); glScissor(0, 0, m_Width, m_Height); } void CGame::Render() { static auto startTime = std::chrono::high_resolution_clock::now(); auto currentTime = std::chrono::high_resolution_clock::now(); float time = std::chrono::duration<float, std::chrono::seconds::period>(currentTime - startTime).count(); glClearColor(0.2f, 0.3f, 0.3f, 1.0f); glClearDepth(1.0f); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); glColorMask(1, 1, 1, 1); // Draw the Filled Man glEnable(GL_STENCIL_TEST); glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE); glStencilFunc(GL_ALWAYS, 0, 0xFF); // all fragments should update the stencil buffer glStencilMask(0xFF); // enable writing to the stencil buffer glm::mat4 model = glm::rotate(glm::mat4(1.0f), time * glm::radians(90.0f), glm::vec3(0.0f, 1.0f, 0.0f)); glm::mat4 view; view = glm::translate(glm::mat4(1.0f), glm::vec3(0.0f, -2.0f, -10.0f)); glm::mat4 projection; projection = glm::perspective(glm::radians(60.0f), m_Width / static_cast<float>(m_Height), 0.03f, 100.0f); glm::mat4 mvp = projection * view * model; m_pShaderPass0->use(); m_pShaderPass0->setMatrix4x4("modelMatrix", glm::value_ptr(model)); m_pShaderPass0->setMatrix4x4("viewMatrix", glm::value_ptr(view)); m_pShaderPass0->setMatrix4x4("projectionMatrix", glm::value_ptr(projection)); m_pShaderPass0->setMatrix4x4("mvpMatrix", glm::value_ptr(mvp)); m_pShaderPass0->setFloat("time", time); glDepthFunc(GL_LEQUAL); glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); m_pManModel->meshes[0].Draw(*m_pShaderPass0); // Draw the Stencil Quad glStencilMask(0xFF); model = glm::rotate(glm::mat4(1.0f), glm::radians(90.0f), glm::vec3(1.0f, 0.0f, 0.0f)); model = glm::translate(model, glm::vec3(0.0f, 4.0f, -2.5f)); model = glm::scale(model, glm::vec3(1.0f, 1.0f, 1.0f)); mvp = projection * view * model; m_pShaderStencilPass->use(); m_pShaderStencilPass->setFloat("time", time); m_pShaderStencilPass->setMatrix4x4("mvpMatrix", glm::value_ptr(mvp)); glDepthFunc(GL_ALWAYS); glDepthMask(0); glStencilFunc(GL_ALWAYS, 1, 0xFF); glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE); glColorMask(0, 0, 0, 0); m_pStencilModel->meshes[0].Draw(*m_pShaderStencilPass); glDepthFunc(GL_LEQUAL); glDepthMask(1); glColorMask(1, 1, 1, 1); // Draw the Wireframe man model = glm::rotate(glm::mat4(1.0f), time * glm::radians(90.0f), glm::vec3(0.0f, 1.0f, 0.0f)); view = glm::translate(glm::mat4(1.0f), glm::vec3(0.0f, -2.0f, -10.0f)); projection = glm::perspective(glm::radians(60.0f), m_Width / static_cast<float>(m_Height), 0.03f, 100.0f); mvp = projection * view * model; m_pShaderPass1->use(); m_pShaderPass1->setMatrix4x4("modelMatrix", glm::value_ptr(model)); m_pShaderPass1->setMatrix4x4("viewMatrix", glm::value_ptr(view)); m_pShaderPass1->setMatrix4x4("projectionMatrix", glm::value_ptr(projection)); m_pShaderPass1->setMatrix4x4("mvpMatrix", glm::value_ptr(mvp)); m_pShaderPass1->setFloat("time", time); glStencilFunc(GL_EQUAL, 1, 0xFF); glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE); glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); m_pManModel->meshes[0].Draw(*m_pShaderPass1); } void CGame::SetupModel() { m_pManModel = new Model(); m_pManModel->Load("man.obj"); m_pStencilModel = new Model(); m_pStencilModel->Load("quad.obj"); } void CGame::SetupGPUProgram() { m_pShaderPass0 = new Shader("vertexPass0.glsl", "fragmentPass0.glsl"); m_pShaderPass1 = new Shader("vertexPass1.glsl", "fragmentPass1.glsl"); m_pShaderStencilPass = new Shader("vertexStencil.glsl", "fragmentStencil.glsl"); } |
vertexPass0.glsl – Draw the Filled Man
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
#version 330 core layout (location = 0) in vec3 position; layout (location = 1) in vec3 normal; uniform mat4 modelMatrix; uniform mat4 viewMatrix; uniform mat4 projectionMatrix; uniform mat4 mvpMatrix; uniform float time; out vec4 fsNormal; void main() { vec4 pos = mvpMatrix * vec4(position.xyz, 1); fsNormal = vec4(normal.xyz, 0); gl_Position = pos; } |
fragmentPass0.glsl – Shade the Filled Man with his Normals
1 2 3 4 5 6 7 8 9 10 |
#version 330 core out vec4 FragColor; in vec4 fsNormal; void main() { vec3 color = vec3(1,0,0); FragColor = vec4(fsNormal.rgb, 1.0f); } |
vertexStencil.glsl – Render the Quad Mask
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
#version 330 core layout (location = 0) in vec3 position; uniform float time; uniform mat4 mvpMatrix; out vec4 fsNormal; void main() { vec3 newModelPos = position + vec3(0, 0, sin(time * 0.4) * 3.0); vec4 pos = mvpMatrix * vec4(newModelPos, 1); gl_Position = pos; } |
fragmentStencil.glsl – Render the Quad Mask
1 2 3 4 5 6 7 |
#version 330 core out vec4 FragColor; void main() { FragColor = vec4(0f, 0f, 0f, 1f); } |
vertexPass1.glsl – Render the Wireframe Man
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
#version 330 core layout (location = 0) in vec3 position; layout (location = 1) in vec3 normal; uniform mat4 modelMatrix; uniform mat4 viewMatrix; uniform mat4 projectionMatrix; uniform mat4 mvpMatrix; uniform float time; out vec4 fsNormal; void main() { vec4 pos = mvpMatrix * vec4(position.xyz, 1); fsNormal = vec4(normal.xyz, 0); gl_Position = pos; } |
fragmentPass1.glsl – Render the Wireframe Man
1 2 3 4 5 6 7 8 9 10 |
#version 330 core out vec4 FragColor; in vec4 fsNormal; void main() { vec3 color = vec3(0,1,0); FragColor = vec4(color.rgb, 1.0f); } |