跳转到内容

GLSL 编程/GLUT/透明纹理

来自维基教科书,开放世界中的开放书籍
地球地图,水体透明,即水的alpha分量为0,陆地为1。

本教程涵盖了alpha纹理贴图的各种常见用法,即RGBA纹理图像,其中A(alpha)分量指定了纹理的透明度。

它结合了纹理球体教程中的着色器代码,以及切面教程透明度教程中介绍的概念。

如果您尚未阅读这些教程,现在是一个很好的机会。

丢弃透明片段

[编辑 | 编辑源代码]

让我们从丢弃片段开始,如切面教程中所述。按照纹理球体教程中描述的步骤,将左侧的图像分配给球体的材质,使用以下片段着色器(保持相同的顶点着色器)

varying vec4 texCoords;
uniform sampler2D mytexture;
float cutoff = 0.1;

void main(void) {
    vec2 longitudeLatitude = vec2((atan(texCoords.y, texCoords.x) / 3.1415926 + 1.0) * 0.5,
                                  (asin(texCoords.z) / 3.1415926 + 0.5));

    gl_FragColor = texture2D(mytexture, longitudeLatitude);

    if (gl_FragColor.a < cutoff)
        // alpha value less than user-specified threshold?
    {
        discard; // yes: discard this fragment
    }
}

(您可以将cutoff变量作为uniform传递。)

如果您现在启动应用程序,片段着色器应该读取RGBA纹理,并将alpha值与变量cutoff中指定的阈值进行比较。如果alpha值小于阈值,则片段将被丢弃,表面将显示为透明。

由于我们可以透过透明部分,因此有意义的是,不要启用背面剔除,如切面教程中所述。

Alpha 测试

[编辑 | 编辑源代码]

OpenGL(非ES)有一个固定功能特性,类似于模板缓冲区,可以通过glAlphaFunc根据片段的alpha值接受或丢弃片段。

    glAlphaFunc(GL_GREATER, 0.1);
    glEnable(GL_ALPHA_TEST);

透明度教程描述了如何使用alpha混合来渲染半透明物体。

让我们提醒一下,正确的透明度支持需要按距离相机排序三角形,因为我们必须禁用深度测试才能在透明三角形“后面”写入。为了避免对单个球形对象排序三角形,我们使用剔除来先绘制背面,然后绘制正面。

因此,获取RGBA纹理,纹理球体着色器,以及以下OpenGL配置

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

glCullFace(GL_FRONT);
glutSolidSphere(1.0,30,30);

glCullFace(GL_BACK);
glutSolidSphere(1.0,30,30);

值得一提的是,这个特定的纹理图像只包含0或1的alpha值。因此,由于邻近纹理的alpha值的插值,只有相对较少的片段接收介于0和1之间的alpha值。只有对于这些片段,渲染顺序才很重要。如果一个人接受这些片段的潜在渲染伪影,则可以通过启用深度测试来提高着色器的性能

glEnable(GL_DEPTH_TEST);

请注意,所有alpha值为0的纹理在本特定纹理图像中都是黑色的。实际上,该纹理图像中的颜色是“预乘”它们的alpha值。(这种颜色也称为“透明度加权”。)因此,对于这个特定的图像,我们实际上应该为预乘的颜色指定混合方程,以避免在混合方程中将颜色与其alpha值再次相乘。因此,着色器的改进(对于这个特定的纹理图像)是采用以下混合规范

glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
半透明球体通常用于徽标和预告片。

自定义颜色混合

[编辑 | 编辑源代码]

在结束本教程之前,我们不应该省略对所述技术的更实际的应用。左侧是带有半透明蓝色海洋的地球图像,我在维基共享资源上找到了它。有一些灯光(或轮廓增强)正在进行,我没有尝试复制。相反,我只是尝试用以下着色器来复制半透明海洋的基本思想,该着色器忽略了纹理贴图的RGB颜色,并根据alpha值用特定颜色替换它们

varying vec4 texCoords;
uniform sampler2D mytexture;

void main(void) {
    vec2 longitudeLatitude = vec2((atan(texCoords.y, texCoords.x) / 3.1415926 + 1.0) * 0.5,
                                  (asin(texCoords.z) / 3.1415926 + 0.5));

    gl_FragColor = texture2D(mytexture, longitudeLatitude);

    if (gl_FragColor.a > 0.5) // opaque 
    {
        gl_FragColor = vec4(0.0, 1.0, 0.0, 1.0); // opaque green
    }
    else // transparent 
    {
        gl_FragColor = vec4(0.0, 0.0, 0.5, 0.7); // semitransparent dark blue
    }
}

当然,为该着色器添加灯光和轮廓增强将会很有趣。也可以更改不透明的绿色颜色,以考虑纹理颜色,例如使用

gl_FragColor = vec4(0.5 * gl_FragColor.r, 2.0 * gl_FragColor.g, 0.5 * gl_FragColor.b, 1.0);

这通过将绿色分量乘以来强调绿色分量,并将红色和蓝色分量乘以来使它们变暗。但是,这会导致绿色过饱和,被钳制到最大强度。可以通过将绿色分量与最大强度1之间的差值减半来避免这种情况。这个差值是1.0 - gl_FragColor.g;它的一半是0.5 * (1.0 - gl_FragColor.g),与该缩减后的距离到最大强度对应的值为:1.0 - 0.5 * (1.0 - gl_FragColor.g)。因此,为了避免绿色过饱和,我们可以使用(而不是不透明的绿色颜色)

gl_FragColor = vec4(0.5 * gl_FragColor.r, 1.0 - 0.5 * (1.0 - gl_FragColor.g), 0.5 * gl_FragColor.b, 1.0);

在实践中,人们必须尝试各种可能性来进行这种颜色转换。为此,使用数值着色器属性(例如,上面一行中的因子0.5)特别有用,可以交互地探索各种可能性。

恭喜!您已经完成了这个相当长的教程。我们已经了解了

  • 如何将丢弃片段与alpha纹理贴图结合使用。
  • 如何使用alpha测试来实现相同的效果。
  • 如何将alpha纹理贴图用于混合。
  • 如何使用alpha纹理贴图来确定颜色。

进一步阅读

[编辑 | 编辑源代码]

如果您还想了解更多


< GLSL 编程/GLUT

除非另有说明,否则本页面上的所有示例源代码均为公有领域。
返回OpenGL 编程 - 照明部分 返回GLSL 编程 - GLUT 部分
华夏公益教科书