GLSL 编程/Blender/透明纹理
本教程涵盖了Alpha 纹理图的各种常见用途,即 RGBA 纹理图像,其 A(Alpha)分量指定了纹素的不透明度。
它将纹理球体教程的着色器代码与截面教程和透明度教程中介绍的概念相结合。
如果您还没有阅读这些教程,这将是一个很好的阅读机会。
让我们从截面教程中解释的丢弃片段开始。按照纹理球体教程中描述的步骤,将左侧的图像分配到具有以下着色器的球体的材质
import bge
cont = bge.logic.getCurrentController()
VertexShader = """
varying vec4 texCoords; // texture coordinates at this vertex
void main()
{
texCoords = gl_MultiTexCoord0; // in this case equal to gl_Vertex
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
}
"""
FragmentShader = """
uniform float cutoff;
varying vec4 texCoords;
// interpolated texture coordinates for this fragment
uniform sampler2D textureUnit;
// a small integer identifying a texture image
void main()
{
vec2 longitudeLatitude = vec2(
(atan(texCoords.y, texCoords.x) / 3.1415926 + 1.0) * 0.5,
1.0 - acos(texCoords.z) / 3.1415926);
// processing of the texture coordinates;
// this is unnecessary if correct texture coordinates
// are specified within Blender
gl_FragColor = texture2D(textureUnit, longitudeLatitude);
if (gl_FragColor.a < cutoff)
// alpha value less than user-specified threshold?
{
discard; // yes: discard this fragment
}
}
"""
mesh = cont.owner.meshes[0]
for mat in mesh.materials:
shader = mat.getShader()
if shader != None:
if not shader.isValid():
shader.setSource(VertexShader, FragmentShader, 1)
shader.setSampler('textureUnit', 0)
shader.setUniform1f('cutoff', cont.owner.get('cutoff'))
此脚本使用名为cutoff
的游戏属性来控制相同名称的统一变量。如果您启动游戏,Blender 应该在系统控制台中(可以通过帮助 > 切换系统控制台在信息窗口的菜单中获取)抱怨在以下行中缺少浮点数
shader.setUniform1f('cutoff', cont.owner.get('cutoff'))
因为我们还没有定义游戏属性。要做到这一点,请转到逻辑编辑器并单击添加游戏属性(如果未打开,请按n打开属性)。名称应为cutoff
,类型应为float
。将值设置为(任何略微远离 0 和 1 的值都可以)。
如果您现在启动游戏引擎,片段着色器应该读取 RGBA 纹理并比较 Alpha 值与游戏属性cutoff
中指定的阈值。如果 Alpha 值小于阈值,则片段将被丢弃,表面将显示为透明。
由于我们可以透过透明的部分,因此可以像截面教程中描述的那样停用背面剔除:在信息窗口的菜单中选择Blender 游戏引擎,然后(在球体的属性窗口的材质选项卡中)取消选中游戏设置 > 背面剔除。(注意,即使未在材质选项卡中选中透明度,通常的深度测试也会确保三角形遮挡是正确的。)
透明度教程描述了如何使用 Alpha 混合渲染半透明物体。将此与 RGBA 纹理相结合,得到以下片段着色器(顶点着色器与上面相同)
import bge
cont = bge.logic.getCurrentController()
VertexShader = """
varying vec4 texCoords; // texture coordinates at this vertex
void main()
{
texCoords = gl_MultiTexCoord0; // in this case equal to gl_Vertex
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
}
"""
FragmentShader = """
uniform float cutoff;
varying vec4 texCoords;
// interpolated texture coordinates for this fragment
uniform sampler2D textureUnit;
// a small integer identifying a texture image
void main()
{
vec2 longitudeLatitude = vec2(
(atan(texCoords.y, texCoords.x) / 3.1415926 + 1.0) * 0.5,
1.0 - acos(texCoords.z) / 3.1415926);
gl_FragColor = texture2D(textureUnit, longitudeLatitude);
}
"""
mesh = cont.owner.meshes[0]
for mat in mesh.materials:
shader = mat.getShader()
if shader != None:
if not shader.isValid():
shader.setSource(VertexShader, FragmentShader, 1)
shader.setSampler('textureUnit', 0)
mat.setBlending(bge.logic.BL_SRC_ALPHA,
bge.logic.BL_ONE_MINUS_SRC_ALPHA)
不幸的是,如果您停用背面剔除,即使启用了透明度 > Z 透明度(在属性窗口的材质选项卡中),您也会在 Blender 2.63 中看到严重的渲染伪像。显然,Blender 2.63 并不总是根据深度对所有三角形进行排序,这在这种情况下是一个问题。但是,将游戏设置 > Alpha 混合更改为Alpha 排序将告诉 Blender 对该材质的所有三角形进行排序,从而纠正该问题。(这应该只在需要时使用,因为排序成本很高,通常没有必要)
请注意,在该特定纹理图像中,所有 Alpha 值为 0 的纹素都是黑色的。实际上,该纹理图像中的颜色已与其 Alpha 值“预乘”。(这种颜色也称为“不透明度加权”。)因此,对于该特定图像,我们实际上应该指定预乘颜色的混合方程,以避免在混合方程中将颜色与其 Alpha 值再次相乘。因此,着色器(对于该特定纹理图像)的改进是使用以下混合规范
mat.setBlending(bge.logic.BL_ONE, bge.logic.BL_ONE_MINUS_SRC_ALPHA)
我们不应该在没有对所介绍技术的更实际应用的情况下结束本教程。左侧是地球的图像,带有半透明的蓝色海洋,我在 Wikimedia Commons 上找到的。有一些照明(或轮廓增强),我没有尝试再现。相反,我只尝试使用以下着色器再现半透明海洋的基本思想,该着色器忽略了纹理图的 RGB 颜色,并根据 Alpha 值用特定颜色替换它们
import bge
cont = bge.logic.getCurrentController()
VertexShader = """
varying vec4 texCoords; // texture coordinates at this vertex
void main()
{
texCoords = gl_MultiTexCoord0; // in this case equal to gl_Vertex
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
}
"""
FragmentShader = """
uniform float cutoff;
varying vec4 texCoords;
// interpolated texture coordinates for this fragment
uniform sampler2D textureUnit;
// a small integer identifying a texture image
void main()
{
vec2 longitudeLatitude = vec2(
(atan(texCoords.y, texCoords.x) / 3.1415926 + 1.0) * 0.5,
1.0 - acos(texCoords.z) / 3.1415926);
gl_FragColor = texture2D(textureUnit, 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
}
}
"""
mesh = cont.owner.meshes[0]
for mat in mesh.materials:
shader = mat.getShader()
if shader != None:
if not shader.isValid():
shader.setSource(VertexShader, FragmentShader, 1)
shader.setSampler('textureUnit', 0)
mat.setBlending(bge.logic.BL_SRC_ALPHA,
bge.logic.BL_ONE_MINUS_SRC_ALPHA)
如果您停用背面剔除,您会注意到 Blender 2.63 中存在渲染伪像。希望这个问题会在未来的版本中得到改善。
当然,将照明和轮廓增强添加到此着色器会很有趣。还可以更改不透明的绿色以考虑纹理颜色,例如使用
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 纹理图来确定颜色。
如果您仍然想知道更多