OpenGL高级-模板测试

效果展示

在这里插入图片描述
在这里插入图片描述

知识点

在片段着色器运行完成后,会执行:透明度测试->模板测试->深度测试
模板测试中存在一个模板缓冲,存储有模板值
模板测试失败片段会直接被丢弃,不会经过深度测试

模板测试对应函数:
    开启模板测试
		glEnable(GL_STENCIL_TEST) 		
	清空模板缓冲
		glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT)
	设置位遮罩:
		如果模板值为1,则模板缓冲对应处模板值可写
		glStencilMask(0xFF) 
		所有模板缓冲对应处模板值不可写
		glStencilMask(0x00)
	配置模板测试:
		设置模板值和mask按位与,将结果与ref比较,func为比较是否成功的判断条件
		gglStencilFunc(GLenum func, GLint ref, GLuint mask)
		设置根据判断条件决定的模板缓冲更新方式:sfail代表模板测试失败时,dpfail代表模板测试通过但深度测试失败时,dppass代表模板测试和深度测试都成功时
		glStencilOp(GLenum sfail, GLenum dpfail, GLenum dppass)

注意:
	使用glStencilMask(0x00)可设置所有片段的模板缓冲为不可写,使用glStencilMask(0xFF) 可设置 渲染物体 的片段的模板缓冲为可写
	
	假设我们有一个矩阵模板缓冲: A,其中的缓冲模板值为A(i,j);
	首先将模板值A(i,j)和mask进行按位与,然后和ref比较,根据func条件判断是否比较成功
	比较结果就是模板测试的结果,如果失败则片段直接被丢弃不会参与深度测试,如果成功会参与深度测试
	当深度测试结果完成时,我们得到每个片段模板和深度测试的结果
	针对每个片段,如果模板测试失败则按照sfail更新其模板缓冲,如果模板缓冲成功深度测试失败则按照dpfail,如果都成功按照dppass
	每个片段模板缓冲的更新方式已确定,检查片段对应模板值是否可写,如果可写则按照更新方式

模板代码

 渲染不使用模板的物体:

// 不使用模板只要关闭模板写入即可
glStencilMask(0x00); 
Shader.Use();
Draw();

 渲染物体

// 所有片段都要写入模板缓冲
glStencilFunc(GL_ALWAYS, 1, 0xFF);
// 设置模板缓冲为可写状态 
glStencilMask(0xFF); 
Shader.Use();
Draw();

 渲染物体的轮廓

// 只渲染轮廓(轮廓就是物体放大后缓冲中为1,但是放大前缓冲为0的片段)
glStencilFunc(GL_NOTEQUAL, 1, 0xFF);
// 禁止修改模板缓冲
glStencilMask(0x00); 
// 关闭深度测试(以免轮廓被地形挡住,物体下方的轮廓可能在地形以下)
glDisable(GL_DEPTH_TEST);
shaderSingleColor.Use();
Draw();

源代码

 仅展示核心代码,其他代码见OpenGL模型文章。

轮廓的片段着色器

#version 330 core
out vec4 color;

void main()
{
    color = vec4(1.00, 0.0, 0.0, 1.0);
}

主程序

// Std. Includes
#include <string>

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

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

// GL includes
#include "Shader.h"
#include "Camera.h"
#include "Model.h"

// GLM Mathemtics
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>

// Other Libs
#include <SOIL.h>

// Properties
GLuint screenWidth = 800, screenHeight = 600;

// Function prototypes
void key_callback(GLFWwindow* window, int key, int scancode, int action, int mode);
void scroll_callback(GLFWwindow* window, double xoffset, double yoffset);
void mouse_callback(GLFWwindow* window, double xpos, double ypos);
void Do_Movement();

// Camera
Camera camera(glm::vec3(0.0f, 0.0f, 3.0f));
bool keys[1024];
GLfloat lastX = 400, lastY = 300;
bool firstMouse = true;
GLfloat ourSize = 0.2;

GLfloat deltaTime = 0.0f;
GLfloat lastFrame = 0.0f;

// The MAIN function, from here we start our application and run our Game loop
int main()
{
    // Init GLFW
    glfwInit();
    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);

    GLFWwindow* window = glfwCreateWindow(screenWidth, screenHeight, "LearnOpenGL", nullptr, nullptr); // Windowed
    glfwMakeContextCurrent(window);

    // Set the required callback functions
    glfwSetKeyCallback(window, key_callback);
    glfwSetCursorPosCallback(window, mouse_callback);
    glfwSetScrollCallback(window, scroll_callback);

    // Options
    glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);

    // Initialize GLEW to setup the OpenGL Function pointers
    glewExperimental = GL_TRUE;
    glewInit();

    // Define the viewport dimensions
    glViewport(0, 0, screenWidth, screenHeight);

    // Setup some OpenGL options
    // 开启深度测试并设置深度测试的选项
    glEnable(GL_DEPTH_TEST);
    glDepthFunc(GL_LESS);
    // 开启模板测试
    glEnable(GL_STENCIL_TEST);
    // 将模板缓冲区的值和0xFF做按位与运算,如果不等于1 则片段能够被绘制(即模板缓冲区中的值应为0)
    glStencilFunc(GL_NOTEQUAL, 1, 0xFF);
    // 设置当通过模板测试和深度测试时更新模板缓冲的值为ref,即上一行中的1
    glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);

    // 构造着色器程序
    Shader shader("C:\\Users\\32156\\source\\repos\\LearnOpenGL\\Shader\\vertexShader.txt", "C:\\Users\\32156\\source\\repos\\LearnOpenGL\\Shader\\fragmentShader.txt");
    Shader shaderSingleColor("C:\\Users\\32156\\source\\repos\\LearnOpenGL\\Shader\\vertexShader.txt", "C:\\Users\\32156\\source\\repos\\LearnOpenGL\\Shader\\lightFragmentShader.txt");
    // 构造模型
    Model ourModel("D:\\Download\\nanosuit\\nanosuit.obj");

    // Game loop
    while (!glfwWindowShouldClose(window))
    {
        // Set frame time
        GLfloat currentFrame = glfwGetTime();
        deltaTime = currentFrame - lastFrame;
        lastFrame = currentFrame;

        // Check and call events
        glfwPollEvents();
        Do_Movement();

        // Clear the colorbuffer
        glClearColor(0.05f, 0.05f, 0.05f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
        
        // 计算观察矩阵和投影矩阵
        glm::mat4 projection = glm::perspective(camera.Zoom, (float)screenWidth / (float)screenHeight, 0.1f, 100.0f);
        glm::mat4 view = camera.GetViewMatrix();
        // 将矩阵传入着色器
        shader.Use();
        glUniformMatrix4fv(glGetUniformLocation(shader.Program, "projection"), 1, GL_FALSE, glm::value_ptr(projection));
        glUniformMatrix4fv(glGetUniformLocation(shader.Program, "view"), 1, GL_FALSE, glm::value_ptr(view));
        shaderSingleColor.Use();
        glUniformMatrix4fv(glGetUniformLocation(shaderSingleColor.Program, "projection"), 1, GL_FALSE, glm::value_ptr(projection));
        glUniformMatrix4fv(glGetUniformLocation(shaderSingleColor.Program, "view"), 1, GL_FALSE, glm::value_ptr(view));

        // 渲染原物体
        shader.Use();
        // 设置片段总会通过模板测试,即所有片段的模板测试都成功
        glStencilFunc(GL_ALWAYS, 1, 0xFF);
        glStencilMask(0xFF);

        // 传入着色器变换矩阵
        glm::mat4 model = glm::mat4(1.0f);
        model = glm::translate(model, glm::vec3(0.0f, -1.75f, 0.0f)); // Translate it down a bit so it's at the center of the scene
        model = glm::scale(model, glm::vec3(ourSize, ourSize, ourSize));	// It's a bit too big for our scene, so scale it down    
        glUniformMatrix4fv(glGetUniformLocation(shader.Program, "model"), 1, GL_FALSE, glm::value_ptr(model));
        // 直接渲染
        ourModel.Draw(shader);

        // 渲染轮廓,使用的还是刚才得到的模板缓冲,但是这次渲染的模型是放大后的
        // 重点是物体放大后多了一圈轮廓的片段,这些轮廓片段在模板缓冲中的模板值为0
        glStencilFunc(GL_NOTEQUAL, 1, 0xFF);
        // 禁止写入模板缓冲,这样模板缓冲中模板值是刚才渲染的物体写入的,模板缓冲中模板值为1的是放大前物体的片段
        // 比较模板缓冲和1,当不相等时保留片段,即保留模板缓冲值为0的轮廓片段
        glStencilMask(0x00);
        // 关闭深度测试,因为物体的轮廓可能在地形的后面(比如人物模型的脚轮廓,因为它会比脚低一些)
        glDisable(GL_DEPTH_TEST);
        shaderSingleColor.Use();
        GLfloat scale = 1.014;
    
        model = glm::mat4(1.0f);
        model = glm::translate(model, glm::vec3(0.0f, -1.75f, 0.0f)); // Translate it down a bit so it's at the center of the scene
        model = glm::scale(model, glm::vec3(ourSize * scale, ourSize * scale, ourSize * scale));	// It's a bit too big for our scene, so scale it down
        glUniformMatrix4fv(glGetUniformLocation(shaderSingleColor.Program, "model"), 1, GL_FALSE, glm::value_ptr(model));
        ourModel.Draw(shaderSingleColor);

        // 将关闭的深度测试和模板写入都打开
        glStencilMask(0xFF);
        glEnable(GL_DEPTH_TEST);

        // Swap the buffers
        glfwSwapBuffers(window);
    }

    glfwTerminate();
    return 0;
}

#pragma region "User input"
GLuint loadTexture(GLchar* path)
{
    //Generate texture ID and load texture data 
    GLuint textureID;
    glGenTextures(1, &textureID);
    int width, height;
    unsigned char* image = SOIL_load_image(path, &width, &height, 0, SOIL_LOAD_RGB);
    // Assign texture to ID
    glBindTexture(GL_TEXTURE_2D, textureID);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, image);
    glGenerateMipmap(GL_TEXTURE_2D);

    // Parameters
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glBindTexture(GL_TEXTURE_2D, 0);
    SOIL_free_image_data(image);
    return textureID;

}

// Moves/alters the camera positions based on user input
void Do_Movement()
{
    // Camera controls
    if (keys[GLFW_KEY_W])
        camera.ProcessKeyboard(FORWARD, deltaTime);
    if (keys[GLFW_KEY_S])
        camera.ProcessKeyboard(BACKWARD, deltaTime);
    if (keys[GLFW_KEY_A])
        camera.ProcessKeyboard(LEFT, deltaTime);
    if (keys[GLFW_KEY_D])
        camera.ProcessKeyboard(RIGHT, deltaTime);
    if (keys[GLFW_KEY_Q])
        ourSize += 0.01;
}

// Is called whenever a key is pressed/released via GLFW
void key_callback(GLFWwindow* window, int key, int scancode, int action, int mode)
{
    if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS)
        glfwSetWindowShouldClose(window, GL_TRUE);

    if (action == GLFW_PRESS)
        keys[key] = true;
    else if (action == GLFW_RELEASE)
        keys[key] = false;
}

void mouse_callback(GLFWwindow* window, double xpos, double ypos)
{
    if (firstMouse)
    {
        lastX = xpos;
        lastY = ypos;
        firstMouse = false;
    }

    GLfloat xoffset = xpos - lastX;
    GLfloat yoffset = lastY - ypos;

    lastX = xpos;
    lastY = ypos;

    camera.ProcessMouseMovement(xoffset, yoffset);
}

void scroll_callback(GLFWwindow* window, double xoffset, double yoffset)
{
    camera.ProcessMouseScroll(yoffset);
}

#pragma endregion
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

仰望—星空

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值