OpenGLES demo - 16. 蒙板 Stencil

本文介绍了如何在iOS的OpenGLES中使用蒙板缓冲(Stencil Buffer)来实现特殊视觉效果,例如镜子。通过设置深度缓冲和蒙板缓冲,以及调整比较函数和操作,使得红色三角形仅在绿色正方形区域内显示,以此来阐述蒙板的工作原理和应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


在OpenGLES的创建中,和颜色缓冲以及深度缓冲相比,蒙板缓冲(Stencil Buffer)不是一个必要的,在实际应用中也出现的少,甚至很多程序不使用蒙板。但是一些特殊的效果有时候会依赖于蒙板缓冲,常见的效果是实现一个镜子,所以我们今天讲一讲简单的蒙板。

蒙板是什么?可以看做是一个像素级别的Mask。它可以按照需求,在屏幕上任意地方、任何形状的一块区域设置一个值,这块区域就是蒙板。然后在接下来的渲染中,开发者可以通过参数的配置,来决定最终渲染的图像是只在这块区域显示,或者是不在这块区域显示。而设置蒙板的这个值,通常是一个8位的整型值。蒙板的原理很简单,下面主要看下在IOS上面怎么实现蒙板的渲染吧。

看过代码的同学应该知道,最开始在AppDelegate里面创建OpenGLESView的时候会设置值,这里讲depth改成24,stencil由0改为8

    [_glView Initialize:8 GreenSize:8 BlueSize:8 AlphaSize:8 DepthSize:24 StencilSize:8 SamplesSize:0];

在创建深度缓冲的时候,由原来的GL_DEPTH_COMPONENT16就改为了GL_DEPTH24_STENCIL8_OES

<p style="margin-top: 0px; margin-bottom: 0px; font-size: 13px; font-family: 'Courier New'; color: rgb(61, 29, 129);"><span style="color: #000000">    </span>glGenRenderbuffers<span style="color: #000000">(</span><span style="color: #272ad8">1</span><span style="color: #000000">, &</span><span style="color: #4f8187">_depthRenderBuffer</span><span style="color: #000000">);</span></p><p style="margin-top: 0px; margin-bottom: 0px; font-size: 13px; font-family: 'Courier New'; color: rgb(61, 29, 129);"><span style="color: #000000">    </span>glBindRenderbuffer<span style="color: #000000">(</span><span style="color: #78492a">GL_RENDERBUFFER</span><span style="color: #000000">, </span><span style="color: #4f8187">_depthRenderBuffer</span><span style="color: #000000">);</span></p><p style="margin-top: 0px; margin-bottom: 0px; font-size: 13px; font-family: 'Courier New'; min-height: 15px;">    </p><p style="margin-top: 0px; margin-bottom: 0px; font-size: 13px; font-family: 'Courier New';">    <span style="color: #703daa">GLuint</span> size = <span style="color: #272ad8">0</span>;</p><p style="margin-top: 0px; margin-bottom: 0px; font-size: 13px; font-family: 'Courier New'; color: rgb(120, 73, 42);"><span style="color: #000000">    size = </span>GL_DEPTH24_STENCIL8_OES<span style="color: #000000">;</span></p><p style="margin-top: 0px; margin-bottom: 0px; font-size: 13px; font-family: 'Courier New'; min-height: 15px;">
</p><p style="margin-top: 0px; margin-bottom: 0px; font-size: 13px; font-family: 'Courier New';">    <span style="color: #3d1d81">glRenderbufferStorage</span>(<span style="color: #78492a">GL_RENDERBUFFER</span>, size, <span style="color: #4f8187">_viewWidth</span>, <span style="color: #4f8187">_viewHeight</span>);</p><p style="margin-top: 0px; margin-bottom: 0px; font-size: 13px; font-family: 'Courier New'; min-height: 15px;">    </p><p style="margin-top: 0px; margin-bottom: 0px; font-size: 13px; font-family: 'Courier New'; color: rgb(61, 29, 129);"><span style="color: #000000">    </span>glBindRenderbuffer<span style="color: #000000">(</span><span style="color: #78492a">GL_RENDERBUFFER</span><span style="color: #000000">, </span><span style="color: #272ad8">0</span><span style="color: #000000">);</span></p>

一般情况下,stencil和depth是公用一块内存,所以depth和stencil的创建也是一起的。


在创建后缓冲的FBO的时候,我们原来已经设置了深度缓冲

glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, _depthRenderBuffer);

在这里,由于_depthRenderBuffer里面也包含了Stencil,所以我们还需要再用_depthRenderBuffer去设置一次stencil的attachment

glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, _depthRenderBuffer);

到这里,我们初始话蒙板缓冲就结束了,下面开始渲染物体了。用到的Shader很简单,Vertex shader接受一个坐标和一个颜色值,讲颜色值传给Fragment shader,Fragment shader显示这个颜色值。深度缓冲清空为1.0,深度比较为GL_LESS, 我们先在屏幕中心画一个深度为0.0的绿色正方形

    float stencilArea[] =
    {
        -0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f,
        0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f,
        -0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f,
        0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f,
    };
    
    glVertexAttribPointer(aLocPos, 4, GL_FLOAT, 0, sizeof(float)*8, &stencilArea[0]);
    glVertexAttribPointer(aLocColor, 4, GL_FLOAT, 0, sizeof(float)*8, &stencilArea[4]);
    
    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);


然后在左边画一个大的红色三角形,深度为-0.5,这样,红色三角形和绿色正方形就有一部分重叠,并覆盖正方形

    float quadArea[] =
    {
        -1.0f, -1.0f, -0.5f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f,
        1.0f, -1.0f, -0.5f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f,
        -1.0f, 1.0f, -0.5f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f,
    };
    
    glVertexAttribPointer(aLocPos, 4, GL_FLOAT, 0, sizeof(float)*8, &quadArea[0]);
    glVertexAttribPointer(aLocColor, 4, GL_FLOAT, 0, sizeof(float)*8, &quadArea[4]);
    
    glDrawArrays(GL_TRIANGLES, 0, 3);


上面就是没有蒙板的最终效果。在使用蒙板之前,我们要清空蒙板缓冲,并设置初始值为0

    glClearStencil(0);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);

然后我们开始开启蒙板,第一个绿色正方形是我们要设置的蒙板的形状,所以我们讲蒙板的比较函数设置为GL_ALWAYS,并讲蒙板的值写为1,并开启蒙板遮罩,这就是下面这个函数要实现的几个功能

    glEnable(GL_STENCIL_TEST);
    glStencilFunc(GL_ALWAYS, 1, 1);

这只是一部分,蒙板缓冲还需要知道,当比较失败了和比较成功之后,蒙板的值要怎么变化,这就需要glStencilOp函数来完成。它的第一个参数是失败了之后蒙板的值怎么变化,第二个参数是我们画的物体没有通过深度测试之后蒙板值怎么变化,而第三个参数是物体通过了深度测试之后值怎么变化。在这个程序中,我们讲蒙板缓冲初始值清空为0,所以没有通过深度测试的话,我们就使用GL_KEEP,即没有画到物体的地方保持0这个初始值,而最后一个参数使用GL_REPLACE,则通过了深度测试的地方,使用我们上一个函数的第二个参数的值,即1。那么,画完绿色正方形之后,正方形的部分蒙板值为1,非正方形的部分蒙板值则为0.

    glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);

画完绿色正方形后,整个屏幕的蒙板值已经发生了变化,接着再设置画红色三角形的蒙板参数。我需要只在绿色正方形的区域画,所以glStencilFunc的第一个参数使用GL_EQUAL,即必须要等于一个蒙板值,才能通过,那么等于多少呢?第二个参数使用1,因为我们刚才已经设置了绿色正方形的蒙板值也为1,所以就和这个值来比较是否相等。然后就是glStencilOp,这里不管是否通过,我们都不再改变蒙板值,所以全部使用GL_KEEP

    glStencilFunc(GL_EQUAL, 1, 1);
    glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);

最后记得关闭蒙板测试

    glDisable(GL_STENCIL_TEST);

来总结一下,最开始全屏的蒙板值是0,然后我们采用总是通过的方式画了绿色正方形,并设置蒙板值为1,这之后,绿色正方形的部分蒙板值为1,剩下地方仍然为0。接着以相等的形式来画红色三角形,比较值为1,这样,只用红色三角形和绿色正方形相交的地方,红色三角形才能画上去。看看最终效果



代码地址 http://download.youkuaiyun.com/detail/hoytgm/7779963

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值