第十五课,混合

丢弃片段

将透明度小于一定阈值的片段完全丢弃。

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

discard将该点颜色完全丢弃。

注意,当采样纹理的边缘的时候,OpenGL会对边缘的值和纹理下一个重复的值进行插值(因为我们将它的环绕方式设置为了GL_REPEAT。这通常是没问题的,但是由于我们使用了透明值,纹理图像的顶部将会与底部边缘的纯色值进行插值。这样的结果是一个半透明的有色边框,你可能会看见它环绕着你的纹理四边形。要想避免这个,每当你alpha纹理的时候,请将纹理的环绕方式设置为GL_CLAMP_TO_EDGE:

glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

GL_CLAMP_TO_EDGE 纹理坐标会被约束在0到1之间,超出的部分会重复纹理坐标的边缘,产生一种边缘被拉伸的效果。

混合

启动混合

glEnable(GL_BLEND);

混合方程

最终颜色 = 源颜色 * alpha值1 + 目标颜色 * alpha值2

我们该如何让OpenGL使用这样的alpha因子呢

glBlendFunc(GLenum sfactor, GLenum dfactor)函数接受两个参数,来设置源因子(alpha值1)和目标因子(alpha值2)。

OpenGL为我们定义了很多个选项,我们将在下面列出大部分最常用的选项。

GL_ZERO—————————————————–0
GL_ONE——————————————————-1
不是混合

GL_SRC_COLOR————————————-源颜色
GL_ONE_MINUS_SRC_COLOR—————-1−源颜色
GL_DST_COLOR————————————-目标颜色向量
GL_ONE_MINUS_DST_COLOR——————1−目标颜色向量
上面四个不知道是如何计算的

GL_SRC_ALPHA————————————源颜色的alpha分量
GL_ONE_MINUS_SRC_ALPHA—————-1− 源颜色的alpha分量
GL_DST_ALPHA———————————–目标颜色的alpha分量
GL_ONE_MINUS_DST_ALPHA —————1− 目标颜色的alpha分量
上面四个为常用的数值,尤其是前两个

GL_CONSTANT_COLOR————————常数颜色向量GL_ONE_MINUS_CONSTANT_COLOR—–1−常数颜色向量
GL_CONSTANT_ALPHA————————-常数颜色向量的alpha分量
GL_ONE_MINUS_CONSTANT_ALPHA——1− 常数颜色向量的alpha分量
常数颜色向量可以通过glBlendColor函数来另外设置。

一般情况下我们使用glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
表示每个颜色向量与自己alpha值相乘的和。

也可以使用glBlendFuncSeparate为RGB和alpha通道分别设置不同的选项:

glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ZERO);

混合颜色的alpha = 源alpha * GL_ONE + 目标alpha * GL_ZERO;

OpenGL甚至允许我们改变方程中源和目标部分的运算符。

glBlendEquation(GLenum mode)允许我们设置运算符

  • GL_FUNC_ADD:默认选项,将两个分量相加:C¯result=Src+Dst。
  • GL_FUNC_SUBTRACT:将两个分量相减: C¯result=Src−Dst。
  • GL_FUNC_REVERSE_SUBTRACT:将两个分量相减,但顺序相反:C¯result=Dst−Src。

没用的知识又增加了。

渲染半透明纹理

  1. 启用混合
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
  1. 绘制
//半透明物体
glBindVertexArray(VAO);
blendShader.use();
model = glm::mat4(1.0f);
model = glm::translate(model, glm::vec3(5.0f,1.0f,2.0f));
model = glm::scale(model, glm::vec3(3.2f)); 
blendShader.setMat4("model", model);
blendShader.setMat4("projection", projection);
blendShader.setMat4("view", view);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, texture_blend);
glDrawArrays(GL_TRIANGLES, 0, 36);

在这里插入图片描述
可以看出正方形半透明物体的后方部分正方形不可见。

发生这一现象的原因是,深度测试和混合一起使用的话会产生一些麻烦。当写入深度缓冲时,深度缓冲不会检查片段是否是透明的,所以透明的部分会和其它值一样写入到深度缓冲中。结果就是窗户的整个四边形不论透明度都会进行深度测试。即使透明的部分应该显示背后的窗户,深度测试仍然丢弃了它们。

要想保证窗户中能够显示它们背后的窗户,我们需要首先绘制背后的这部分窗户。这也就是说在绘制的时候,我们必须先手动将窗户按照最远到最近来排序,再按照顺序渲染。

绘制

绘制顺序

  1. 先绘制所有不透明的物体。
  2. 对所有透明的物体排序。
  3. 按顺序绘制所有透明的物体。

排序透明物体的方法

排序透明物体的一种方法是,从观察者视角获取物体的距离。这可以通过计算摄像机位置向量和物体的位置向量之间的距离所获得。接下来我们把距离和它对应的位置向量存储到一个STL库的map数据结构中map会自动根据键值(Key)对它的值排序,所以只要我们添加了所有的位置,并以它的距离作为键,它们就会自动根据距离值排序了。

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中获取值,之后以正确的顺序绘制对应的窗户:

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);
}

上面的代码需要我们提前知道不同物体之间的位置,而在上方渲染的正方体虽然是由六个面组成但还是一个物体,故无法使用这种方法(说明这种方法并不是最佳方法)。

正如教程中所说
在这里插入图片描述
虽然按照距离排序物体这种方法对我们这个场景能够正常工作,但它并没有考虑旋转、缩放或者其它的变换,奇怪形状的物体需要一个不同的计量,而不是仅仅一个位置向量。

更高级的技术还有次序无关透明度(Order Independent Transparency, OIT)
次序无关的半透明渲染实现
源论文链接

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情代码图片