Learn OpenGL 13 模板测试

模板测试

当片段着色器处理完一个片段之后,模板测试(Stencil Test)会开始执行,和深度测试一样,它也可能会丢弃片段。接下来,被保留的片段会进入深度测试,它可能会丢弃更多的片段。模板测试是根据又一个缓冲来进行的,它叫做模板缓冲(Stencil Buffer),我们可以在渲染的时候更新它来获得一些很有意思的效果。

一个模板缓冲中,(通常)每个模板值(Stencil Value)是8位的。所以每个像素/片段一共能有256种不同的模板值。我们可以将这些模板值设置为我们想要的值,然后当某一个片段有某一个模板值的时候,我们就可以选择丢弃或是保留这个片段了。

每个窗口库都需要为你配置一个模板缓冲。GLFW自动做了这件事,所以我们不需要告诉GLFW来创建一个,但其它的窗口库可能不会默认给你创建一个模板库,所以记得要查看库的文档。

模板缓冲的一个简单的例子如下:

模板缓冲首先会被清除为0,之后在模板缓冲中使用1填充了一个空心矩形。场景中的片段将会只在片段的模板值为1的时候会被渲染(其它的都被丢弃了)。

模板缓冲操作允许我们在渲染片段时将模板缓冲设定为一个特定的值。通过在渲染时修改模板缓冲的内容,我们写入了模板缓冲。在同一个(或者接下来的)渲染迭代中,我们可以读取这些值,来决定丢弃还是保留某个片段。使用模板缓冲的时候你可以尽情发挥,但大体的步骤如下:

  • 启用模板缓冲的写入。
  • 渲染物体,更新模板缓冲的内容。
  • 禁用模板缓冲的写入。
  • 渲染(其它)物体,这次根据模板缓冲的内容丢弃特定的片段。

所以,通过使用模板缓冲,我们可以根据场景中已绘制的其它物体的片段,来决定是否丢弃特定的片段。

你可以启用GL_STENCIL_TEST来启用模板测试。在这一行代码之后,所有的渲染调用都会以某种方式影响着模板缓冲。

glEnable(GL_STENCIL_TEST);

注意,和颜色和深度缓冲一样,你也需要在每次迭代之前清除模板缓冲。

glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);

和深度测试的glDepthMask函数一样,模板缓冲也有一个类似的函数。glStencilMask允许我们设置一个位掩码(Bitmask),它会与将要写入缓冲的模板值进行与(AND)运算。默认情况下设置的位掩码所有位都为1,不影响输出,但如果我们将它设置为0x00,写入缓冲的所有模板值最后都会变成0.这与深度测试中的glDepthMask(GL_FALSE)是等价的。

glStencilMask(0xFF); // 每一位写入模板缓冲时都保持原样
glStencilMask(0x00); // 每一位在写入模板缓冲时都会变成0(禁用写入)

大部分情况下你都只会使用0x00或者0xFF作为模板掩码(Stencil Mask),但是知道有选项可以设置自定义的位掩码总是好的。

模板函数

和深度测试一样,我们对模板缓冲应该通过还是失败,以及它应该如何影响模板缓冲,也是有一定控制的。一共有两个函数能够用来配置模板测试:glStencilFunc和glStencilOp。

glStencilFunc(GLenum func, GLint ref, GLuint mask)一共包含三个参数:

  • func:设置模板测试函数(Stencil Test Function)。这个测试函数将会应用到已储存的模板值上和glStencilFunc函数的ref值上。可用的选项有:GL_NEVER、GL_LESS、GL_LEQUAL、GL_GREATER、GL_GEQUAL、GL_EQUAL、GL_NOTEQUAL和GL_ALWAYS。它们的语义和深度缓冲的函数类似。
  • ref:设置了模板测试的参考值(Reference Value)。模板缓冲的内容将会与这个值进行比较。
  • mask:设置一个掩码,它将会与参考值和储存的模板值在测试比较它们之前进行与(AND)运算。初始情况下所有位都为1。

在一开始的那个简单的模板例子中,函数被设置为:

glStencilFunc(GL_EQUAL, 1, 0xFF)

这会告诉OpenGL,只要一个片段的模板值等于(GL_EQUAL)参考值1,片段将会通过测试并被绘制,否则会被丢弃。

但是glStencilFunc仅仅描述了OpenGL应该对模板缓冲内容做什么,而不是我们应该如何更新缓冲。这就需要glStencilOp这个函数了。

glStencilOp(GLenum sfail, GLenum dpfail, GLenum dppass)一共包含三个选项,我们能够设定每个选项应该采取的行为:

  • sfail:模板测试失败时采取的行为。
  • dpfail:模板测试通过,但深度测试失败时采取的行为。
  • dppass:模板测试和深度测试都通过时采取的行为。

每个选项都可以选用以下的其中一种行为:

行为描述
GL_KEEP保持当前储存的模板值
GL_ZERO将模板值设置为0
GL_REPLACE将模板值设置为glStencilFunc函数设置的ref
GL_INCR如果模板值小于最大值则将模板值加1
GL_INCR_WRAP与GL_INCR一样,但如果模板值超过了最大值则归零
GL_DECR如果模板值大于最小值则将模板值减1
GL_DECR_WRAP与GL_DECR一样,但如果模板值小于0则将其设置为最大值
GL_INVERT按位翻转当前的模板缓冲值

默认情况下glStencilOp是设置为(GL_KEEP, GL_KEEP, GL_KEEP)的,所以不论任何测试的结果是如何,模板缓冲都会保留它的值。默认的行为不会更新模板缓冲,所以如果你想写入模板缓冲的话,你需要至少对其中一个选项设置不同的值。

所以,通过使用glStencilFunc和glStencilOp,我们可以精确地指定更新模板缓冲的时机与行为了,我们也可以指定什么时候该让模板缓冲通过,即什么时候片段需要被丢弃。

物体轮廓

仅仅看了前面的部分你还是不太可能能够完全理解模板测试的工作原理,所以我们将会展示一个使用模板测试就可以完成的有用特性,它叫做物体轮廓(Object Outlining)。

物体轮廓所能做的事情正如它名字所描述的那样。我们将会为每个(或者一个)物体在它的周围创建一个很小的有色边框。当你想要在策略游戏中选中一个单位进行操作的,想要告诉玩家选中的是哪个单位的时候,这个效果就非常有用了。为物体创建轮廓的步骤如下:

  1. 在绘制(需要添加轮廓的)物体之前,将模板函数设置为GL_ALWAYS,每当物体的片段被渲染时,将模板缓冲更新为1。
  2. 渲染物体。
  3. 禁用模板写入以及深度测试。
  4. 将每个物体缩放一点点。
  5. 使用一个不同的片段着色器,输出一个单独的(边框)颜色。
  6. 再次绘制物体,但只在它们片段的模板值不等于1时才绘制。
  7. 再次启用模板写入和深度测试。

这个过程将每个物体的片段的模板缓冲设置为1,当我们想要绘制边框的时候,我们主要绘制放大版本的物体中模板测试通过的部分,也就是物体的边框的位置。我们主要使用模板缓冲丢弃了放大版本中属于原物体片段的部分。

实现物体轮廓

所以我们首先来创建一个很简单的片段着色器,它会输出一个边框颜色。

#version 330 core
out vec4 FragColor;


void main()
{
    FragColor = vec4(0.04, 0.28, 0.26, 1.0);
}

接着启动模板测试以及设置模板测试函数以及通过后的操作,这里默认所有像素都会通过模板测试,并且如果不设置模班测试函数默认也是使用这个函数,接着设置通过测试后用1替换掉原来的模板值,也就是0(默认的)。

    glEnable(GL_DEPTH_TEST);
    glDepthFunc(GL_LESS);
    glEnable(GL_STENCIL_TEST);
    glStencilFunc(GL_NOTEQUAL, 1, 0xFF);
    glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);

 另外在渲染地面之前我关闭了模板写入,防止给地面也加上边框。在第一次渲染完后也就是给需要加上边框的物体设置了模板值以后,使用新创建的边框着色器,接着放大要加上边框的物体的scale,这样放大了的部分模板值就还是0,也就是会被渲染成新的着色器设置的颜色。

关键代码

 glEnable(GL_DEPTH_TEST);
    glDepthFunc(GL_LESS);
    glEnable(GL_STENCIL_TEST);
    glStencilFunc(GL_ALWAYS, 1, 0xFF);
    glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);

   // glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);//隐藏光标
  

    // render loop
    // -----------
    while (!glfwWindowShouldClose(window))//每次循环的开始前检查一次GLFW是否被要求退出
    {

        glClearColor(0.1f, 0.1f, 0.1f, 1.0f);//置清空屏幕所用的颜色
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);

        processInput(window);

        float currentFrame = glfwGetTime();
        deltaTime = currentFrame - lastFrame;
        lastFrame = currentFrame;

        lightingShader.use(); // 不要忘记在设置uniform变量之前激活着色器程序!



        lightingShader.setVec3("viewPos", camera.Position);
        lightingShader.setFloat("material.shininess", 32.0f);

        // directional light
        lightingShader.setVec3("dirLight.direction", glm::vec3(-0.2f, -1.0f, -0.3f));
        lightingShader.setVec3("dirLight.ambient", glm::vec3(0.05f, 0.05f, 0.1f));
        lightingShader.setVec3("dirLight.diffuse", glm::vec3(0.4f, 0.4f, 0.7f));
        lightingShader.setVec3("dirLight.specular", glm::vec3(0.7f, 0.7f, 0.7f));

        // Point lights
        for (int i = 0; i < 4; ++i) {
            std::string pointLightIndex = "pointLights[" + std::to_string(i) + "].";
            lightingShader.setVec3(pointLightIndex + "position", pointLightPositions[i]);
            lightingShader.setVec3(pointLightIndex + "ambient", glm::vec3(pointLightColors[i].x * 0.1, pointLightColors[i].y * 0.1, pointLightColors[i].z * 0.1));
            lightingShader.setVec3(pointLightIndex + "diffuse", pointLightColors[i]);
            lightingShader.setVec3(pointLightIndex + "specular", pointLightColors[i]);
            lightingShader.setFloat(pointLightIndex + "constant", 1.0f);
            lightingShader.setFloat(pointLightIndex + "linear", 0.09f);
            lightingShader.setFloat(pointLightIndex + "quadratic", 0.032f);
        }

        // SpotLight
        lightingShader.setVec3("spotLight.position", glm::vec3(camera.Position.x, camera.Position.y, camera.Position.z));
        lightingShader.setVec3("spotLight.direction", glm::vec3(camera.Front.x, camera.Front.y, camera.Front.z));
        lightingShader.setVec3("spotLight.ambient", glm::vec3(0.0f, 0.0f, 0.0f));
        lightingShader.setVec3("spotLight.diffuse", glm::vec3(1.0f, 1.0f, 1.0f));
        lightingShader.setVec3("spotLight.specular", glm::vec3(1.0f, 1.0f, 1.0f));
        lightingShader.setFloat("spotLight.constant", 1.0f);
        lightingShader.setFloat("spotLight.linear", 0.009f);
        lightingShader.setFloat("spotLight.quadratic", 0.0032f);
        lightingShader.setFloat("spotLight.cutOff", glm::cos(glm::radians(10.0f)));
        lightingShader.setFloat("spotLight.outerCutOff", glm::cos(glm::radians(12.5f)));



        lightingShader.setFloat("time",glfwGetTime());

        glm::mat4 projection;
        projection = glm::perspective(glm::radians(camera.Zoom), (float)SCR_WIDTH / (float)SCR_HEIGHT, 0.1f, 100.0f);

        glm::mat4 view;
        view = camera.GetViewMatrix();

        lightingShader.setFloat("mixValue", mixValue);

        lightingShader.setMat4("projection", projection);
        lightingShader.setMat4("view", view);

        glm::mat4 model = glm::mat4(1.0f);
        lightingShader.setMat4("model", model);


        glStencilMask(0x00); // 记得保证我们在绘制地板的时候不会更新模板缓冲
        
        plane.DrawArray(&lightingShader, diffuseMap, specularMap, emissionMap,6);
        

        glStencilFunc(GL_ALWAYS, 1, 0xFF); // 所有的片段都应该更新模板缓冲
        glStencilMask(0xFF); // 启用模板缓冲写入


        for (unsigned int i = 0; i < 10; i++)
        {
            glm::mat4 model;
            model = glm::translate(model, cubePositions[i]);
            float angle = 20.0f * i;
            model = glm::rotate(model, glm::radians(angle), glm::vec3(1.0f, 0.3f, 0.5f));
            lightingShader.setMat4("model", model);

            cube.DrawArray(&lightingShader, diffuseMap, specularMap, emissionMap,36);

        }

        model = glm::mat4();
        model = glm::scale(model, glm::vec3(0.3, 0.3, 0.3));
        lightingShader.setMat4("model", model);
        testModel.Draw(&lightingShader);
       

       lightShader.use();
       lightShader.setMat4("projection", projection);
       lightShader.setMat4("view", view);
       model = glm::mat4(1.0f);
       model = glm::translate(model, lightPos);
       model = glm::scale(model, glm::vec3(0.2f)); // a smaller cube
       lightShader.setMat4("model", model);


       for (unsigned int i = 0; i < 4; i++)
       {
           lightShader.setVec3("lightcolor", pointLightColors[i]);
           model = glm::mat4(1.0f);
           model = glm::translate(model, pointLightPositions[i]);
           model = glm::scale(model, glm::vec3(0.2f)); // Make it a smaller cube
           lightShader.setMat4("model", model);

           light.DrawArray(&lightShader, diffuseMap, specularMap, emissionMap,36);
       }




       borderShader.use();
       glStencilFunc(GL_NOTEQUAL, 1, 0xFF);
       glStencilMask(0x00); // 禁止模板缓冲的写入
       glDisable(GL_DEPTH_TEST);

       borderShader.setMat4("projection", projection);
       borderShader.setMat4("view", view);

       for (unsigned int i = 0; i < 10; i++)
       {
           glm::mat4 model;
           model = glm::translate(model, cubePositions[i]);
           float angle = 20.0f * i;
           model = glm::rotate(model, glm::radians(angle), glm::vec3(1.0f, 0.3f, 0.5f));
           model = glm::scale(model, glm::vec3(1.1, 1.1, 1.1));
           borderShader.setMat4("model", model);

           cube.DrawArray(&borderShader, diffuseMap, specularMap, emissionMap, 36);

       }

       model = glm::mat4();
       model = glm::scale(model, glm::vec3(0.31, 0.301, 0.31));
       borderShader.setMat4("model", model);
       testModel.Draw(&borderShader);


       glStencilMask(0xFF);
       glStencilFunc(GL_ALWAYS, 0, 0xFF);
       glEnable(GL_DEPTH_TEST);


        // glfw: swap buffers and poll IO events (keys pressed/released, mouse moved etc.)
        // -------------------------------------------------------------------------------
        glfwSwapBuffers(window);//交换颜色缓冲
        glfwPollEvents();//函数检查有没有触发什么事件(比如键盘输入、鼠标移动等)、更新窗口状态,并调用对应的回调函数(可以通过回调方法手动设置)。
    }

    // glfw: terminate, clearing all previously allocated GLFW resources.
    // ------------------------------------------------------------------
    glfwTerminate();//释放 GLFW 库所占用的资源
    return 0;
}

最终效果

  • 24
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
目标检测(Object Detection)是计算机视觉领域的一个核心问题,其主要任务是找出图像中所有感兴趣的目标(物体),并确定它们的类别和位置。以下是对目标检测的详细阐述: 一、基本概念 目标检测的任务是解决“在哪里?是什么?”的问题,即定位出图像中目标的位置并识别出目标的类别。由于各类物体具有不同的外观、形状和姿态,加上成像时光照、遮挡等因素的干扰,目标检测一直是计算机视觉领域最具挑战性的任务之一。 二、核心问题 目标检测涉及以下几个核心问题: 分类问题:判断图像中的目标属于哪个类别。 定位问题:确定目标在图像中的具体位置。 大小问题:目标可能具有不同的大小。 形状问题:目标可能具有不同的形状。 三、算法分类 基于深度学习的目标检测算法主要分为两大类: Two-stage算法:先进行区域生成(Region Proposal),生成有可能包含待检物体的预选框(Region Proposal),再通过卷积神经网络进行样本分类。常见的Two-stage算法包括R-CNN、Fast R-CNN、Faster R-CNN等。 One-stage算法:不用生成区域提议,直接在网络中提取特征来预测物体分类和位置。常见的One-stage算法包括YOLO系列(YOLOv1、YOLOv2、YOLOv3、YOLOv4、YOLOv5等)、SSD和RetinaNet等。 四、算法原理 以YOLO系列为例,YOLO将目标检测视为回归问题,将输入图像一次性划分为多个区域,直接在输出层预测边界框和类别概率。YOLO采用卷积网络来提取特征,使用全连接层来得到预测值。其网络结构通常包含多个卷积层和全连接层,通过卷积层提取图像特征,通过全连接层输出预测结果。 五、应用领域 目标检测技术已经广泛应用于各个领域,为人们的生活带来了极大的便利。以下是一些主要的应用领域: 安全监控:在商场、银行
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值