纹理过滤器
纹理图像映射到屏幕坐标系的图元中,很多时候不是和屏幕坐标系(像素)一一对应的,因此需要进行放大或缩小(尽量避免一个方向上放大一个方向上缩小,避免这个问题最好方法是不要使用扭曲的纹理坐标)以铺满对应的图元。纹理过滤器通过glTexParameteri函数来指定。放大或缩小图像切换点一般出现在缩放为0.0位置,但是采用了不同的纹理过滤器比如放大过滤器是GL_LINEAR并且缩小过滤器是GL_NEAREST_MIPMAP_LINEAR或GL_NEAREST_MIPMAP_NEAREST那么切换点就出现在0.5处,避免缩小的纹理看上去比放大的纹理图像更加锐利(清晰)。
拓展应用:
纹理过滤器的应用:
对字体文本使用纹理贴图字体库而不是位图,或对直线进行纹理映射,然后对纹理进行过滤后绘制可以实现,字体和直线的抗锯齿效果。
纹理变换的实现:
图像的缩放和旋转,对纹理映射到的多边形进行缩放旋转,就可以对纹理进行缩放旋转了,原因是多边形顶点上的纹理坐标是没有变化的,顶点位置变了纹理图像每帧都会光栅化纹理映射,所以也会随之变化。
1)放大纹理过滤器
//GL_NEAREST最近点采样块有明显锯齿,GL_LINEAR线性过滤效果较好
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
2)缩小纹理过滤器
//GL_NEAREST最近点采样块有明显锯齿,GL_LINEAR双线性采样效果较好
//#define GL_NEAREST_MIPMAP_NEAREST 0x2700
// 两个mipmap之间选择最近的纹理单元,采用最近点采样
//#define GL_LINEAR_MIPMAP_NEAREST 0x2701
// 两个mipmap之间进行计算双线性采样后,选择最近点
//#define GL_NEAREST_MIPMAP_LINEAR 0x2702
// 两个mipmap之间选择最近的纹理单元后,采用双线性采样
//#define GL_LINEAR_MIPMAP_LINEAR 0x2703
// 两个mipmap之间进行计算双线性采样,两个结果再进行线性匀和,也叫三线性采样
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
Mipmap多重细节纹理
mipmap会成倍开销纹理内存,且是在两片之间插值,消耗计算开销。
但在动态场景中,当一个纹理迅速地远离观察点而去,纹理图像必须随被投影的图像一起缩小,为了实现这个效果,必须用OGL对纹理图像进行mipmap线性过滤,恰当的缩小,使它在映射到物体表面时不会产生令人不快的视觉效果,例如物体移动时候产生闪烁或抖动等。物体放大时候,都是应用最大的纹理,不会进行mipmap的选择。
具体的过滤算法
1)它会根据被贴图的物体的大小(以像素为单位,在光栅化时选择)自动确定应该使用哪个纹理。当物体的图像变化时候,纹理图像也随之变化。
2)为了使用mipmap,必须提供全系列的大小为2的整数次方的纹理图像,其范围大小从nxm到1x1,较小的纹理图像一般是更高一级分辨率纹理图像的4个纹理单元的平均值,但是具体的每个层级的mipmap图像也可以是完全不同的(OGL并没有规定任何特定的方法来计算低分辨率的纹理图像)。
3)level参数的起点为0标识分辨率最高的图像,最小的为n。当uv坐标不在[0,n]之间时候,例如[0,m]之间,比如m>n(小于也可以),那么在u方向会产生m层纹理图像,在v方向也会产生m层纹理图像,但是uv会进行不同的组合,所以最终会产生mxm份的纹理图像拷贝(巨大的开销,
不需要mipmap的2.5D锁定视角游戏尽量不使用)。
为了使mipmap生效,需要选择一种恰当的过滤模式即可。
mimmap纹理图像的过滤插值,就应该是在已知的纹理图像中,和规定的过滤算法,得到uv指定的纹理图像的纹理图像拷贝输出(不用拷贝那么在纹理中进行 选择);如果是在纹理图像拷贝之间选择纹理,那么根据向上,向下,还是取中间插值得的新的纹理图像。
实际中的应用函数
实际中的应用一般指定源图像(最大分辨率图像即可)。
对于OGL 3.0和以后的版本,使用glGenerateMipmap它将为绑定到一个特定的纹理目标的当前纹理图像构建mipmap栈。
glGenerateMipmap(GLenum target)函数:
Specifies the target to which the texture whose mimaps to generate is bound. target must
be GL_TEXTURE_1D, GL_TEXTURE_2D, GL_TEXTURE_3D,GL_TEXTURE_1D_ARRAY, GL_TEXTURE_2D_ARRAY, GL_TEXTURE_CUBE_MAP,
or GL_TEXTURE_CUBE_MAP_ARRAY.
如果这些值保留位默认值,所创建的整个mipmap栈层级下降为只有一个纹理单元的纹理图像,每个连续层级创建过程中采用的过滤方法和实现相关的。
OGL 3.0以前的版本,使用glTexParameter*()把GL_GENERATE_MIPMAP设置为GL_TRUE来创建mipmap栈。OGL 3.0后不再支持GL_GENERATE_MIPMAP了。 如果是更早1.4之前的版本,只能手动的构建mipmap栈,而得不到OGL的协助,可以使用 OGL utility封装了对mipmap栈的操作,例如我们已经创建了第0层的mipmap,
可以使用gluBuild*DMipmaps()函数创建和定义一系列大小递减的mipmap,直到1x1,且指定的源纹理图像不是2的整数次方,或者纹理图像太大了也可以用该函数处理。
gluBuild*DMipmaps()函数
int APIENTRY gluBuild1DMipmaps (
GLenum target,
GLint components,
GLint width,
GLenum format,
GLenum type,
const void *data);
int APIENTRY gluBuild2DMipmaps (
GLenum target,
GLint components,
GLint width,
GLint height,
GLenum format,
GLenum type,
const void *data);
创建一系列的mipmap,并调用glTexImages*D()加载这些纹理图像,参数的含义和glTexImages*D()相同,如果成功则返回0.
随着对细节层控制的增加,如果只需要创建glBuild*DMipmaps()函数创建的整套mipmap的一个子集,那么使用gluBuild*DMipmapLevels函数处理即可,它是指定levels下面的,base到max指定的纹理层(mipmap其实是二维的纹理集合,u,v方向都要组合,所以levels下还有0到n层的纹理,因为摄像机会扁平等可能要levels下m
x n层的纹理)。
计算Mipmap层
对于那个mipmap层将作为一个特定多边形的纹理取决于纹理图像的大小和被贴图多边形的大小之间的缩放因子。层级 = log2(最大缩放因子) + LODbias, LODbias是glTexEnv*()函数设置的偏移细节层的细节常量,默认是0.0。缩放因子为负数时候,表示放大,缩放因子为正数时候表示缩小,如果有mipmap那么选择mipmap层指定的纹理层级,一个维度的纹理层级下面还有其它n维度的纹理组合。
Mipmap层的细节控制
在默认情况下,必须为各个维的每种分辨率都提供一个mipmap。
为了减少mipmap内存消耗和减少使用高分辨率纹理时产生的跳跃效果,可以通过指定mimap层的细节控制。
用glTexParameter*()函数指定,GL_TEXTURE_BASE_LEVEL用于指定一个维度下可以使用的最高分辨率的纹理层,GL_TEXTURE_MAX_LEVEL对应最小分辨率的纹理层。
GL_TEXTURE_MIN_LOD维度上mipmap纹理层级的最小值,GL_TEXTURE_MAX_LOD维度上mipmap纹理层级的最大值。小于GL_TEXTURE_MAX_LEVEL的GL_TEXTURE_MAX_LOD才会起作用。稍微大于GL_TEXTURE_BASE_LEVEL的GL_TEXTURE_MIN_LOD或稍微小于GL_TEXTURE_MAX_LEVEL的GL_TEXTURE_MAX_LOD能够最为有效的减少与mipmap层过渡相关的视觉效果(例如跳跃)。
示例:
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 2);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 5);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_LOD, 2.5);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LOD, 4.5);
实例:
#include <stdlib.h>
#include <GL/glut.h>
GLubyte mipmapImage32[32][32][3];
GLubyte mipmapImage16[16][16][3];
GLubyte mipmapImage8[8][8][3];
GLubyte mipmapImage4[4][4][3];
GLubyte mipmapImage2[2][2][3];
GLubyte mipmapImage1[1][1][3];
void makeImages(void)
{
int i, j;
for (i = 0; i < 32; i++) {
for (j = 0; j < 32; j++) {
mipmapImage32[i][j][0] = 255;
mipmapImage32[i][j][1] = 255;
mipmapImage32[i][j][2] = 0;
}
}
for (i = 0; i < 16; i++) {
for (j = 0; j < 16; j++) {
mipmapImage16[i][j][0] = 255;
mipmapImage16[i][j][1] = 0;
mipmapImage16[i][j][2] = 255;
}
}
for (i = 0; i < 8; i++) {
for (j = 0; j < 8; j++) {
mipmapImage8[i][j][0] = 255;
mipmapImage8[i][j][1] = 0;
mipmapImage8[i][j][2] = 0;
}
}
for (i = 0; i < 4; i++) {
for (j = 0; j < 4; j++) {
mipmapImage4[i][j][0] = 0;
mipmapImage4[i][j][1] = 255;
mipmapImage4[i][j][2] = 0;
}
}
for (i = 0; i < 2; i++) {
for (j = 0; j < 2; j++) {
mipmapImage2[i][j][0] = 0;
mipmapImage2[i][j][1] = 0;
mipmapImage2[i][j][2] = 255;
}
}
mipmapImage1[0][0][0] = 255;
mipmapImage1[0][0][1] = 255;
mipmapImage1[0][0][2] = 255;
}
void myinit(void)
{
glEnable(GL_DEPTH_TEST);
glDepthFunc(GL_LESS);
glShadeModel(GL_FLAT);
glTranslatef(0.0, 0.0, -3.6);
makeImages();
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
glTexImage2D(GL_TEXTURE_2D, 0, 3, 32, 32, 0,
GL_RGB, GL_UNSIGNED_BYTE, &mipmapImage32[0][0][0]);
glTexImage2D(GL_TEXTURE_2D, 1, 3, 16, 16, 0,
GL_RGB, GL_UNSIGNED_BYTE, &mipmapImage16[0][0][0]);
glTexImage2D(GL_TEXTURE_2D, 2, 3, 8, 8, 0,
GL_RGB, GL_UNSIGNED_BYTE, &mipmapImage8[0][0][0]);
glTexImage2D(GL_TEXTURE_2D, 3, 3, 4, 4, 0,
GL_RGB, GL_UNSIGNED_BYTE, &mipmapImage4[0][0][0]);
glTexImage2D(GL_TEXTURE_2D, 4, 3, 2, 2, 0,
GL_RGB, GL_UNSIGNED_BYTE, &mipmapImage2[0][0][0]);
glTexImage2D(GL_TEXTURE_2D, 5, 3, 1, 1, 0,
GL_RGB, GL_UNSIGNED_BYTE, &mipmapImage1[0][0][0]);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
GL_NEAREST_MIPMAP_NEAREST);
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL);
glEnable(GL_TEXTURE_2D);
//glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);//GL_NEAREST最近点采样块有明显锯齿,GL_LINEAR线性过滤效果较好
//GL_NEAREST最近点采样块有明显锯齿,GL_LINEAR双线性采样效果较好
//#define GL_NEAREST_MIPMAP_NEAREST 0x2700 // 两个mipmap之间选择最近的纹理单元,采用最近点采样
//#define GL_LINEAR_MIPMAP_NEAREST 0x2701 // 两个mipmap之间进行计算双线性采样后,选择最近点
//#define GL_NEAREST_MIPMAP_LINEAR 0x2702 // 两个mipmap之间选择最近的纹理单元后,采用双线性采样
//#define GL_LINEAR_MIPMAP_LINEAR 0x2703 // 两个mipmap之间进行计算双线性采样,两个结果再进行线性匀和
//glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
//gluBuild2DMipmapLevels();
//gluBuild3DMipmaps();
}
void display(void)
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glBegin(GL_QUADS);
GLfloat endCoordValue = 12.0f;
//http://my.oschina.net/sweetdark/blog/177812
glTexCoord2f(0.0, 0.0);
glVertex3f(-2.0, -1.0, 0.0);
glTexCoord2f(0.0, endCoordValue); //8.0
glVertex3f(-2.0, 1.0, 0.0);
glTexCoord2f(endCoordValue, endCoordValue);
glVertex3f(2000.0, 1.0, -6000.0);
glTexCoord2f(endCoordValue, 0.0);
glVertex3f(2000.0, -1.0, -6000.0);
glEnd();
glFlush();
}
void myReshape(int w, int h)
{
glViewport(0, 0, w, h);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluPerspective(60.0, 1.0*(GLfloat)w/(GLfloat)h, 1.0, 30000.0);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}
int main(int argc, char** argv)
{
glutInit(&argc, argv);
glutInitDisplayMode (GLUT_SINGLE | GLUT_RGB | GLUT_DEPTH);
glutInitWindowSize (500, 500);
glutCreateWindow (argv[0]);
myinit();
glutReshapeFunc (myReshape);
glutDisplayFunc(display);
glutMainLoop();
return 0; /* ANSI C requires main to return int. */
}