混合(个人感觉叫透明度更好些)
OpenGL中,混合(Blending)通常是实现物体透明度(Transparency)的一种技术。明就是说一个物体(或者其中的一部分)不是纯色(Solid Color)的,它的颜色是物体本身的颜色和它背后其它物体的颜色的不同强度结合。一个有色玻璃窗是一个透明的物体,玻璃有它自己的颜色,但它最终的颜色还包含了玻璃之后所有物体的颜色。这也是混合这一名字的出处,我们混合(Blend)(不同物体的)多种颜色为一种颜色。所以透明度能让我们看穿物体。也就是我们通常说的RGBA,A指的alpha通道。
丢弃片段
下面是草的纹理,我们将草贴到一个四边形上,放到场景中。 所以当添加像草这样的植被到场景中时,我们不希望看到草的方形图像,而是只显示草的部分,并能看透图像其余的部分。我们想要丢弃(Discard)显示纹理中透明部分的片段,不将这些片段存储到颜色缓冲中。在此之前,我们还要学习如何加载一个透明的纹理: 加载图片的RGBA
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
从纹理中获取RGBA
void main()
{
// FragColor = vec4(vec3(texture(texture1, TexCoords)), 1.0);
FragColor = texture(texture1, TexCoords);
// 如果纹理的alpha小于1,说明是透明纹理 ,则舍弃该片段
if(texColor.a < 0.1)
discard;
FragColor = texColor;
}
渲染半透明纹理
虽然直接丢弃片段很好,但它不能让我们渲染半透明的图像。我们要么渲染一个片段,要么完全丢弃它。要想渲染有多个透明度级别的图像,我们需要启用混合(Blending)。和OpenGL大多数的功能一样,我们可以启用GL_BLEND来启用混合:
glEnable(GL_BLEND);
启用了混合之后,我们需要告诉OpenGL它该如何混合。OpenGL中的混合是通过下面这个方程来实现的:
使用glBlendFunc(GLenum sfactor, GLenum dfactor)函数接受两个参数,来设置源和目标因子。OpenGL为我们定义了很多个选项,我们将在下面列出大部分最常用的选项。注意常数颜色向量Cˉconstant可以通过glBlendColor函数来另外设置。
GL_ZERO 因子等于0 GL_ONE 因子等于1 GL_SRC_COLOR 因子等于源颜色向量Cˉsource GL_ONE_MINUS_SRC_COLOR 因子等于1?Cˉsource GL_DST_COLOR 因子等于目标颜色向量Cˉdestination GL_ONE_MINUS_DST_COLOR 因子等于1?Cˉdestination GL_SRC_ALPHA 因子等于Cˉsource的alpha分量 GL_ONE_MINUS_SRC_ALPHA 因子等于1? Cˉsource的alpha分量 GL_DST_ALPHA 因子等于Cˉdestination的alpha分量 GL_ONE_MINUS_DST_ALPHA 因子等于1? Cˉdestination的alpha分量 GL_CONSTANT_COLOR 因子等于常数颜色向量Cˉconstant GL_ONE_MINUS_CONSTANT_COLOR 因子等于1?Cˉconstant GL_CONSTANT_ALPHA 因子等于Cˉconstant的alpha分量 GL_ONE_MINUS_CONSTANT_ALPHA 因子等于1? Cˉconstant的alpha分量
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, 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。
通常我们都可以省略调用glBlendEquation,因为GL_FUNC_ADD对大部分的操作来说都是我们希望的混合方程。
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
结果不正确,是因为混合和深度测试同时开启。深度测试将后面的片元直接丢弃。当写入深度缓冲时,深度缓冲不会检查片段是否是透明的,所以透明的部分会和其它值一样写入到深度缓冲中。结果就是窗户的整个四边形不论透明度都会进行深度测试。即使透明的部分应该显示背后的窗户,深度测试仍然丢弃了它们。 要想让混合在多个物体上工作,我们需要最先绘制最远的物理,最后绘制最近的物体。普通不需要混合的物体任然可以使用深度缓冲正常绘制。我们仍要保证它们在绘制(排序的)透明物体之前已经绘制完毕了。当绘制一个有不透明和透明物体的场景的时候,大体的原则如下: 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);
}
https://learnopengl-cn.github.io/04%20Advanced%20OpenGL/03%20Blending/
|