第三十五章 混合总结

文章介绍了如何在OpenGL中实现物体透明度,包括通过alpha值控制透明度,使用纹理的alpha通道,以及混合功能来渲染半透明物体。还讨论了深度测试与透明度结合时的问题,提出了按距离排序透明物体以解决重叠透明部分的渲染顺序。

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

混合:实现物体透明度的一种技术。令物体的颜色是物体本身的颜色和背后其他物体的颜色的不同强度的结合。
物体的透明度是通过颜色的alpha值决定的,该值是颜色向量的第四个分量,如果第四分量是1,则透明度为0,如果第四分量为0,则透明度为 全透明
纹理一般来说有三个颜色分量,但一些材质会有一个内嵌的alpha通道,对每个纹素,英文:Texel,都包含了一个alpha值。能准确告诉我们纹理各个部分的
透明度。

下面学习一下,怎么让某些图片不透明,或者完全透明。
举例:丢弃一个图片中除去中间部分,其他的空白的片段要全部丢弃。相当于要透视其他空白部分,不将这些片段存储到颜色缓冲中。

需要学习如何加载一个透明的纹理。
首先加载有alpha值的纹理,stb_image在纹理有alpha通道的时候会自动加载,但是我们最好告诉OpenGL,纹理现在在使用alpha通道:
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
保证在片段着色器中获取纹理的全部4个颜色分量,而不仅仅是RGB分量:
FragColor = texture(texture1, TexCoords);

下面举例在场景中加入几颗草的物体:
首先创建一个vector,向里面添加几个glm::vec3变量代表草的位置:
vectorglm::vec3 vegetation;
vegetation.push_back(glm::vec3(-1.5f, 0.0f, -0.48f));
vegetation.push_back(glm::vec3( 1.5f, 0.0f, 0.51f));
vegetation.push_back(glm::vec3( 0.0f, 0.0f, 0.7f));
vegetation.push_back(glm::vec3(-0.3f, 0.0f, -2.3f));
vegetation.push_back(glm::vec3( 0.5f, 0.0f, -0.6f));
草的纹理是添加到四边形对象上的,还需要创建另一个VAO,填充VBO,设置正确的顶点属性指针:
glBindVertexArray(vegetationVAO);
glBindTexture(GL_TEXTURE_2D, grassTexture);
for(unsigned int i = 0; i < vegetation.size(); i++)
{
model = glm::mat4(1.0f);
model = glm::translate(model, vegetation[i]);
shader.setMat4(“model”, model);
glDrawArrays(GL_TRIANGLES, 0, 6);
}
下面需要对alpha值进行处理,GLSL中有discard命令,一旦被调用会保证片段不会被进一步处理,就不会进入颜色缓冲。
下面就可以在片段着色器中检测一个片段的alpha值是否低于某个阈值,如果是的话,则丢弃该片段:
#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内建的texture函数来采样纹理的颜色,它第一个参数是纹理采样器,第二个参数是对应的纹理坐标。texture函数的返回值就是,在该纹理坐标上的rgba值(vec4)。
当采用纹理的边缘的时候,OpenGL会对边缘的值和纹理下一个重复的值进行插值,通常使用的GL_REPEAT,但是使用透明值后,纹理图像的顶部
会与底部边缘的纯色值进行插值,结果会得到一个半透明的有色边框,环绕纹理四边形。要避免这个的话,每当使用alpha纹理的时候:
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

直接丢弃片段只能渲染完全透明的片段,如果要渲染半透明的图像,需要用到 混合。可以用GL_BLEND来启用混合:
glEnable(GL_BLEND);
下面需要告诉OpenGL如何进行混合:
最终颜色值=源颜色向量源因子值+目标颜色向量目标因子值
上述是当片段着色器运行完成后,并且所有的测试都通过之后,这个混合方程才会应用到片段颜色输出与当前颜色缓冲中的值,当前颜色缓冲中的值是当前片段
之前存储的之前片段的颜色。当然两个颜色都是由OpenGL自动设定的,物体该是啥颜色就是什么颜色,但是两个因子是可以自由改变的。
举例:
有两个正方形,一个全红,一个全绿。希望半透明的绿色在红色方形智商。红色的方形是目标颜色,即在颜色缓冲中,需要在红色上绘制绿色。
有专门的函数来设置两个因子:glBlendFunc。
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分量
其中常数颜色向量可以通过函数 glBlendColor来另外设置。
举例:为了获取两个物体的混合结果,使用源颜色向量的alpha作为源因子,使用1-alpha作为目标因子。会产生下面的代码:
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

可以用glBlendFuncSeparate为RGB和alpha通道分别设置不同的选项:
glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ZERO);

补充:
还可以通过函数glBlendEquation改变方程中 源和目标的运算符:
GL_FUNC_ADD 默认选项,两个分量相加
GL_FUNC_SUBTRACT 两个分量相减
GL_FUNC_REVERSE_SUBTRACT 两个分量相减,但顺序相反

下面看下渲染半透明纹理具体实战:
首先启用混合,设定相应的混合函数:
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);————因子等于源分量,因子等于1-源分量
启用混合后,不需要丢弃片段了,需要把片段着色器还原:
#version 330 core
out vec4 FragColor;

in vec2 TexCoords;

uniform sampler2D texture1;

void main()
{
FragColor = texture(texture1, TexCoords);
}
则现在,每当OpenGL渲染了一个片段后,都会讲当前片段的颜色和当前颜色缓冲中的片段颜色根据alpha值来进行混合。
但是,深度测试和混合一起使用还是有问题的,当写入深度缓冲的时候,深度缓冲不会检查片段是否透明的,所以透明的部分会和其他值一样写入深度缓冲中。
举例:假如两个窗户有部分重合,窗户的整个四边形不论透明度都会进行深度测试,即使透明的部分应该显示背后的窗户,深度测试仍然会丢弃它们:
想解决该问题,首先需要绘制被遮住的窗户,也就是说:在绘制的时候,必须手动将窗户按照最远到最近来排序,再按顺序渲染。
这种做法就是让混合在多个物体上工作,需要先绘制最远的物体,再绘制最近的物体。
普通不需要混合的物体可以使用深度缓冲正常绘制,不需要排序,只需要在绘制透明物体之前就绘制完毕了。
当绘制一个有不透明和透明物体的场景,步骤如下:
1.先绘制不透明物体
2.对透明物体进行排序
3.按顺序绘制所有透明物体

对透明物体排序方法是:从观察者角度获取物体距离,这个可以通过计算摄像机位置向量和物体的位置向量之间的距离所获得。接下来把距离和对应的位置向量
存储到STL库的map中。自动根据key-value对value进行排序,只要添加了所有位置,用距离作为key,就会得到排序好的value。
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];
}
得到了key从小到大的排序。则在渲染的时候,将以逆序,也就是从大到小,从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);
}
使用了map的一个反向迭代器(Reverse Iterator),反向遍历其中的条目,并将每个窗户四边形位移到对应的窗户位置上。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值