GLSL 编程/GLUT/纹理球体
本教程介绍了纹理映射。
它是关于 OpenGL 2.x 中 GLSL 着色器纹理的一系列教程中的第一个。在本教程中,我们从球体上的单个纹理贴图开始。更具体地说,我们将地球表面的图像映射到球体上。在此基础上,后续教程将涵盖诸如纹理表面的照明、透明纹理、多纹理、光泽映射等主题。
“纹理映射”(或“纹理化”)的基本思想是将图像(即“纹理”或“纹理贴图”)映射到三角形网格上;换句话说,将平面图像放到三维形状的表面上。
为此,定义了“纹理坐标”,它们只是指定纹理(即图像)中的位置。水平坐标正式称为S
,垂直坐标称为T
。但是,通常将它们称为x
和y
。在动画和建模工具中,纹理坐标通常称为U
和V
。
为了将纹理图像映射到网格,网格的每个顶点都赋予一对纹理坐标。(这个过程(以及结果)有时被称为“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.x
和 mytexture_ST.y
中保存平铺参数的x和y分量,而偏移参数的x和y分量存储在 mytexture_ST.w
和 mytexture_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 管道”的描述。
- 关于片段着色器中不同变量的插值,您应该阅读关于 “光栅化” 的讨论。
返回 OpenGL 编程 - 光照部分 | 返回 GLSL 编程 - GLUT 部分 |