GLSL 编程/Blender/纹理球体
本教程介绍了 **纹理映射**。
它是 Blender 中关于在 GLSL 着色器中进行纹理化的一系列教程中的第一个。在本教程中,我们从球体上的单一纹理贴图开始。更具体地说,我们将地球表面的图像映射到一个球体上。在此基础上,后续教程将涵盖诸如纹理化表面的光照、透明纹理、多纹理、光泽映射等主题。
“纹理映射”(或“纹理化”)的基本思想是将图像(即“纹理”或“纹理贴图”)映射到三角形网格上;换句话说,将平面图像放到三维形状的表面上。
为此,定义了“纹理坐标”,它们简单地指定纹理(即图像)中的位置。水平坐标正式称为 S
,垂直坐标称为 T
。但是,通常将它们称为 x
和 y
。在动画和建模工具中,纹理坐标通常称为 U
和 V
。
为了将纹理图像映射到网格,网格的每个顶点都给定了一对纹理坐标。(这个过程(以及结果)有时被称为“UV 映射”,因为每个顶点都映射到 UV 空间中的一个点。)因此,每个顶点都映射到纹理图像中的一个点。然后,可以为三个顶点之间的任何三角形的每个点插值顶点的纹理坐标,从而网格所有三角形的每个点都可以具有一个(插值)纹理坐标对。这些纹理坐标将网格的每个点映射到纹理贴图中的特定位置,因此映射到该位置的颜色。因此,渲染纹理映射网格包括对所有可见点进行两个步骤:插值纹理坐标并在插值纹理坐标指定的位置查找纹理图像的颜色。
在 OpenGL 中,任何有效的浮点数都是有效的纹理坐标。但是,当 GPU 被要求查找纹理图像的像素(或“纹素”(例如使用下面描述的“texture2D”指令))时,它将在内部将纹理坐标映射到 0 到 1 之间的范围,具体方式取决于“环绕模式”。例如,环绕模式“重复”基本上使用纹理坐标的小数部分来确定 0 到 1 之间的纹理坐标。另一方面,环绕模式“钳位”将纹理坐标钳位到此范围。然后,这些 0 到 1 之间的内部纹理坐标用于确定纹理图像中的位置: 指定纹理图像的左下角; 右下角; 左上角;等等。OpenGL 的环绕模式对应于 Blender 中 **属性 > 纹理选项卡 > 图像映射** 下的设置。不幸的是,Blender 似乎没有设置 OpenGL 环绕模式,但它始终设置为“重复”。
要将左侧的地球表面图像映射到 Blender 中的球体上,您首先需要将此图像下载到您的计算机上:单击左侧的图像,直到您看到一个更大的版本,然后将其保存(通常使用右键单击)到您的计算机上(记住您将它保存到哪里)。然后切换到 Blender 并添加一个球体(在 **信息窗口** 中选择 **添加 > 网格 > UV 球体**),在 **3D 视图** 中选择它(通过右键单击),激活平滑着色(在 **3D 视图** 的 **工具架** 中,如果它未处于活动状态,请按 **t**),确保 **显示 > 着色:GLSL** 在 **3D 视图** 的 **属性** 中设置(如果它们未显示,请按 **n**),**并且**将 **3D 视图** 的 **视口着色** 切换到 **纹理**(**3D 视图** 中主菜单右侧的第二个图标)。现在(球体仍然处于选中状态)添加一个材质(在 **属性窗口 > 材质选项卡 > 新建** 中)。然后添加一个新的纹理(在 **属性窗口 > 纹理选项卡 > 新建** 中),为 **类型** 选择 **图像或电影**,然后单击 **图像 > 打开**。在文件浏览器中选择您的文件,然后单击 **打开图像**(或在文件浏览器中双击它)。图像现在应该出现在 **纹理选项卡** 的预览部分,Blender 应该将它放到 3D 视图中的球体上。
现在您应该确保 **属性窗口 > 纹理选项卡 > 映射** 中的 **坐标** 设置为 **生成**。这意味着我们的纹理坐标将设置为对象空间中的坐标。在任何建模工具中指定或生成纹理坐标(即 UV)是一个完全不同的主题,超出了本教程的范围。
使用这些设置,Blender 也会将纹理坐标发送到顶点着色器。(实际上,我们也可以使用 gl_Vertex
中的对象坐标,因为在这种情况下它们是相同的。)因此,我们可以编写一个接收纹理坐标并将其传递给片段着色器的顶点着色器。然后,片段着色器对四维纹理坐标进行一些计算以计算经度和纬度(缩放至 0 到 1 的范围),这将用作这里的纹理坐标。通常,此步骤是不必要的,因为纹理坐标应该已经正确地指定了在哪里查找纹理图像。(实际上,应该避免在片段着色器中对纹理坐标进行任何此类处理,以提高性能;这里我只是使用此技巧来避免设置适当的 UV 纹理坐标。)设置着色器的 Python 脚本可能是
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 = """
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);
// look up the color of the texture image specified
// by the uniform "textureUnit" at the position
// specified by "longitudeLatitude.x" and
// "longitudeLatitude.y" and return it in "gl_FragColor"
}
"""
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.setSampler('textureUnit', 0)
在 Python 脚本中:它将统一变量 textureUnity
设置为 0。这指定应该使用 **属性窗口 > 纹理选项卡** 中列表中的第一个纹理。值 1 将选择列表中的第二个纹理,等等。实际上,对于您在片段着色器中使用的每个 sampler2D
变量,您都必须使用如上所示的 setSampler
调用来设置其值。实际上,一个 sampler2D
统一变量指定了 GPU 的纹理单元。(纹理单元是硬件的一部分,负责查找和插值纹理图像中的颜色。)GPU 的纹理单元数量在内置常量 gl_MaxTextureUnits
中可用,它通常为 4 或 8。因此,片段着色器中可用的不同纹理图像的数量仅限于此数量。
如果一切顺利,当您按下 **p** 启动游戏引擎时,纹理图像现在应该正确地映射到球体上。(否则 Blender 会将其以不同的方式映射到球体上。)恭喜!
由于许多技术都使用纹理映射,因此理解这里发生的事情非常有益。因此,让我们回顾一下着色器代码。
Blender 球体对象的顶点带有属性数据 gl_MultiTexCoord0
,用于每个顶点,它指定纹理坐标,在我们这个特定的例子中,这些纹理坐标与属性 gl_Vertex
中的值相同,gl_Vertex
指定了物体空间中的位置。
然后,顶点着色器将每个顶点的纹理坐标写入到变量 texCoords
中。对于三角形的每个片段(即每个覆盖的像素),在三个三角形顶点处此变量的值会进行插值(参见 “光栅化” 中的描述),并将插值后的纹理坐标传递给片元着色器。在这个特定例子中,片元着色器在 longitudeLatitude
中计算新的纹理坐标。通常,这并不必要,因为应该在 Blender 中使用 UV 映射来指定正确的纹理坐标。然后,片元着色器使用纹理坐标在由 uniform textureUnit
指定的纹理图像中查找纹理空间中插值位置的颜色,并将此颜色返回到 gl_FragColor
中,然后将其写入帧缓冲区并在屏幕上显示。
为了理解其他教程中介绍的更复杂的纹理映射技术,您必须对这些步骤有一个很好的了解。
您已经学习完了最重要的教程之一。我们已经学习了
- 如何为纹理设置 Blender 对象。
- 如何导入纹理图像。
- 顶点着色器和片元着色器如何协同工作将纹理图像映射到网格上。
如果您想了解更多
- 关于顶点着色器和片元着色器之间的数据流进出(即顶点属性、变量等),您应该阅读 “OpenGL ES 2.0 管道” 的描述。
- 关于片元着色器中变量的插值,您应该阅读 “光栅化” 的讨论。