1. MIP贴图
MIP贴图的产生主要是为了解决渲染时的闪烁和性能耗费的问题,简单来说就是当需要屏幕上只需要处理一小部分片段时,但是这时候确加载了大的纹理图像,而大的纹理图像需要更多的变换操作这就带来了性能的消耗,而图片过大与屏幕显示区域不成正比,这样各种变换从采样到纹理应用的过程中就容易带来闪烁的效果。
MIP贴图可以解决掉上述问题,当载入某个纹理时,会为加载的这个纹理图像生成多个不同大小的纹理图像,最大的为纹理图像本身大小依次减半,直到最小的为纹理单元的大小,这样当处理小片段时可以选用小的纹理,大的片段可以选用大的纹理,这样就避免了闪烁和性能的耗费的问题了。但是使用MIP贴图之后加载的纹理图像需要的内存会多三分之一左右。
加载MIP贴图我们可以使用glTexImage函数而其中的level会指定使用那层的MIP层纹理,如果MIP贴图没有被使用那么久加载第零层,使用的话会加载所有MIP层的。我们也可以通过glTexParameteri限制MIP加载范围和使用范围。
2.MIP贴图过滤
没有使用MIP时,我们使用最邻近过滤或者线性过滤,都会给第零层的纹理贴图进行过滤,而使用了MIP贴图,我们也只会为第零层也就是基层纹理进行过滤,其它的MIP层会被忽略,但是我们可以通过下面过滤参数来指定那层需要过滤。 MIP贴图纹理的过滤.png3. 生成MIP层
上面说到了可以在加载已经构建好的MIP层,但有时我们是不需要处理小片段纹理贴图的,这时候加载所有的MIP层就有些不合适,这时候我们我们可以在使用某个纹理时在生成MIP层,我们可以使用
void glGaneraterMapMip(GLenum target)
需要注意的是在运行过程中生成MIP层要比记载已经创建好的MIP层慢很多,所以我们也要根据使用场景来确定使用哪种MIP层加载方式。
4. 各向滤性
对于纹理过滤我们可以使用基本的最邻近过滤和线性过滤来处理纹理图像显示时的变形,但有时候由于观察角度的改变物体各个方向上的排布也会发生改变,变的不那么规整,这时候如果纹理过滤的方式不做调整的话,就会失去一些真实性,各向滤性的纹理过滤方式会解决这个问题,它会根据物体各个方向上的不同的排布,而进行不同的采样,进而达到更准确的过滤。
5. 压缩纹理
我们可以通过glTexImage来加载纹理并对其压缩,其中internalFormat会指定压缩的格式,需要注意的是不同的硬件支持的压缩格式不同的,所以我们要在纹理压缩之前对齐做出兼容判断,glTexImage是当使用纹理时对其压缩加载,我们也可以预先加载纹理,然后保存本地在使用时直接加载预先压缩好的纹理,这时我们可以通过
void glCompressTexImage1d(GLenum target, GLint level, GLint internalformat , GLseizei width, GLint border , GLenum format, GLenum type, void *data)
void glCompressTexImage2d(GLenum target, GLint level, GLint internalformat , GLseizei width, GLseizei height, GLint border , GLenum format, GLenum type, void *data)
void glCompressTexImage3d(GLenum target, GLint level, GLint internalformat , GLseizei width, GLseizei height, GLseizei depth, GLint border , GLenum format, GLenum type, void *data)
这个函数和glTexImage函数参数一样,仅有的区别是internalformat参数必须指定一种本地支持的压缩格式。
需要注意的是经过压缩的纹理会占用更小的内存,对纹理的读写效率更高,但是弊端就是可能不能保证纹理的质量,这要依据使用场景选择是否要对纹理进行压缩。
6. 下面是一个纹理贴图的例子
GLShaderManager shaderManager;
GLMatrixStack modelViewMatrix;
GLMatrixStack projectionMatrix;
GLFrustum viewFrustum;
GLGeometryTransform transformPipeline;
GLBatch floorBatch;
GLBatch ceilingBatch;
GLBatch leftWallBatch;
GLBatch rightWallBatch;
GLfloat viewZ = -65.0f;
#define TEXTURE_BRICK 0
#define TEXTURE_FLOOR 1
#define TEXTURE_CEILING 2
#define TEXTURE_COUNT 3
GLuint textures[TEXTURE_COUNT];
const char *szTextureFiles[TEXTURE_COUNT] = { "brick.tga", "floor.tga", "ceiling.tga" };
void ProcessMenu(int value)
{
GLfloat fLargest;
GLint iLoop;
//便利纹理对象
for(iLoop = 0; iLoop < TEXTURE_COUNT; iLoop++)
{
//根据纹理对象句柄得到纹理对象
glBindTexture(GL_TEXTURE_2D, textures[iLoop]);
switch(value)
{
case 0:
//纹理缩小过滤,最邻近滤波纹理坐标会与最近纹理单元进行映射完成一一对应的关系,达到过滤的作用。
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
break;
case 1:
//纹理缩小过滤,线性过滤,纹理坐标会落在最近的纹理单元中心并对周围纹理单元进行加权平均运算得到一个插值纹理单元与纹理坐标映射完成一一对应达到过滤作用。
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
break;
case 2:
//MIP贴图缩小过滤,最邻近的MIP贴图最邻近过滤,对当前应用的MIP贴图的纹理图像的最邻近的纹理图像进行最邻近过滤。
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_NEAREST);
break;
case 3:
//MIP贴图缩小过滤,最邻近的MIP贴图线性过滤,对当前应用的MIP贴图的纹理图像的最邻近的纹理图像进行线性过滤
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_LINEAR);
break;
case 4:
//MIP贴图缩小过滤,线性的MIP贴图对最邻近过滤,对当前应用的MIP贴图的纹理图像的周围纹理图像进行加权平均运算得到插值的纹理图像然后对齐进行最邻近过滤。
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
break;
case 5:
//MIP贴图缩小过滤,线性的MIP贴图对最邻近过滤,对当前应用的MIP贴图的纹理图像的周围纹理图像进行加权平均运算得到插值的纹理图像然后对齐进行线性过滤。
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
break;
case 6:
//得到本地硬件支持的最大各向异性滤波数量
glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &fLargest);
//纹理缩小过滤,各向异性过滤,因纹理在各个方向可能存在不同的颜色和空间排布,所以针对方向上不同的纹理状态采取不同的采样方式,进而达到更准确的过滤
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, fLargest);
break;
case 7:
//关闭各向滤性过滤
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, 1.0f);
break;
}
}
// 重新渲染
glutPostRedisplay();
}
void SetupRC()
{
GLbyte *pBytes;
GLint iWidth, iHeight, iComponents;
GLenum eFormat;
GLint iLoop;
//设置背景颜色
glClearColor(0.0f, 0.0f, 0.0f,1.0f);
//初始化存储着色器
shaderManager.InitializeStockShaders();
//设置纹理对象个数,与纹理对象句柄集合
glGenTextures(TEXTURE_COUNT, textures);
for(iLoop = 0; iLoop < TEXTURE_COUNT; iLoop++)
{
//根据纹理句柄得到纹理对象
glBindTexture(GL_TEXTURE_2D, textures[iLoop]);
//读取图片像素
pBytes = gltReadTGABits(szTextureFiles[iLoop],&iWidth, &iHeight,
&iComponents, &eFormat);
//设置纹理参数
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);//纹理放大过滤,最邻近过滤
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);//纹理缩小过滤,最邻近过滤
//纹理环绕,这里是沿着S,T坐标进行纹理剪裁环绕,并对其坐标内的纹理单元采用上一个纹理单元的最后一行或者一列进行采样然后拼接,这样避免了纹理剪裁环绕模式因对齐纹理图像进行切割成小方块后而应用到几何体上出现缝隙的问题。
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
//对像素解包,这里用的tag图本身是没有紧密包装的,紧密包装是按照本地硬件寻址大小而对像素地址进行字节对齐的一种方式,方便一次取更多的数据提高读写效率
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
//加载纹理到内存,纹理单元为2D图像模式,设置使用基层纹理,设置压缩方式为对RGB进行压缩方式,也可设置为原图的压缩方式通过iComponents指针找到,设置纹理的宽和高,设置纹理数据的类型为无符号字节,像素数据的指针,因无法将基本颜色值写入到内存,这里通过像素的指针来指向基本颜色值
glTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGB, iWidth, iHeight, 0, eFormat, GL_UNSIGNED_BYTE, pBytes);
//为所有纹理生成2D纹理单元的MIP贴图层
glGenerateMipmap(GL_TEXTURE_2D);
// 释放图片像素
free(pBytes);
}
GLfloat z;
//批量管理顶点坐标,管理地板的顶点和纹理坐标,指定顶点坐标为三角带链接,指定顶点坐标和纹理对象数
floorBatch.Begin(GL_TRIANGLE_STRIP, 28, 1);
for(z = 60.0f; z >= 0.0f; z -=10.0f)
{
//设置地板的顶点的坐标和纹理坐标与顶点坐标的映射关系,并且存储到floorBatch容器中进行批量管理
floorBatch.MultiTexCoord2f(0, 0.0f, 0.0f);
floorBatch.Vertex3f(-10.0f, -10.0f, z);
floorBatch.MultiTexCoord2f(0, 1.0f, 0.0f);
floorBatch.Vertex3f(10.0f, -10.0f, z);
floorBatch.MultiTexCoord2f(0, 0.0f, 1.0f);
floorBatch.Vertex3f(-10.0f, -10.0f, z - 10.0f);
floorBatch.MultiTexCoord2f(0, 1.0f, 1.0f);
floorBatch.Vertex3f(10.0f, -10.0f, z - 10.0f);
}
//批量管理结束标识
floorBatch.End();
//批量管理顶点坐标,管理天花板的顶点和纹理坐标,指定顶点坐标为三角带链接,指定顶点坐标和纹理对象数
ceilingBatch.Begin(GL_TRIANGLE_STRIP, 28, 1);
for(z = 60.0f; z >= 0.0f; z -=10.0f)
{
//设置天花板的顶点的坐标和纹理坐标与顶点坐标的映射关系,并且存储到ceilingBatch容器中进行批量管理
ceilingBatch.MultiTexCoord2f(0, 0.0f, 1.0f);
ceilingBatch.Vertex3f(-10.0f, 10.0f, z - 10.0f);
ceilingBatch.MultiTexCoord2f(0, 1.0f, 1.0f);
ceilingBatch.Vertex3f(10.0f, 10.0f, z - 10.0f);
ceilingBatch.MultiTexCoord2f(0, 0.0f, 0.0f);
ceilingBatch.Vertex3f(-10.0f, 10.0f, z);
ceilingBatch.MultiTexCoord2f(0, 1.0f, 0.0f);
ceilingBatch.Vertex3f(10.0f, 10.0f, z);
}
//批量管理结束标识
ceilingBatch.End();
//批量管理顶点坐标,管理左边的墙的顶点和纹理坐标,指定顶点坐标为三角带链接,指定顶点坐标和纹理对象数
leftWallBatch.Begin(GL_TRIANGLE_STRIP, 28, 1);
for(z = 60.0f; z >= 0.0f; z -=10.0f)
{
//设置地板的顶点的坐标和纹理坐标与顶点坐标的映射关系,并且存储到leftWallBatch容器中进行批量管理
leftWallBatch.MultiTexCoord2f(0, 0.0f, 0.0f);
leftWallBatch.Vertex3f(-10.0f, -10.0f, z);
leftWallBatch.MultiTexCoord2f(0, 0.0f, 1.0f);
leftWallBatch.Vertex3f(-10.0f, 10.0f, z);
leftWallBatch.MultiTexCoord2f(0, 1.0f, 0.0f);
leftWallBatch.Vertex3f(-10.0f, -10.0f, z - 10.0f);
leftWallBatch.MultiTexCoord2f(0, 1.0f, 1.0f);
leftWallBatch.Vertex3f(-10.0f, 10.0f, z - 10.0f);
}
//批量管理结束标识
leftWallBatch.End();
//批量管理顶点坐标,管理右边的墙的顶点和纹理坐标,指定顶点坐标为三角带链接,指定顶点坐标和纹理对象数
rightWallBatch.Begin(GL_TRIANGLE_STRIP, 28, 1);
for(z = 60.0f; z >= 0.0f; z -=10.0f)
{
//设置地板的顶点的坐标和纹理坐标与顶点坐标的映射关系,并且存储到rightWallBatch容器中进行批量管理
rightWallBatch.MultiTexCoord2f(0, 0.0f, 0.0f);
rightWallBatch.Vertex3f(10.0f, -10.0f, z);
rightWallBatch.MultiTexCoord2f(0, 0.0f, 1.0f);
rightWallBatch.Vertex3f(10.0f, 10.0f, z);
rightWallBatch.MultiTexCoord2f(0, 1.0f, 0.0f);
rightWallBatch.Vertex3f(10.0f, -10.0f, z - 10.0f);
rightWallBatch.MultiTexCoord2f(0, 1.0f, 1.0f);
rightWallBatch.Vertex3f(10.0f, 10.0f, z - 10.0f);
}
//批量管理结束标识
rightWallBatch.End();
}
void ShutdownRC(void)
{
//删除纹理对象
glDeleteTextures(TEXTURE_COUNT, textures);
}
void SpecialKeys(int key, int x, int y)
{
// 通过前后建控制模型视图的z坐标
if(key == GLUT_KEY_UP)
viewZ += 0.5f;
if(key == GLUT_KEY_DOWN)
viewZ -= 0.5f;
//重新渲染
glutPostRedisplay();
}
void ChangeSize(int w, int h)
{
GLfloat fAspect;
if(h == 0)
h = 1;
//设置视口大小
glViewport(0, 0, w, h);
fAspect = (GLfloat)w/(GLfloat)h;
//进行透视投影
viewFrustum.SetPerspective(80.0f,fAspect,1.0,120.0);
//将投影矩阵载入投影堆栈中
projectionMatrix.LoadMatrix(viewFrustum.GetProjectionMatrix());
//设置管线管理模型视图矩阵堆栈和投影矩阵堆栈
transformPipeline.SetMatrixStacks(modelViewMatrix, projectionMatrix);
}
void RenderScene(void)
{
//清理颜色缓冲区
glClear(GL_COLOR_BUFFER_BIT);
//推入一个单位矩阵到模型视图矩阵堆栈当中
modelViewMatrix.PushMatrix();
//模型视图矩阵变换,沿z轴移动变换
modelViewMatrix.Translate(0.0f, 0.0f, viewZ);
//使用交换存储着色器可以将纹理坐标应用到几何体上,这里transformPipeline.GetModelViewProjectionMatrix()将投影矩阵转换成单元的模型视图投影矩阵,设置一重纹理贴图
shaderManager.UseStockShader(GLT_SHADER_TEXTURE_REPLACE, transformPipeline.GetModelViewProjectionMatrix(), 0);
//得到地板纹理对象
glBindTexture(GL_TEXTURE_2D, textures[TEXTURE_FLOOR]);
//渲染地板
floorBatch.Draw();
//得到天花板纹理对象
glBindTexture(GL_TEXTURE_2D, textures[TEXTURE_CEILING]);
//渲染天花板
ceilingBatch.Draw();
//得到墙纹理对象
glBindTexture(GL_TEXTURE_2D, textures[TEXTURE_BRICK]);
//渲染左边的墙
leftWallBatch.Draw();
//渲染右边的墙
rightWallBatch.Draw();
//弹出模型视图矩阵,每次重新渲染要把结果给到ChangeSize中的管线进行管理
modelViewMatrix.PopMatrix();
// 双缓冲渲染时交换前后台渲染数据
glutSwapBuffers();
}
int main(int argc, char *argv[])
{
gltSetWorkingDirectory(argv[0]);
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB);
glutInitWindowSize(800, 600);
glutCreateWindow("Anisotropic Tunnel");
glutReshapeFunc(ChangeSize);
glutSpecialFunc(SpecialKeys);
glutDisplayFunc(RenderScene);
//创建菜单管理
glutCreateMenu(ProcessMenu);
//创建各个菜单管理的操作
glutAddMenuEntry("GL_NEAREST",0);
glutAddMenuEntry("GL_LINEAR",1);
glutAddMenuEntry("GL_NEAREST_MIPMAP_NEAREST",2);
glutAddMenuEntry("GL_NEAREST_MIPMAP_LINEAR", 3);
glutAddMenuEntry("GL_LINEAR_MIPMAP_NEAREST", 4);
glutAddMenuEntry("GL_LINEAR_MIPMAP_LINEAR", 5);
glutAddMenuEntry("Anisotropic Filter", 6);
glutAddMenuEntry("Anisotropic Off", 7);
//菜单管理的事件触发方式
glutAttachMenu(GLUT_RIGHT_BUTTON);
GLenum err = glewInit();
if (GLEW_OK != err) {
fprintf(stderr, "GLEW Error: %s\n", glewGetErrorString(err));
return 1;
}
SetupRC();
glutMainLoop();
//删除纹理对象
ShutdownRC();
return 0;
}
网友评论