跳转到内容

GLSL 编程/GLUT/纹理球体

来自维基教科书,开放世界中的开放书籍
从阿波罗 17 号看到的地球。地球的形状接近一个相当光滑的球体。

本教程介绍了纹理映射

它是关于 OpenGL 2.x 中 GLSL 着色器纹理的一系列教程中的第一个。在本教程中,我们从球体上的单个纹理贴图开始。更具体地说,我们将地球表面的图像映射到球体上。在此基础上,后续教程将涵盖诸如纹理表面的照明、透明纹理、多纹理、光泽映射等主题。

一个近似球体的三角形网格。
地球表面的图像。水平坐标表示经度,垂直坐标表示纬度。

纹理映射

[编辑 | 编辑源代码]

“纹理映射”(或“纹理化”)的基本思想是将图像(即“纹理”或“纹理贴图”)映射到三角形网格上;换句话说,将平面图像放到三维形状的表面上。

为此,定义了“纹理坐标”,它们只是指定纹理(即图像)中的位置。水平坐标正式称为S,垂直坐标称为T。但是,通常将它们称为xy。在动画和建模工具中,纹理坐标通常称为UV

为了将纹理图像映射到网格,网格的每个顶点都赋予一对纹理坐标。(这个过程(以及结果)有时被称为“UV 映射”,因为每个顶点都映射到 UV 空间中的一个点。)因此,每个顶点都映射到纹理图像中的一个点。然后可以为三角形中三个顶点之间的每个点插值顶点的纹理坐标,从而网格中所有三角形的每个点都可以具有一个(插值)纹理坐标对。这些纹理坐标将网格的每个点映射到纹理贴图中的特定位置,从而映射到该位置的颜色。因此,渲染纹理映射网格包括对所有可见点执行两个步骤:插值纹理坐标以及在插值纹理坐标指定的位置查找纹理图像的颜色。

在 OpenGL 中,任何有效的浮点数都是有效的纹理坐标。但是,当 GPU 被要求查找纹理图像的像素(或“纹素”(例如使用下面描述的“texture2D”指令)时,它将在内部将纹理坐标映射到 0 到 1 之间的范围,其方式取决于导入纹理时指定的“包装模式”:包装模式“repeat”基本上使用纹理坐标的小数部分来确定 0 到 1 之间的纹理坐标。另一方面,包装模式“clamp”将纹理坐标钳制到此范围。然后,使用 0 到 1 之间的这些内部纹理坐标来确定纹理图像中的位置: 指定纹理图像的左下角; 右下角; 左上角;等等。

纹理球体

[编辑 | 编辑源代码]

要将地球表面的图像映射到球体上,首先要加载图像。为此,请使用 OpenGL 编程教程 06中解释的 SOIL。

  glActiveTexture(GL_TEXTURE0);
  GLuint texture_id = SOIL_load_OGL_texture
    (
     "Earthmap720x360_grid.jpg",
     SOIL_LOAD_AUTO,
     SOIL_CREATE_NEW_ID,
     SOIL_FLAG_INVERT_Y | SOIL_FLAG_TEXTURE_REPEATS
     );

SOIL_FLAG_TEXTURE_REPEATS将在使用 [0, 1] 之外的纹理坐标时使纹理重复自身。

顶点着色器

attribute vec3 v_coord;
varying vec4 texCoords;
uniform mat4 m,v,p;

void main(void) {
    mat4 mvp = p*v*m;
    gl_Position = mvp * vec4(v_coord, 1.0);
    texCoords = vec4(v_coord, 1.0);
}

片段着色器

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));
        // processing of the texture coordinates;
        // this is unnecessary if correct texture coordinates are specified by the application

    gl_FragColor = texture2D(mytexture, longitudeLatitude);
        // look up the color of the texture image specified by the uniform "mytexture"
        // at the position specified by "longitudeLatitude.x" and
        // "longitudeLatitude.y" and return it in "gl_FragColor"
}

如果一切顺利,纹理图像现在应该出现在球体上。恭喜!

它是如何工作的

[编辑 | 编辑源代码]

由于许多技术使用纹理映射,因此了解这里发生了什么非常有益。因此,让我们回顾一下着色器代码。

球体对象的顶点来自 FreeGLUT glutSolidSphere 函数。我们将在片段着色器中需要它们,以便将它们转换为纹理图像空间中的纹理坐标。

然后,顶点着色器将每个顶点的纹理坐标写入 varying 变量 texCoords。对于三角形的每个片段(即每个覆盖的像素),对该 varying 在三个三角形顶点处的值进行插值(参见 “光栅化”中的描述),并将插值纹理坐标传递给片段着色器。然后,片段着色器使用它们在由 uniform mytexture 指定的纹理图像中查找纹理空间中插值位置的颜色,并将该颜色返回到 gl_FragColor 中,然后将其写入帧缓冲区并在屏幕上显示。

在这种情况下,我们以算法方式生成纹理坐标,但通常它们是通过您的 3D 建模器指定的,并作为额外的顶点属性传递。

为了理解其他教程中介绍的更复杂的纹理映射技术,您必须对这些步骤有一个很好的了解。

重复和移动纹理

[编辑 | 编辑源代码]

在某些 3D 框架中,您可能已经遇到了平铺偏移参数,每个参数都有一个x和一个y分量。这些参数允许您重复纹理(通过缩小纹理坐标空间中的纹理图像)并在表面上移动纹理图像(通过在纹理坐标空间中偏移它)。为了再现这种行为,必须定义另一个 uniform。

uniform vec4 mytexture_ST; // tiling and offset parameters

我们可以为每个纹理指定一个这样的 vec4 uniform。(记住:“S”和“T”是纹理坐标的正式名称,它们通常称为“U”和“V”或“x”和“y”。)此 uniform 在 mytexture_ST.xmytexture_ST.y 中保存平铺参数的xy分量,而偏移参数的xy分量存储在 mytexture_ST.wmytexture_ST.z 中。uniform 应该像这样使用

gl_FragColor = texture2D(mytexture, mytexture_ST.xy * texCoords.xy + mytexture_ST.zw);

这使得着色器表现得像内置着色器。在其他教程中,为了使着色器代码更简洁,通常不实现此功能。

为了完整起见,以下是具有此功能的新片段着色器代码

varying vec4 texCoords;
uniform sampler2D mytexture;
uniform vec4 mytexture_ST; // tiling and offset parameters

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

    // Apply tiling and offset
    vec2 texCoordsTransformed = longitudeLatitude * mytexture_ST.xy + mytexture_ST.zw;

    gl_FragColor = texture2D(mytexture, texCoordsTransformed);
}

例如,您可以尝试复制所有大陆(水平缩放 2x 以查看两次纹理 - 确保您的纹理是GL_REPEAT的),并缩小北极(从 -0.05 垂直开始以缩小顶部)

  glUniform4f(uniform_mytexture_ST, 2,1, 0,-.05);

您已经完成了最重要的教程之一。我们已经了解了

  • 如何导入纹理图像以及如何将其附加到着色器的纹理属性。
  • 顶点着色器和片段着色器如何协同工作将纹理图像映射到网格上。
  • 纹理的平铺和偏移参数如何工作以及如何实现它们。

进一步阅读

[编辑 | 编辑源代码]

如果您想了解更多

  • 关于顶点着色器和片段着色器的数据流进出(即顶点属性、varying 等),您应该阅读 “OpenGL ES 2.0 管道”的描述。
  • 关于片段着色器中不同变量的插值,您应该阅读关于 “光栅化” 的讨论。


< GLSL 编程/GLUT

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