大多数时候我们会将一幅纹理图像应用到模型上,或多或少就像壁纸一样。有时候纹理还可以看成是数据,不是颜色值。纹理坐标提供了在图像和模型之间的映射关系。在我们着色器中处理使用纹理之前我们需要在我们的OpenGL应用程序中做一些设置步骤。在这一节中,我们将覆盖一下经典纹理用法案例。
OpenGL
在我们着色器中使用纹理的第一步就是需要创建一个OpenGL纹理对象。一个通用的用法就是我们同样需要从文件中加载图像数据来添加到纹理中,如果你不确定如何做这一步,那么请看这一页的介绍。
在着色器中需要设置一个一致变量来储存纹理单元。注意,着色器接收的是纹理单元而不是纹理名字。OpenGL提供了多个单元,因此我们在一个着色器中使用多个纹理。纹理单元有效数据是GL_MAX_COMBIEND_TEXTURE_IMAGE_UNITS。
让我们假定在着色器中定义一个叫做texUnit的一致变量。同样假定我们仅仅只有一个纹理,因此我们将绑定到纹理单元0.
在OpenGL应用程序这一端,在设置的步骤,我们需要得到一致变量texUnit的位置。到目前为止OpenGL是将texUnit定义成整型变量。因此,假定变量p存储GLSL程序的名字,下面的代码得到位置:
GLint texUnitLoc = glGetUniformLocation(p, “texUnit”);
如果提供给着色器的纹理单元总是跟我们在应用程序设置的一样。房子我们不得不经常修改纹理单元。为了设置变量texUnit为纹理单元0我们需要这么写:
glProgramUniformli(p, texUnitLoc, 0);
绘制实现绑定纹理到纹理单元。因此,如果我们假定texture1D是纹理单元(由glGenTextures创建)和纹理是2D,我们可以这么写:
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, texture1D);
后面我们将绑定到VAOs和调用glDraw*命令。
GLSL
现在,让我们回到GLSL这一端。首先必须定义一个变量texUnit。这个变量定义成sampler*类型。Samplers是一个不透明类型,比如说,它们不能像常规变量那样处理,它们的用法在函数中有限制,必须适当使用。采样器处理的纹理是在OpenGL应用程序端定义的,输入函数可以从纹理数据中返回值。每个纹理类型都有一个合适的采样类型,实际上,就像一个纹理模板GL_TEXTURE_2D,那么采样类型会sampler2D。
为了处理纹理我们需要两个元素:纹理单元,和纹理坐标。在这里有很多函数来处理纹理数据。其中的很多都是用来查询,其他一些提供纹理数据,让我们从一些查询开始。
一个通用的着色器可以处理纹理通过它们的大小。大小信息(宽度和高度)可以通过一致变量来提供给着色器。另外一个选项就是使用textureSize函数。这个函数返回的纹理在x, y, 和z分量的大小,而w是在纹理数组或者立方体纹理的索引。这个函数同样可以使用mipmap级数,在这种情况下上面所有的信息就是相对于查询mipmap级数信息。
纹理mipmap级数大小可以有textureQueryLevels来查询。这个函数可以返回在纹理中(从OpenGL4.3有效)有效的mipmap计算数目。
Mipmap级数,或者结合mipmap等级,可以在特定片段使用textureQueryLod获取。这个函数返回一个vec2其中的x分量表示将会被使用的等级和相应的比例。实际上x=3.25然后等级3将会贡献75%和等级4是25%。
我们可以这个动作,参考下面的代码:
#version 420
uniform sampler2D texUnit;
out vec4 outputF;
void main()
{
vec2 res = textureQueryLod(texUnit, VertexIn.texCoord.xy);
if(res.x == 0)
outputF = vec4(1.0, 0.0, 0.0, 0.0);
else if(res.x < 1)
outputF = vec4(0.0, 1.0, 0.0, 0.0);
else if(res.x < 2)
outputF = vec4(0.0, 0.0, 1.0, 0.0);
else if(res.x < 3)
outputF = vec4(0.0, 1.0, 1.0, 0.0);
else if(res.x < 4)
outputF = vec4(1.0, 0.0, 1.0, 0.0);
else if(res.x < 5)
outputF = vec4(1.0, 1.0, 0.0, 0.0);
else if(res.x < 6)
outputF = vec4(0.5, 1.0, 0.0, 0.0);
else if(res.x < 7)
outputF = vec4(0.0, 1.0, 0.5, 0.0);
}
结果就像下面的图示一样:
检索纹理数据
为了处理检索纹理数据给我们的模型着色我们使用两个函数:texture和texelFetch。第一个函数使用0到1的纹理坐标,或者纹理坐标是重复情况下的小数部分。这些都是相对坐标。取回,另外一个方面,使用整数纹理坐标,就像直线和像素列。不能执行线性过滤,一个单一的纹理数据被取回。
一旦我们得到纹理数据,我们可以输出作为最终的颜色,或者跟反射强度计算混合。下面的图展示了上面的情况(左边:取代,右边:混合)
纹理颜色同样也可以像素材质混合。实际上,方向光着色器结合纹理可以像下面这么写:
顶点着色器:纹理坐标被添加到顶点属性中。顶点着色器仅仅传递而已。
#version 330
layout (std140) uniform Matrices{
mat4 m_pvm;
mat4 m_viewModel;
mat3 m_normal;
};
layout(std140) uniform Lights{
vec3 l_dir;
};
in vec4 position;
in vec3 normal;
in vec2 texCoord;
out Data{
Vec3 normal;
Vec4 eye;
Vec2 texCoord;
}DataOut;
void main()
{
DataOut.eye = m_viewModel * position;
DataOut.normal = normalize(m_normal * normal);
DataOut.texCoord = texCoord;
gl_Position = m_pvm * position;
}
片段着色器:添加一个采样器。纹理坐标用来实验函数texture来检索纹理数据。
#version 330
Layout(std140) uniform Material{
Vec4 diffuse;
Vec4 ambient;
Vec4 specular;
Float shininess;
};
Layout(std140) uniform Lights{
Vec3 l_dir;
};
In Data{
Vec4 eye;
Vec3 normal;
Vec2 texCoord;
}DataIn;
Uniform sampler2D texUnit;
Out vec4 colorOut;
Void main()
{
Vec4 spec = vec4(0.0);
Vec3 n = normalize(DataIn.normal);
Vec3 e = normalize(DataIn.eye);
Float intensity = max(0.0, dot(n, l_dir));
If(intensity > 0.0)
{
Vec3 h = normalize(l_dir + e);
Float intSpec = max(0.0, dot(h, n));
Spec = specular * pow(intSpec, shininess);
}
Vec4 texColor = texture(texUnit, DataIn.texCoord);
Vec4 diffColor = intensity * diffuse * texColor;
Vec4 ambColor = ambient * texColor;
colorOut = max(diffColor + spec , ambColor);
}
注意,仅仅只有漫反射和环境光颜色跟纹理相混合。镜面光是不受纹理影响的。
源代码和Visual Studio 2010工程可以在这里下载。注意,你必须有DevlL才能运行这个demo。
注意
如何采样器被定义成数组,那么采样器必须使用整型索引来取得动态一致值。这就意味着在运行的着色器中的所有实例索引值必须是常量。
下面的代码是可以的,因为我们总是使用同一个索引。
uniform int anIndex;
uniform sampler2D myTexture[3];
in vec2 texCoord;
void main()
{
...
color = texture(myTexture[anIndex], texCoord);
....
}
下面我们呈现出一个如何错误的例子。我们想支持如果屏幕y坐标小于一个固定值让它旋转一个纹理,如果不是则选择另外一个纹理。
uniform int anIndex;
uniform sampler2D myTexture[3];
in vec2 texCoord;
void main()
{
...
if(gl_FragCoord.y > 100.0)
index = 0;
else
index = 1;
color = texture(myTexture[index], texCoord);
...
}