当前位置: 首页 > news >正文

LearnOpenGL-高级OpenGL-2.模板测试

本人刚学OpenGL不久且自学,文中定有代码、术语等错误,欢迎指正

我写的项目地址:https://github.com/liujianjie/LearnOpenGLProject

文章目录

  • 简单理解
  • 模板测试
    • 模板介绍
    • 模板函数
  • 物体轮廓
    • 介绍
    • 代码
  • 给加载的模型添加轮廓

简单理解

  • 同深度测试一样有一个模板缓冲区,可以存储值,0-255值
  • 想象喷油漆时使用的图案模板,先把模板贴在汽车上或者其他什么地方,然后开始喷油漆。在模板镂空的地方会有油漆喷到汽车上,而没有镂空的地方会挡住油漆。在喷完之后,揭下模板,图案就喷涂在汽车上了
  • 先绘制了一个正方体,在转为屏幕坐标时,占据了一个二维矩阵大小的模板值且为1,想绘制第二个正方体,跟第一个正方体位置差不多,就判断第二个正方体所占二维矩阵像素片段对应的模板缓冲值为0才输出,则不会覆盖第一个正方体。

模板测试

模板介绍

  • 简介

    • 当片段着色器处理完一个片段之后,模板测试(Stencil Test)会开始执行,和深度测试一样,它也可能会丢弃片段。

    • 模板测试是根据又一个缓冲来进行的,它叫做模板缓冲(Stencil Buffer),GFLW自动创建

    • 一个模板缓冲中,(通常)每个模板值(Stencil Value)是8位的。所以每个像素/片段一共能有256种不同的模板值

    • 我们可以将这些模板值设置为我们想要的值,然后当某一个片段有某一个模板值的时候,我们就可以选择丢弃或是保留这个片段了

  • 简单例子

    这个例子解释:

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

  • 大体步骤

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

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

  • 代码

    • 启用模板测试

      glEnable(GL_STENCIL_TEST);
      
    • 需要在每次迭代之前清除模板缓冲

      glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
      
    • 开启和禁止写入模板缓冲值

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

      glStencilMask(0x00)等同深度测试中的glDepthMask(GL_FALSE)=记住就行

模板函数

  • 模板缓冲如何测试

    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,片段将会通过测试并被绘制,否则会被丢弃。

  • 它应该如何影响模板缓冲

    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)的,不论任何测试的结果是如何,模板缓冲都会保留它的值。(即不更新模板值)

物体轮廓

介绍

  • 实现图示

  • 步骤

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

代码

  • glsl

    #version 330 core
    layout (location = 0) in vec3 aPos;uniform mat4 model;
    uniform mat4 view;
    uniform mat4 projection;void main()
    {gl_Position = projection * view * model * vec4(aPos, 1.0);
    }
    
    #version 330 core
    out vec4 FragColor;void main(){ FragColor = vec4(0.04, 0.28, 0.26, 1.0);
    }
    
  • cpp

    // configure global opengl state
    // -----------------------------
    glEnable(GL_DEPTH_TEST);
    glDepthFunc(GL_LESS); // always pass the depth test (same effect as glDisable(GL_DEPTH_TEST))
    // 要启用模板测试
    glEnable(GL_STENCIL_TEST);
    glStencilFunc(GL_NOTEQUAL, 1, 0xFF); // 这可以去除(下面重新设过了)。
    glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);// 当模板值测试成功如何更新模板缓冲,模板测试和深度测试通过为1// build and compile shaders
    // -------------------------
    Shader shader("assest/shader/4高级OpenGL/2.1.模板测试.vs", "assest/shader/4高级OpenGL/2.1.模板测试.fs");
    Shader colorShader("assest/shader/4高级OpenGL/2.1.模板测试-colorshader.vs", "assest/shader/4高级OpenGL/2.1.模板测试-colorshader.fs");
    while (!glfwWindowShouldClose(window)){ colorShader.use();colorShader.setMat4("view", view);colorShader.setMat4("projection", projection);shader.use();shader.setMat4("view", view);shader.setMat4("projection", projection);// 0.确保绘制地板的时候不会更新模板缓冲glStencilMask(0x00);// floorglBindVertexArray(planeVAO);glBindTexture(GL_TEXTURE_2D, floorTexture);model = glm::mat4(1.0f);shader.setMat4("model", model);glDrawArrays(GL_TRIANGLES, 0, 6);glBindVertexArray(0);// 1.在绘制(需要添加轮廓的)物体之前,将模板函数设置为GL_ALWAYS,每当物体的片段被渲染时,将模板缓冲更新为1。glStencilFunc(GL_ALWAYS, 1, 0xFF);glStencilMask(0xFF);// 2.渲染正常大小的物体// cubesfloat time = sin(glfwGetTime());glBindVertexArray(cubeVAO);glActiveTexture(GL_TEXTURE0);glBindTexture(GL_TEXTURE_2D, cubeTexture);model = glm::translate(model, glm::vec3(-1.0f, 0.0f, -1.0f));model = glm::rotate(model, time, glm::vec3(0, 0, 1));shader.setMat4("model", model);glDrawArrays(GL_TRIANGLES, 0, 36);model = glm::mat4(1.0f);model = glm::translate(model, glm::vec3(2.0f, 0.0f, 0.0f));model = glm::rotate(model, time, glm::vec3(0, 0, 1));shader.setMat4("model", model);glDrawArrays(GL_TRIANGLES, 0, 36);// 3.再次绘制物体,但只在它们片段的模板值不等于1时才绘制。glStencilFunc(GL_NOTEQUAL, 1, 0xFF); // 为了不覆盖正常大小的物体// 4.禁用模板写入以及深度测试。//glStencilMask(0x00);      // 这好像没用,禁用模板写入,绘制轮廓模板测试成功依旧成功,虽然没将轮廓所占的模板缓冲值设置为1,但是后面没有其它需要绘制的物体了glDisable(GL_DEPTH_TEST);// 为避免被地板覆盖轮廓,使后绘制的轮廓始终在前面// 5.将每个物体放大一点点。model = glm::mat4(1.0f);model = glm::translate(model, glm::vec3(-1.0f, 0.0f, -1.0f));model = glm::rotate(model, time, glm::vec3(0, 0, 1));model = glm::scale(model, glm::vec3(1.1f, 1.1f, 1.1f));// 6.使用一个不同的片段着色器,输出一个单独的(边框)颜色。colorShader.use();colorShader.setMat4("model", model);glDrawArrays(GL_TRIANGLES, 0, 36);// 同5和6model = glm::mat4(1.0f);model = glm::translate(model, glm::vec3(2.0f, 0.0f, 0.0f));model = glm::rotate(model, time, glm::vec3(0, 0, 1));model = glm::scale(model, glm::vec3(1.1f, 1.1f, 1.1f));colorShader.use();colorShader.setMat4("model", model);glDrawArrays(GL_TRIANGLES, 0, 36);// 7.再次启用模板写入和深度测试。glStencilMask(0xFF);    // 若改为禁用模板写入0x00,clear将不会清除模板的值,这样导致因为存留上一帧的模板残留值,会影响下一帧的图像输出glEnable(GL_DEPTH_TEST);// glfw: swap buffers and poll IO events (keys pressed/released, mouse moved etc.)// -------------------------------------------------------------------------------glfwSwapBuffers(window);glfwPollEvents();
    }
    
  • 效果

  • 我认为的过程-简单版

    • 先绘制正常大小的箱子,并将所占模板缓冲区矩阵填为1

      000000000000000000000000000000000000
      000000000000000000000000000000000000
      000000000000000000000000000000000000
      000000000000000000000000000000000000
      000000000011111111111111000000000000
      000000000011111111111111000000000000
      000000000011111111111111000000000000
      000000000011111111111111000000000000
      000000000011111111111111000000000000
      000000000000000000000000000000000000
      000000000000000000000000000000000000
      000000000000000000000000000000000000
      
    • 再绘制放大一点点的箱子,与模板缓冲区的1值做对比,不等于1时才测试成功

      即:放大的箱子,不会覆盖原先正常大小的箱子片段输出的颜色,而是会占据原来大小箱子周围的片段。

      上面矩阵周围为0的片段

给加载的模型添加轮廓

  • 先说问题

    由于模型的基准点在两脚之间(建模工具的原因),若放大顶点要绘制轮廓的大小,将会绘制错误

    正方体放大顶点能绘制正确是因为,正方体的基准点在中心

  • 如何解决

    要绘制轮廓的顶点,将模型顶点朝着模型法线方向增长一点

  • 代码

    glsl

    #version 330 core
    layout (location = 0) in vec3 aPos;
    layout (location = 1) in vec3 aNormal;out vec3 Normal;uniform mat4 model;
    uniform mat4 view;
    uniform mat4 projection;void main()
    {Normal = mat3(transpose(inverse(model))) * aNormal;// 朝着法线方向增长gl_Position = projection * view * model * vec4(aPos, 1.0) + vec4(0.001 * Normal, 0);
    }

    cpp

    // 2.渲染正常大小的物体
    // 渲染这个模型
    glm::mat4 model = glm::mat4(1.0f);
    model = glm::translate(model, glm::vec3(0.0f, 0.0f, 0.0f));
    model = glm::scale(model, glm::vec3(0.1f, 0.1f, 0.1f));
    ourShader.setMat4("model", model);
    ourModel.Draw(ourShader);// 3.再次绘制物体,但只在它们片段的模板值不等于1时才绘制。
    glStencilFunc(GL_NOTEQUAL, 1, 0xFF); // 为了不覆盖正常大小的物体
    // 4.禁用模板写入以及深度测试。
    glStencilMask(0x00);      // 这好像没用,若启用,绘制轮廓模板测试成功也只是将轮廓所占的模板缓冲值设置为1!
    glDisable(GL_DEPTH_TEST);// 为避免被地板覆盖轮廓,使后绘制的轮廓始终在前面// 5.将每个物体放大一点点。不用再这放大,在顶点着色器里放大
    model = glm::mat4(1.0f);
    model = glm::translate(model, glm::vec3(0.0f, 0.0f, 0.0f));
    model = glm::scale(model, glm::vec3(0.100f, 0.100f, 0.100f));
    
  • 效果

    请添加图片描述

相关文章:

LearnOpenGL-高级OpenGL-2.模板测试

本人刚学OpenGL不久且自学,文中定有代码、术语等错误,欢迎指正 我写的项目地址:https://github.com/liujianjie/LearnOpenGLProject 文章目录简单理解模板测试模板介绍模板函数物体轮廓介绍代码给加载的模型添加轮廓简单理解 同深度测试一样…...

【Git从入门到精通】Git入门

什么是版本控制 版本控制是一套系统,按时间记录某一个或一系列文件的变更,查看以前的特定版本。 使用版本控制系统,你可以将文件或者整个项目恢复到先前的状态,还可以对以前的文件进行对比。 本地版本控制系统 本地版本控制系…...

软件测试18

在桌面上打开终端窗口, 执行如下操作: 查看当前系统中开放的端口有哪些查看哪个程序正在使用 3306 端口(需要 root 用户权限) 注意: 1.某些端口号具备固定用途: 例如: 远程访问常用端口号:22 默认情况下是mysql使用的端口号&…...

C语言实现快速排序(hoare法、挖坑法、前后指针法与非递归实现)——不看后悔系列

目录 1. hoare法 方法与步骤 代码实现 2. 挖坑法 方法与步骤 代码实现 3. 前后指针法 方法与步骤 代码实现 4. 快速排序的缺点与优化 1.快速排序的缺点 2.快速排序的优化 ① 三数取中法选 key 代码实现 ② 小区间优化 代码实现 5. 快速排序的非递归实现 附录…...

如何为系统可靠性的量化提供依据

SLA 即 Service Level Agreement,也就是服务等级协议,它指的是系统服务提供者(Provider)对客户(Customer)的一个服务承诺。 而 SLO 就是 SLA 的具体目标管理办法,它由一系列相关的指标 SLI &am…...

量化投资中的因子是什么?因子是如何分类的,包括哪些?

因子就是对个股有解释的因素。因子的种类很多,不同类别的因子从不同的维度对个股收益进行解释。比如基本面因子的数据来源方面有很大一部分是财务报表,从估值、成长、盈利能力等多个方面对股票收益进行解释。量价因子是围绕价格、成交量等技术指标构建的…...

力扣-修复表中的名字

大家好,我是空空star,本篇带大家了解一道简单的力扣sql练习题。 文章目录前言一、题目:1667. 修复表中的名字二、解题1.正确示范①提交SQL运行结果2.正确示范②提交SQL运行结果3.正确示范③提交SQL运行结果4.正确示范④提交SQL运行结果5.其他…...

【博客633】linux vxlan设备工作原理

linux vxlan设备工作原理 vxlan处理包的原理:以k8s cni flannel组件创建的vxlan设备为例 1、k8s cni组件创建flannel设备flannel.1,且这个设备为vxlan类型的设备 root10.10.10.12:/home/ubuntu# ethtool -i flannel.1 driver: vxlan version: 0.1 fi…...

3.12学习周报

文章目录前言文献阅读摘要简介方法介绍讨论结论相关性分析总结前言 本周阅读文献《Streamflow and rainfall forecasting by two long short-term memory-based models》,文献主要提出两种基于长短时记忆网络的混合模型用于对水流量和降雨量进行预测。小波-LSTM&am…...

电力电子中逐波限流控制以及dsp实现

逐波限流是指在电力系统运行中,对电力设备进行电流保护的一种措施。它的实现方式是通过对电力系统的电流进行逐波监测和控制,每一波电流都可以独立地进行限制,从而保护电力系统设备不受过载损坏或短路故障的影响。 逐波限流的作用是提高电力…...

【数据结构】 顺序表

文章目录1 线性表2 顺序表2.1 概念及结构2.2 接口实现2.3 数组相关面试题2.4 顺序表的问题与思考1 线性表 线性表(linear list)是n个具有相同特性的数据元素的有限序列。线性表是一种在实际中广泛使用的数据结构,常见的线性表:顺序…...

Elasticsearch 集群规划- 单台机器核心数计算公式

在做集群规划的时候,到底需要给集群的每个节点多少个核心数?这个问题一直困扰了我很久。最近一段时间做千亿数据,PB存储量集群规划的时候,突然想明白了这件事,大致可以用一个公式来计算!我觉得这是一个非常…...

Tesla都使用什么编程语言?

作者 | 初光 出品 | 车端 备注 | 转载请阅读文中版权声明 知圈 | 进“汽车电子与AutoSAR开发”群,请加微“cloud2sunshine” 总目录链接>> AutoSAR入门和实战系列总目录 带着对更美好未来的愿景,特斯拉不仅成为有史以来最有价值的汽车公司&…...

1143. 最长公共子序列——【Leetcode每日刷题】

1143. 最长公共子序列 给定两个字符串 text1 和 text2,返回这两个字符串的最长 公共子序列 的长度。如果不存在 公共子序列 ,返回 0 。 一个字符串的 子序列 是指这样一个新的字符串:它是由原字符串在不改变字符的相对顺序的情况下删除某些…...

【并发基础】线程的通知与等待:obj.wait()、obj.notify()、obj.notifyAll()详解

目录 〇、先总结一下这三个方法带来的Java线程状态变化 一、obj.wait() 1.1 作用 1.2 使用前需要持有线程共享对象的锁 1.3 使用技巧 二、obj.notify(All)() 1.1 notify() 方法 1.1.1 调用notify()或notifyAll()不会释放线程的锁 1.2 notifyAll() 方法 1.3 使用技巧 三、使用实…...

css黏性定位-实现商城的分类滚动的标题吸附

传统的黏性定位是使用js通过计算高度来实现的,当元素滚动到一定位置时吸附在当前位置。下面我们通过css来实现黏性定位功能。 黏性定位 黏性定位目前主流的浏览器已经全部支持,顾名思义,黏性定位具有吸附的效果,其实它是positio…...

@Component和@bean注解在容器中创建实例区别

Component和Bean的区别 在Spring Boot中,Component注解和Bean注解都可以用于创建bean。它们的主要区别在于它们的作用范围和创建方式。 Component注解是一种通用的注解,可以用于标注任何类。被标注的类将被Spring容器自动扫描并创建为一个bean。这个bea…...

不写注释就是垃圾

最近Linux6.2出来了增加了很多新的东西,有看点的是,Linux确实要可以在Apple M1上面运行了,这应该是一个很大的新闻,如果有这么稳定的硬件支持,那对于Linux来说相当于又打下了一大片的江山。其中关于Linux6.2的特性罗列…...

深信服一面

1.C变量存储在哪里,生命周期是怎样的 2.静态成员变量和成员函数的特性,在哪里用过吗 3.new和delete是什么,和malloc和free对比有啥优势 4.new能不能重载,重载new有什么用 5.多态是怎么实现的,有什么优势和目的 6.…...

【C语言】深度理解指针(中)

前言✈上回说到,我们学习了一些与指针相关的数据类型,如指针数组,数组指针,函数指针等等,我们还学习了转移表的基本概念,学会了如何利用转移表来实现一个简易计算器。详情请点击传送门:【C语言】…...

【大模型RAG】Docker 一键部署 Milvus 完整攻略

本文概要 Milvus 2.5 Stand-alone 版可通过 Docker 在几分钟内完成安装;只需暴露 19530(gRPC)与 9091(HTTP/WebUI)两个端口,即可让本地电脑通过 PyMilvus 或浏览器访问远程 Linux 服务器上的 Milvus。下面…...

Redis数据倾斜问题解决

Redis 数据倾斜问题解析与解决方案 什么是 Redis 数据倾斜 Redis 数据倾斜指的是在 Redis 集群中,部分节点存储的数据量或访问量远高于其他节点,导致这些节点负载过高,影响整体性能。 数据倾斜的主要表现 部分节点内存使用率远高于其他节…...

短视频矩阵系统文案创作功能开发实践,定制化开发

在短视频行业迅猛发展的当下,企业和个人创作者为了扩大影响力、提升传播效果,纷纷采用短视频矩阵运营策略,同时管理多个平台、多个账号的内容发布。然而,频繁的文案创作需求让运营者疲于应对,如何高效产出高质量文案成…...

智能AI电话机器人系统的识别能力现状与发展水平

一、引言 随着人工智能技术的飞速发展,AI电话机器人系统已经从简单的自动应答工具演变为具备复杂交互能力的智能助手。这类系统结合了语音识别、自然语言处理、情感计算和机器学习等多项前沿技术,在客户服务、营销推广、信息查询等领域发挥着越来越重要…...

A2A JS SDK 完整教程:快速入门指南

目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库&#xff…...

掌握 HTTP 请求:理解 cURL GET 语法

cURL 是一个强大的命令行工具,用于发送 HTTP 请求和与 Web 服务器交互。在 Web 开发和测试中,cURL 经常用于发送 GET 请求来获取服务器资源。本文将详细介绍 cURL GET 请求的语法和使用方法。 一、cURL 基本概念 cURL 是 "Client URL" 的缩写…...

k8s从入门到放弃之HPA控制器

k8s从入门到放弃之HPA控制器 Kubernetes中的Horizontal Pod Autoscaler (HPA)控制器是一种用于自动扩展部署、副本集或复制控制器中Pod数量的机制。它可以根据观察到的CPU利用率(或其他自定义指标)来调整这些对象的规模,从而帮助应用程序在负…...

车载诊断架构 --- ZEVonUDS(J1979-3)简介第一篇

我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 做到欲望极简,了解自己的真实欲望,不受外在潮流的影响,不盲从,不跟风。把自己的精力全部用在自己。一是去掉多余,凡事找规律,基础是诚信;二是…...

ArcGIS Pro+ArcGIS给你的地图加上北回归线!

今天来看ArcGIS Pro和ArcGIS中如何给制作的中国地图或者其他大范围地图加上北回归线。 我们将在ArcGIS Pro和ArcGIS中一同介绍。 1 ArcGIS Pro中设置北回归线 1、在ArcGIS Pro中初步设置好经纬格网等,设置经线、纬线都以10间隔显示。 2、需要插入背会归线&#xf…...

从零手写Java版本的LSM Tree (一):LSM Tree 概述

🔥 推荐一个高质量的Java LSM Tree开源项目! https://github.com/brianxiadong/java-lsm-tree java-lsm-tree 是一个从零实现的Log-Structured Merge Tree,专为高并发写入场景设计。 核心亮点: ⚡ 极致性能:写入速度超…...