Android OpenGL ES详解——模板Stencil

目录

一、概念

1、模板测试

2、模板缓冲

二、模板测试如何使用

1、开启和关闭模板测试

2、开启/禁止模板缓冲区写入

3、模板测试策略函数

4、更新模板缓冲

5、模板测试应用——物体轮廓

三、模板缓冲如何使用

1、创建模板缓冲

2、使用模板缓冲

3、模板缓冲应用——描边

四、源码下载


一、概念

1、模板测试

在OpenGL中,模板测试(Stencil Testing)是一种用于控制像素绘制的方式。它允许开发者根据模板缓冲区(Stencil Buffer)中的值来决定是否绘制某个像素。模板测试通常用于实现一些高级的图形效果,如阴影、反射、折射等。

当片段着色器处理完片段之后,模板测试(Stencil Test) 就开始执行了,和深度测试一样,它能丢弃一些片段。仍然保留下来的片段进入深度测试阶段,深度测试可能丢弃更多。模板测试基于另一个缓冲,这个缓冲叫做模板缓冲(Stencil Buffer),我们被允许在渲染时更新它来获取有意思的效果。

模板测试与深度测试类似,但在渲染管线中发生在深度测试之前。模板测试也会丢弃掉一些片段,只是丢弃的片段数量比深度测试少。

2、模板缓冲

模板测试是基于一个缓冲区 -- 模板缓冲区(stenci buffer),同理该缓冲区也是由我们创建窗口库创建的,我使用的库是GLFW库。

模板缓冲中的模板值(Stencil Value)通常是8位的,因此每个片段/像素共有256种不同的模板值(译注:8位就是1字节大小,因此和char的容量一样是256个不同值)。这样我们就能将这些模板值设置为我们链接的,然后在模板测试时根据这个模板值,我们就可以决定丢弃或保留它了。

StencilBuffer是模板缓冲,可以用来实现一些诸如描边,遮罩之类的操作。

模板测试和深度测试作用类似,模板测试主要是通过对比模板缓冲区来决定是否需要对片段进行丢弃。

在渲染时更新模板缓冲区可以获取很多有趣的效果。

一个模板缓冲区通常使用8位来表示,因此一个像素可以有256中表示方法。

每个窗口库都需要为你设置模板缓冲。GLFW自动做了这件事,所以你不必告诉GLFW去创建它,但是其他库可能没默认创建模板库,所以一定要查看你使用的库的文档。(GLFW 是一个 OpenGL 的应用框架,支持 Linux 和 Windows。GLFW 主要用来处理特定操作系统下的特定任务,例如 OpenGL 窗口管理、分辨率切换、键盘、鼠标以及游戏手柄、定时器输入、线程创建等等。)

上面图示中:首先要使用模板测试,必须先开启glEnabIe(GL STENCIL TEST),然后先使用0来清空模板缓冲区,然后开启矩形片段用1填充,此时我们将1写入模板缓冲区,绘制出来的图形只会显示模板缓冲区为1的像素图形。

模板测试就是通过模板缓冲中记录的模板信息实现的,具体来说就是渲染管线在模板缓冲区中为每个位置的片元保存了一个“模板值”,当像素需要进行模板测试时,将设定的模板参考值与该片元对应位置的模板值进行比较,符合条件的片元通过测试,不符合条件的则被丢弃,不进行渲染;

二、模板测试如何使用

无论我们在渲染哪里的片段,模板缓冲操作都允许我们把模板缓冲设置为一个特定值。改变模板缓冲的内容实际上就是对模板缓冲进行写入。在同一次(或接下来的)渲染迭代我们可以读取这些值来决定丢弃还是保留这些片段。当使用模板缓冲的时候,你可以随心所欲,但是需要遵守下面的原则:

  • 开启模板缓冲写入。
  • 渲染物体,更新模板缓冲。
  • 关闭模板缓冲写入。
  • 渲染(其他)物体,这次基于模板缓冲内容丢弃特定片段。

使用模板缓冲我们可以基于场景中已经绘制的片段,来决定是否丢弃特定的片段。

1、开启和关闭模板测试

你可以开启GL_STENCIL_TEST来开启模板测试。接着所有渲染函数调用都会以这样或那样的方式影响到模板缓冲。

glEnable(GL_STENCIL_TEST);

注意:在Android中,只使用这句是无法启用模板测试的,还需要使用下面代码配置GLSurfaceView,因为默认的配置是没有配置模板测试的。

要注意的是,像颜色和深度缓冲一样,在每次循环,你也得清空模板缓冲。

glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);

2、开启/禁止模板缓冲区写入

同时,和深度测试的glDepthMask函数一样,模板缓冲也有一个相似函数。glStencilMask允许我们给模板值设置一个位掩码(Bitmask,掩码‌是一种用于对数据进行编码和解码的技术,通过二进制代码对目标字段进行位与运算,屏蔽当前的输入位,从而实现数据的部分或全部隐藏,以保护敏感信息。掩码在计算机科学和数据处理中有着广泛的应用,包括数据压缩、权限控制、网络编程等‌,掩码在图像处理和对象分割等领域中用于标识和处理特定的图像区域‌。),它与模板值进行按位与(AND)运算决定缓冲是否可写。默认设置的位掩码都是1,这样就不会影响输出,但是如果我们设置为0x00,所有写入模板缓冲最后都是0。这和深度缓冲的glDepthMask(GL_FALSE)很类似:

// 0xFF == 0b11111111
//此时,模板值与它进行按位与运算结果是模板值,模板缓冲可写
// 每一位写入模板缓冲时都保持原样
glStencilMask(0xFF); // 0x00 == 0b00000000 == 0
//此时,模板值与它进行按位与运算结果是0,模板缓冲不可写
// 每一位在写入模板缓冲时都会变成0(禁用写入)
//注意:他不是关闭模板,而是不让模板被写入,关闭模板的话连判断都没了。
glStencilMask(0x00); 

大多数情况你的模板遮罩(stencil mask)写为0x00或0xFF就行,但是最好知道有一个选项可以自定义位遮罩。

3、模板测试策略函数

和深度测试类似,模板测试也有对应的api函数来设置模板测试的策略,如测试失败或者成功的情况下如何处理模板缓冲区。glStencilFunc函数决定一个片段的去留——是否会显示在屏幕上。通过测试则显示,否则不显示。

模板测试提供了两个函数:glStencilFunc and glStencilOp.

void glStencilFunc(GLenum func, GLint ref, GLuint mask)

函数有三个参数:

  • func:设置模板测试操作。这个测试操作应用到已经储存的模板值和 giStenciFunc 的 ref 值上即判断模板测试func:通过的条件,例如我func设置为GL EQUAL,ref为1,那么模板测试通过的条件就是模板值等于1,可用的选项是:GL_NEVERGL_LEQUALGL_GREATERGL_GEQUALGL_EQUALGL_NOTEQUALGL_ALWAYS。它们的语义和深度缓冲的相似。func设置模板测试成功与否的策略。包括下面几种情况:
func策略描述
GL_NEVER每次模板测试都失败
GL_LESS比参考值更小就通过测试
GL_LEQUAL小余或等于参考值就通过测试
GL_GREATER比参考值更大就通过测试
GL_GEQUAL大于或等于参考值就通过测试
GL_EQUAL缓冲区的值和参数值ref相等
GL_NOTEQUAL缓冲区的值和参数值ref不相等
GL_ALWAYS每次模板测试都成功

  • ref:指定模板测试的参考值,这个值会用来和模板缓冲区中的值进行比较,来判断是否丢弃片段(按照上述func参数设置的类型进行比较)。
  • mask:设置一个掩码,在模板缓冲区和ref参考值进行比较值前,会使用mask做一个按位与的操作,初始化当前值,一般是在为0xFF即可,保留原值。

在上面简单模板的例子里,方程应该设置为:

glStencilFunc(GL_EQUAL, 1, 0xFF)

它会告诉OpenGL,无论何时,一个片段模板值等于(GL_EQUAL)引用值1,片段就能通过测试被绘制了,否则就会被丢弃。

但是glStencilFunc只描述了OpenGL对模板缓冲做什么,而不是描述我们如何更新缓冲。这就需要glStencilOp登场了。

4、更新模板缓冲

glStencilOp函数决定片段如何改变模板。判断达成某些条件的片段,要如何更新模板。这个函数决定一个片段,是否并如何改变一个模板上的值。

void glStencilOp(GLenum sfail, GLenum dpfail, GLenum dppass)

函数包含三个选项,我们可以指定每个选项的动作:

  • sfail: 如果模板测试失败将采取的动作。
  • dpfail: 如果模板测试通过,但是深度测试失败时采取的动作。
  • dppass: 如果深度测试和模板测试都通过,将采取的动作。

每个选项都可以使用下列任何一个动作。

操作描述
GL_KEEP保持现有的模板值
GL_ZERO将模板值置为0
GL_REPLACE将模板值设置为用glStencilFunc函数设置的ref
GL_INCR如果模板值不是最大值就将模板值+1
GL_INCR_WRAPGL_INCR一样将模板值+1,如果模板值已经是最大值则设为0
GL_DECR如果模板值不是最小值就将模板值-1
GL_DECR_WRAPGL_DECR一样将模板值-1,如果模板值已经是最小值则设为最大值
GL_INVERT按位反转当前模板缓冲区的值

如设置:

glStencilOp(GL_KEEP,GL_KEEP,GL_REPLACE);

则表示模板测试失败时,保持原有模板值,模板测试成功但是深度测试失败时保持原有模板值,两种都成功,则更新模板值为参考值。

glStencilOp函数默认设置为 (GL_KEEP, GL_KEEP, GL_KEEP) ,所以任何测试的任何结果,模板缓冲都会保留它的值。默认行为不会更新模板缓冲,所以如果你想写入模板缓冲的话,你必须像任意选项指定至少一个不同的动作。

使用glStencilFuncglStencilOp,我们就可以指定在什么时候以及我们打算怎么样去更新模板缓冲了,我们也可以指定何时让测试通过或不通过。什么时候片段会被抛弃。glStencilOp一般只设置一次,而glStencilFunc在绘制每个模型前都会设置一次。

5、模板测试应用——物体轮廓

下面我们使用一个案例来分析模板测试。

看了前面的部分你未必能理解模板测试是如何工作的,所以我们会展示一个用模板测试实现的一个特别的和有用的功能,叫做物体轮廓(Object Outlining)

物体轮廓就像它的名字所描述的那样,它能够给每个(或一个)物体创建一个有颜色的边。在策略游戏中当你打算选择一个单位的时候它特别有用。给物体加上轮廓的步骤如下:

  1. 首先开启模板测试,清空模板缓冲区,开启模板缓冲区写入
  2. 在绘制源物体之前设置模板测试操作为GL_ALWAYS,设置参考值为1,掩码为0xFF,这样可以在每次绘制的时候都可以将模板缓冲区写入1。//使用gIStencil0p选项中的GL_REPLACE将模板值设置成glStencilFunc中的ref值
  3. 绘制源物体,写入模板缓冲
  4. 禁止模板缓冲区写入并禁止深度测试。
  5. 将源物体放大一点。
  6. 使用一个纯颜色的片段着色器对源物体进行绘制。
  7. 绘制源物体,但是此时使用GL_NOTEQUAL 的方式进行模板缓冲区绘制策略
  8. 重新开启深度缓冲区和模板可写入操作。

这个过程将每个物体的片段模板缓冲设置为1,当我们绘制边框的时候,我们基本上绘制的是放大版本的物体的通过测试的地方,放大的版本绘制后物体就会有一个边。我们基本会使用模板缓冲丢弃所有的不是原来物体的片段的放大的版本内容。

场景中的物体边框的绘制方法最后看起来像这样:

glEnable(GL_DEPTH_TEST);
glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);glStencilMask(0x00); // 绘制地板时确保关闭模板缓冲的写入
normalShader.Use();
DrawFloor()  glStencilFunc(GL_ALWAYS, 1, 0xFF);
glStencilMask(0xFF);
DrawTwoContainers();glStencilFunc(GL_NOTEQUAL, 1, 0xFF);
glStencilMask(0x00);
glDisable(GL_DEPTH_TEST);
shaderSingleColor.Use();
DrawTwoScaledUpContainers();
glStencilMask(0xFF);
glEnable(GL_DEPTH_TEST);

理解这段代码后面的模板测试的思路并不难以理解。如果还不明白尝试再仔细阅读上面的部分,尝试理解每个函数的作用,现在你已经看到了它的使用方法的例子。

这个边框的算法的结果在深度测试教程的那个场景中,看起来像这样:

三、模板缓冲如何使用

使用模板缓存,分为两大部分:

1、创建模板缓冲

  • 创建Stencil,用其他模型,遮罩等的片段,去创建模板缓冲。(遮罩也完全可以不被显示出来,只更新模板)
  • 打开模板缓冲的写入 glStencilMask(0xFF) ,用某些模型,或遮罩图片,写入缓冲。

2、使用模板缓冲

利用创建好的Stencil去和需要被遮罩处理的片段比较,去决定目标片段是否被保留。

3、模板缓冲应用——描边

描边的例子步骤如下:

1、创建模板缓冲

打开模板缓冲的写入和深度测试,以正常的绘制模型和写入模板。

写入原始模型的片段(绘图),同时写入模板缓冲(创建Mask)

2、使用缓冲的部分

关闭模板缓冲写入,防止模板为污染

关闭Z-test深度测试,将描边画在最上面。

将原始模型放大一点,改用单色shader(描边色)

当模板值不是1的时候,写入单色(中间有原模型的部位,不会被写入。没有原模型的部位,被写入。)

注意整体绘制顺序,因为关闭ZBuffer深度缓冲之后,后画的一定会覆盖先画的。

四、源码下载

推荐文章

android opengl测试demo opengl模板测试_mob64ca14150f43的技术博客_51CTO博客

https://learnopengl-cn.readthedocs.io/zh/latest/04%20Advanced%20OpenGL/02%20Stencil%20testing/#_1https://zhuanlan.zhihu.com/p/620697689

OpenGL学习笔记——StencilBuffer-腾讯游戏学堂

学习OpenGL ES for Android(十九)— 模板测试_gles20-CSDN博客

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/996.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

RHCE笔记-DNS服务器

一.DNS简介 DNS(域名系统)是一种互联网服务,负责将我们熟悉的域名(比如 www.example.com)转换为计算机能理解的IP地址(比如 192.0.2.1)。这样,当你在浏览器中输入网址时,…

高效自动化测试,引领汽车座舱新纪元——实车篇

引言 作为智能网联汽车的核心组成部分,智能座舱不仅是驾驶者与车辆互动的桥梁,更是个性化、智能化体验的源泉。实车测试作为验证智能座舱功能实现、用户体验、行车安全及法规符合性的关键环节,能够最直接地模拟真实驾驶场景,确保…

数智税务 | 大企业税务管理,即将面临哪些需求变革?

大企业税务管理,即将面临哪些需求变革? 随着“金税四期”的推进和发票电子化的发展,中国税务机关的税收征管模式逐步从传统的“经验管税”、“以票控税”转向“以数治税”的精准监管模式。这一转变既为大企业供应链加速升级带来了便利&#…

数字IC后端实现之Innovus Place跑完density爆涨案例分析

下图所示为咱们社区a7core后端训练营学员的floorplan。 数字IC后端实现 | Innovus各个阶段常用命令汇总 该学员跑placement前density是59.467%,但跑完place后density飙升到87.68%。 仔细查看place过程中的log就可以发现Density一路飙升! 数字IC后端物…

[SAP ABAP] 自定义字段提供F4帮助

在SAP系统中,F4帮助是一个强大的功能,它允许用户在输入字段值时快速搜索和选择数 我们可以通过编写代码来为自定义字段提供F4帮助 程序代码 REPORT z437_test_2024.* 自定义数据类型 TYPES: BEGIN OF ty_mara,matnr TYPE mara-matnr, " 物料编号…

c怎么与python交互

ctypes是Python的一个外部库,可以使用python语言调用已经编译好的C语言函数以及数据类型并进行数据交换等。ctypes的官方文档在https://docs.python.org/3/library/ctypes.html 1、ctypes基本数据类型映射表 2、python调用c语言的函数库 (1&#xff09…

ssm042在线云音乐系统的设计与实现+jsp(论文+源码)_kaic

摘 要 随着移动互联网时代的发展,网络的使用越来越普及,用户在获取和存储信息方面也会有激动人心的时刻。音乐也将慢慢融入人们的生活中。影响和改变我们的生活。随着当今各种流行音乐的流行,人们在日常生活中经常会用到的就是在线云音乐系统…

使用TypeORM进行数据库操作

💓 博客主页:瑕疵的CSDN主页 📝 Gitee主页:瑕疵的gitee主页 ⏩ 文章专栏:《热点资讯》 使用TypeORM进行数据库操作 引言 TypeORM 简介 安装 TypeORM 配置 TypeORM 定义实体 连接数据库 运行项目 高级功能 事务管理 关…

【2024】强网杯

web: PyBlockly: ​ 网站是一个通过block的堆积木的形式编程,有两种数据类型以及四种函数,分别是正常运算,print输出,min和max功能,随便写一些代码,发现结果会回显出来。 ​ 再来…

使用 Qt 实现自定义罗盘控件

用 Qt 编写一个简单的罗盘控件,该控件能够动态显示方向。该控件实现了一个带有北(N)和南(S)标记的圆形罗盘面盘,具有可以根据输入角度旋转的指针。 代码功能概述 该项目定义了一个 CompassWidget 类&…

项目模块十四:HttpRequest模块

一、项目设计思路 存储HTTP请求要素,提供简单接口 二、成员变量 全部公有 string _method; // 请求方法 string _path; // 资源路径 string _version; // 协议版本 string _body; // 请求正文 smatch _matches; // 资源路径正则提取 …

NASA:全球无机气溶胶酸度的机载观测和模拟比较

目录 简介 摘要 代码 引用 网址推荐 0代码在线构建地图应用 机器学习 Airborne Observations and Modeling Comparison of Global Inorganic Aerosol Acidity 全球无机气溶胶酸度的机载观测和模拟比较 简介 该数据集提供了在2006年至2017年期间收集的十一项空中观测活…

汽车零部件展|2025 第十二届广州国际汽车零部件加工技术及汽车模具展览会邀您共赏汽车行业盛会

汽车是我国国民经济的重要支柱行业,在我国国民经济中起到举足轻重的作用,也是现代高端制造业的代表。改革开放以来,我国汽车产销量保持增长态势,至 2017 年实现汽车销量 2,887.89 万辆。受到国际经济形势、居民需求下滑、国民经济…

JavaEE-多线程初阶(1)

目录 1. 线程的概念 1.1 线程是是什么 1.2 为什么要有线程 1.3 线程和进程的区别 1.4 Java的线程和操作系统线程的关系 2. 第一个多线程程序 2.1 Hello Thread 2.2 使用jconsole观察线程 3. 创建线程 3.1 继承Thread类 3.2 实现Runnable接口 1. 线程的概念 1.1 线程…

[FE] React 初窥门径(四):React 组件的加载过程(render 阶段)

1. 回顾 前几篇文章中,我们采用了 VSCode 插件 CodeTour 来记录代码的执行过程, 并把相关的数据 .tour/ 放到了 github: thzt/react-tour 中。 截止到本文为之,我们总共记录了这些 code-tour, .tour/ ├── 2. 构建过程.tour ├─…

ASP .NET CORE 6 在项目中集成WatchDog开源项目

概念 WatchDog是一个开源的项目,可以实现对.Net 应用程序和API实现实时应用日志和性能监控平台。可以实现实时记录和查看应用程序中的消息、事件、HTTP请求和响应,以及运行时捕获的异常,有效帮助开发人员去排查应用异常,提升开发效…

Chrome浏览器音/视频无法自动播放

背景:由于google的一些制度,我们在写html项目时会发现刷新页面时无法自动播放audio和video,即使你添加了autoplay属性也无济于事, 但是IE和Edge浏览器是可以自动播放的。 解决方案: 本人在网上搜寻了很多方法&#xf…

WPF自定义日历控件Calendar 的方法

推荐下载地址 https://www.haolizi.net/example/view_2107.html <UserControl.Resources><local1:DayConverter x:Key"DayConverter"/><!--导入转换器--><Style x:Key"CalendarStyle1"TargetType"{x:Type Calendar}">&…

TOEIC 词汇专题:旅游计划篇

TOEIC 词汇专题&#xff1a;旅游计划篇 制定旅行计划时&#xff0c;尤其是跨国旅游&#xff0c;会涉及到很多独特的英语词汇。以下是与“旅游计划”相关的托业词汇&#xff0c;帮助你更加自如地规划行程。 1. 旅行服务和优惠 出发前了解一下与服务和优惠相关的常用词汇&#…

Java集合框架面试指南

Java集合框架面试指南 文章目录 Java集合框架面试指南ArrayList特点&#xff1a;LinkedList特点&#xff1a;ArrayDeque特点&#xff1a;PriorityQueue特点&#xff1a;HashMap特点&#xff1a;HashSet特点&#xff1a;LinkedHashMap特点LinkedHashMap经典用法 TreeMap特点Conc…