【OpenGL】笔记二十一、Alpha测试、混合测试

1. 流程

OpenGL中,混合(Blending)通常是实现物体透明度(Transparency)的一种技术。透明就是说一个物体(或者其中的一部分)不是纯色(Solid Color)的,它的颜色是物体本身的颜色和它背后其它物体的颜色的不同强度结合。这在我们之前的教程中其实有过类似的功能,就是在片段着色器中对两个纹理的颜色进行mix,使得最终呈现出来的颜色是两个纹理不同程度的混合:
在这里插入图片描述
而这次我们可以用一种更通用的方法,那就是alpha测试

1.1 Alpha测试

在我们读取图片的时候,有的图片数据除了RGB通道之外,还包含了Alpha通道,我们生成该纹理的时候可以设置为GL_RGBA来读取:

glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);

有了这个Alpha参数,我们可以怎么用呢?

同样看到之前混合纹理时用的片段着色器:

FragColor = mix(texture(texture1, TexCoord), texture(texture2, TexCoord), visibility);

我们使用了一个mix函数,使得两个纹理根据一个0-1之间的值visibility来分别设定它们的可见度,而这个visibility其实就可以看作我们读取的Alpha值,为0时原图片完全透明(着色时不使用它的颜色),为1时原图片完全不透明(着色时完全使用它的颜色)

现在我们想要实现一种效果,我们要在游戏场景中渲染一块草地,只用一个贴图,如下图:
在这里插入图片描述
直接将它渲染在场景中可能会是这样:
在这里插入图片描述
这是因为空白部分的纯色纹理也被渲染出来了,现在我们只要这个图片中草的片段被渲染出来怎么办?

这时候就要用到之前读取的图片alpha值了,在读取的时候,空白部分的alpha值被设定为0,我们可以在片段着色器里这样设置:

#version 330 core
out vec4 FragColor;

in vec2 TexCoords;

uniform sampler2D texture1;

void main()
{             
    vec4 texColor = texture(texture1, TexCoords);
    if(texColor.a < 0.1)
        discard;
    FragColor = texColor;
}

没错,GLSL提供了一个discard功能,在我们读取纹理值,发现它的alpha值小于0.1时,我们就调用discard丢弃这个片段而不着色,这样就能够只渲染草片段的纹理了:
在这里插入图片描述
这只是一种简单的应用,实际应用时,我们还更可能碰到一种情况,就是在渲染半透明物体时可能需要将物体本身的纹理与背景的颜色混合,而使用mix函数肯定无法完成这种较为复杂的操作,这时候就不得不提到OpenGL的另一个功能了

1.2 混合测试

在OpenGL中,也提供了一种类似于mix的方法,那就是混合(Blend)
在这里插入图片描述

我们在开始渲染时,需要开启这个功能:

glEnable(GL_BLEND);

然后,我们需要使用一个函数来指定混合效果:

glBlendFunc(GLenum sfactor, GLenum dfactor);

它的参数有这些类型:
在这里插入图片描述
对于一般的mix效果(使用源颜色向量的alpha作为源因子,使用1−alpha作为目标因子),我们可以这样设置:

glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

我们还可以通过glBlendEquation(GLenum mode)来设置运算符达到其他效果(默认是两个颜色相加):
在这里插入图片描述

1.3 半透明

接下来,我们就来渲染一个有色玻璃:
在这里插入图片描述

首先,在初始化时我们启用混合,并设定相应的混合函数:

glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

由于启用了混合,我们就不需要丢弃片段了,所以我们把片段着色器还原:

#version 330 core
out vec4 FragColor;

in vec2 TexCoords;

uniform sampler2D texture1;

void main()
{             
    FragColor = texture(texture1, TexCoords);
}

效果:
在这里插入图片描述
发现有一个问题,前面的玻璃怎么把后面的玻璃遮挡住了?

这是因为开启了深度测试的原因,如果我们从近到远渲染玻璃的话,后面玻璃被遮挡的部分就不会渲染了,所以这个是比较麻烦的事,一般情况下,我们每次渲染半透明物体的时候,都是把它们按照与相机的距离从后到前渲染,我们可以利用map存储玻璃,键值设为距离,这样它就会自动按照距离存储了:

std::map<float, glm::vec3> sorted;
for (unsigned int i = 0; i < windows.size(); i++)
{
    float distance = glm::length(camera.Position - windows[i]);
    sorted[distance] = windows[i];
}

这里使用了map的一个反向迭代器(Reverse Iterator),反向遍历其中的条目,实现从远到近

for(std::map<float,glm::vec3>::reverse_iterator it = sorted.rbegin(); it != sorted.rend(); ++it) 
{
    model = glm::mat4();
    model = glm::translate(model, it->second);              
    shader.setMat4("model", model);
    glDrawArrays(GL_TRIANGLES, 0, 6);
}

1.4 深度测试、模板测试、Alpha测试和混合测试

那么我们最后结合深度测试、模板测试、Alpha测试和混合测试来一遍

首先开启深度和Alpha测试

    glEnable(GL_DEPTH_TEST);
    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

然后渲染箱子和灯两类不透明物体:

先渲染灯

    while (!glfwWindowShouldClose(window))
    {
        // 输入
        processInput(window);
        //调用glClear函数,清除颜色缓冲之后,整个颜色缓冲都会被填充为glClearColor里所设置的颜色
        glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);

        // 渲染指令
        float timeValue = glfwGetTime();
        deltaTime = timeValue - lastFrame;
        lastFrame = timeValue;

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

        view = camera.GetViewMatrix();
        
        light.view = view;
        light.projection = projection;
        light.Draw(lightShader);

接着开启模板测试渲染箱子,让模板缓存先写入:

        glEnable(GL_STENCIL_TEST);
        glStencilOp(GL_KEEP, GL_REPLACE, GL_REPLACE);

        glStencilFunc(GL_ALWAYS, 1, 0xFF);
        glStencilMask(0xFF);

        box.view = view;
        box.projection = projection;
        box.Draw(ourShader, camera, timeValue);

之后我们关闭模板测试,开始从远到近绘制半透明的玻璃:

        glDisable(GL_STENCIL_TEST);

        glass.view = view;
        glass.projection = projection;
        glass.Draw(glassShader, camera);

最后我们开启模板测试,绘制箱子边框:

        glEnable(GL_STENCIL_TEST);

        glStencilFunc(GL_NOTEQUAL, 1, 0xFF);
        //glStencilMask(0x00);
        glDisable(GL_DEPTH_TEST);
        box.Draw(singleShader, camera, timeValue, 1.05f);
        glEnable(GL_DEPTH_TEST);
        glDisable(GL_STENCIL_TEST);

        glfwSwapBuffers(window);
        glfwPollEvents();
    }

之所以先渲染不透明再渲染半透明,是因为半透明的物体颜色需要混合背景的不透明物体

而之所以要在第一遍渲染箱子之后关闭模板测试,是为了不让渲染玻璃时影响模板缓存

效果(正面):
在这里插入图片描述
效果(反面):
在这里插入图片描述

1.5 测试顺序

在这里插入图片描述
alpha的操作会在multisample fragment operations里面

也就是测试顺序是:裁剪 Alpha 模板 深度 混合

  • 3
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

ycr的帐号

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

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

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

打赏作者

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

抵扣说明:

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

余额充值