8.纹理
纹理可以理解为图像,不过我们常见的是**.jpg、.png等图片格式,而纹理一般为.tga**格式。 无论.jpg、.png还是.tga格式都是压缩类型。
- .jpg用有损压缩方式去除冗余的图像和彩色数据,在获得极高的压缩率的同时能展现十分丰富生动的图像,即可以用较少的磁盘空间得到较好的图片质量
- .png是一种无损压缩的位图片形格式
- .tga格式是计算机上应用最广泛的图象格式。在兼顾了BMP的图象质量的同时又兼顾了JPEG的体积优势。并且还有自身的特点:通道效果、方向性。在CG领域常作为影视动画的序列输出格式,因为兼具体积小和效果清晰的特点。
纹理函数
- 改变像素存储方式
void glPixelStorei (GLenum pname, GLint param)
- 恢复像素存储方式
void glPixelStoref (GLenum pname, GLfloat param)
示例:
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
参数1:指定OpenGL如何从数据缓存区中解包图像数据,GL_UNPACK_ALIGNMENT 指内存中每个像素行起点的排列请求,允许设置为1 (byte排列)、2(排列为偶数byte的行)、4(字word排列列)、8(行从双字节边界开始)
参数2:表示参数GL_UNPACK_ALIGNMENT设置的值
- 把颜色缓存区内容作为像素图直接读取
void glReadPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, GLvoid *pixels)
参数1、2:指定从帧缓冲区读取的第一个像素的窗口坐标
参数3、4:指定像素矩形的尺寸。 一个宽度和高度对应于单个像素
参数5:format,OpenGL的像素格式,参考下方 "OpenGL像素格式" 表格
参数6:type,解释参数pixels指向的数据,告诉OpenGL使用缓冲区中的什么数据类型来存储颜色分量,参考下方 "像素数据的数据类型" 表格
参数7:pixels,返回像素数据,指向图形数据的指针。
- 指定读取的缓存
glReadBuffer(GLenum mode)
- 指定写入的缓存
glWriteBuffer(GLenum mode)
OpenGL像素格式
常量 | 描述 |
---|---|
GL_RGB | 按照红、绿、蓝顺序排列的颜色 |
GL_RGBA | 按照红、绿、蓝、Alpha顺序排列的颜色 |
GL_BGR | 按照蓝、绿、红顺序排列的颜色 |
GL_BGRA | 按照蓝、绿、红、Alpha顺序排列的颜色 |
GL_RED | 每个像素只包含一个红色分量 |
GL_GREEN | 每个像素只包含一个绿色分量 |
GL_BLUE | 每个像素只包含一个蓝色分量 |
GL_RG | 每个像素依次包含一个红色和绿色的分量 |
GL_RED_INTEGER | 每个像素包含了一个整数形式的红色分量 |
GL_GREEN_INTEGER | 每个像素包含了一个整数形式的绿色分量 |
GL_BLUE_INTEGER | 每个像素包含了一个整数形式的蓝色分量 |
GL_RG_INTEGER | 每个像素包依次含了一个整数形式的红色、绿色分量 |
GL_RGB_INTEGER | 每个像素包依次含了一个整数形式的红色、绿色、蓝色分量 |
GL_RGBA_INTEGER | 每个像素包依次含了一个整数形式的红色、绿色、蓝色、Alpha分量 |
GL_BGR_INTEGER | 每个像素包依次含了一个整数形式的蓝色、绿色、红色分量 |
GL_BGRA_INTEGER | 每个像素包依次含了一个整数形式的蓝色、绿色、红色、Alpha分量 |
GL_STENCIL_INDEX | 每个像素只包含一个模版值 |
GL_DEPTH_COMPONENT | 每个像素值包含了一个深度值 |
GL_DEPTH_STENCIL | 每个像素包含一个深度值和一个模版值 |
像素数据的数据类型
常量 | 描述 |
---|---|
GL_UNSIGNED_BYTE | 每种颜色分量都是一个8位无符号整数 |
GL_BYTE | 8位有符号整数 |
GL_UNSIGNED_SHORT | 16位无符号整数 |
GL_SHORT | 16位有符号整数 |
GL_UNSIGNED_INT | 32位无符号整数 |
GL_INT | 32位有符号整数 |
GL_FLOAT | 单精度浮点数 |
GL_HALF_FLOAT | 半精度浮点数 |
GL_UNSIGNED_BYTE_3_3_2 | 包装的RGB值 |
GL_UNSIGNED_BYTE_2_3_3_REV | 包装的RGB值 |
GL_UNSIGNED_SHORT_5_6_5 | 包装的RGB值 |
GL_UNSIGNED_SHORT_5_6_5_REV | 包装的RGB值 |
GL_UNSIGNED_SHORT_4_4_4_4 | 包装的RGB值 |
GL_UNSIGNED_SHORT_4_4_4_4_REV | 包装的RGB值 |
GL_UNSIGNED_SHORT_5_5_5_1 | 包装的RGB值 |
GL_UNSIGNED_SHORT_1_5_5_5_REV | 包装的RGB值 |
GL_UNSIGNED_INT_8_8_8_8 | 包装的RGB值 |
GL_UNSIGNED_INT_8_8_8_8_REV | 包装的RGB值 |
GL_UNSIGNED_INT_10_10_10_2 | 包装的RGB值 |
GL_UNSIGNED_INT_2_10_10_10_REV | 包装的RGB值 |
GL_UNSIGNED_INT_24_8 | 包装的RGB值 |
GL_UNSIGNED_INT_10F_11F_REV | 包装的RGB值 |
GL_FLOAT_24_UNSIGNED_INT_24_8_REV | 包装的RGB值 |
- 载入纹理
// 载入一维纹理(不知道什么状况使用)
void glTexImage1D(GLenum target,GLint level,GLint
internalformat,GLsizei width,GLint border,GLenum
format,GLenum type,void *data);
// 载入二维纹理(常用)
void glTexImage2D(GLenum target,GLint level,GLint
internalformat,GLsizei width,GLsizei height,GLint
border,GLenum format,GLenum type,void * data);
// 载入三维纹理
void glTexImage3D(GLenum target,GLint level,GLint internalformat,GLSizei width,GLsizei height,GLsizei depth,GLint border,GLenum format,GLenum type,void *data);
参数:
target:纹理类型 `GL_TEXTURE_1D`、`GL_TEXTURE_2D`、`GL_TEXTURE_3D`。
Level:mip贴图层次指,一般我们都把这个参数设置为0。
internalformat参数:纹理单元存储的颜色成分(从读取像素图是获得)。
width、height、depth参数:指加载纹理理的宽度、高度、深度。注意!这些值必须是 2的整数次⽅。(这是因为OpenGL旧版本上的遗留下的一个要求。当然现在已经可以支持不不是2的整数次方。但是开发者们还是习惯使用以2的整数次方去设置这些参数。)
border参数:允许为纹理贴图指定一个边界宽度。
format参数:OpenGL的像素格式
type:像素数据的数据类型(GL_UNSIGNED_BYTE,每个颜色分量都是一个8位无符号整数)
data参数:指向纹理图像数据的指针
- 从tga文件载入纹理
GLbyte *gltReadTGABits(const char *szFileName, GLint *iWidth, GLint *iHeight, GLint *iComponents, GLenum *eFormat, GLbyte *pData = NULL);
szFileName:纹理文件名称
iWidth:文件宽度地址
iHeight:文件高度地址
iComponents:文件组件地址
eFormat:文件格式地址
pData:pBits,指向图像数据的指针
- 更新纹理
// 更新一维纹理
void glTexSubImage1D(GLenum target,GLint level,GLint xOffset,GLsizei width,GLenum
format,GLenum type,const GLvoid *data);
// 更新二维纹理
void glTexSubImage2D(GLenum target,GLint level,GLint xOffset,GLint yOffset,GLsizei
width,GLsizei height,GLenum format,GLenum type,const GLvoid *data);
// 更新三维纹理
void glTexSubImage3D(GLenum target,GLint level,GLint xOffset,GLint yOffset,GLint
zOffset,GLsizei width,GLsizei height,GLsizei depth,Glenum type,const GLvoid * data);
- 插入替换纹理
//一维
void glCopyTexSubImage1D(GLenum target,GLint level,GLint xoffset,GLint x,GLint y,GLsize
width);
//二维
void glCopyTexSubImage2D(GLenum target,GLint level,GLint xoffset,GLint yOffset,GLint x,
y,GLsizei width,GLsizei height);
//三维
void glCopyTexSubImage3D(GLenum target,GLint level,GLint xoffset,GLint yOffset,GLint
zOffset,GLint x,GLint y,GLsizei width,GLsizei height);
- 复制纹理
//一维
void glCopyTexImage1D(GLenum target,GLint level,GLenum
internalformt,GLint x,GLint y,GLsizei width,GLint border);
//二维
void glCopyTexImage2D(GLenum target,GLint level,GLenum
internalformt,GLint x,GLint y,GLsizei width,GLsizei
height,GLint border);
x,y 在颜色缓存区中指定了开始读取纹理数据的位置; 缓存区里的数据,是源缓存区通过glReadBuffer设置的。
注意:不存在glCopyTextImage3D ,因为我们⽆法从2D颜色缓存区中获取体积数据。
- 纹理单元
使⽤函数分配纹理标识
指定纹理标识的数量和指针(指针指向一个⽆符号整形数组,由纹理单元标识符填充)。 void glGenTextures(GLsizei n,GLuint * textTures);
绑定纹理状态
参数target:GL_TEXTURE_1D、GL_TEXTURE_2D、GL_TEXTURE_3D
参数texture:需要绑定的纹理标识
void glBindTexture(GLenum target,GLunit texture);
删除纹理标识
纹理单元以及纹理标识指针(指针指向一个无符号整形数组,由纹理单元标识符填充)。
void glDeleteTextures(GLsizei n,GLuint *textures);
测试纹理标识是否有效
如果texture是一个已经分配空间的纹理标识,那么这个函数会返回GL_TRUE,否则会返回GL_FALSE。
GLboolean glIsTexture(GLuint texture);
设置纹理单元为活跃状态(当前使用状态)
glActiveTexture (GLenum texture);
- 设置纹理参数
glTexParameterf(GLenum target,GLenum pname,GLFloat param);
glTexParameteri(GLenum target,GLenum pname,GLint param);
glTexParameterfv(GLenum target,GLenum pname,GLFloat *param);
glTexParameteriv(GLenum target,GLenum pname,GLint *param);
参数1:target,指定这些参数将要应用在哪个纹理模式上,⽐如GL_TEXTURE_1D、GL_TEXTURE_2D、GL_TEXTURE_3D。
参数2:pname,指定需要设置哪个纹理属性
参数3:param,设定特定的纹理属性的值
常见纹理属性参数
属性 | 意义 | 可选值 |
---|---|---|
GL_TEXTURE_MIN_FILTER(缩小过滤) | 指当纹理图象被使用到一个小于或等于它的形状上时(纹理图象中的多个像素会被应用到实际绘制时的一个像素) | 过滤模式 |
GL_TEXTURE_MAG_FILTER(放大过滤) | 指当纹理图象被使用到一个大于它的形状上时(纹理图象中的一个像素会被应用到实际绘制时的多个像素) | 过滤模式 |
GL_TEXTURE_S | 在纹理坐标X方向超过1.0的⽅向上的贴图模式 | 4种环绕模式 |
GL_TEXTURE_T | 在纹理坐标Y方向超过1.0的⽅向上的贴图模式 | 4种环绕模式 |
GL_TEXTURE_R | 在纹理坐标Z方向超过1.0的⽅向上的贴图模式 | 4种环绕模式 |
推荐纹理缩小时,使用邻近过滤;纹理放⼤时,使用线性过滤
- 过滤方式
GL_NEAREST(邻近过滤) 和 GL_LINEAR(线性过滤)
前者表示“使用纹理中坐标最接近的一个像素的颜色作为需要绘制的像素颜色”,
后者表示“使用纹理中坐标最接近的若干个颜色,通过加权平均算法得到需要绘制的像素颜色”。
前者只经过简单比较,需要运算较少,可能速度较快,
后者需要经过加权平均计算,其中涉及除法运算,可能速度较慢(但如果有专门的处理硬件,也可能两者速度相同)。
从视觉效果上看,前者效果较差,在一些情况下锯齿现象明显,后者效果会较好(但如果纹理图象本身比较大,则两者在视觉效果上就会比较接近)。
- 环绕模式
GL_REPEAT:对纹理的默认行为。重复纹理图像
GL_MIRRORED_REPEAT:重复纹理图像,但每次镜像放置
GL_CLAMP_TO_EDGE:纹理坐标被约束在0到1之间,超过的部分会重复纹理坐标的边缘,产生一种边缘拉伸的效果
GL_CLAMP_TO_BORDER:超出的坐标为用户指定的边缘颜色
纹理坐标
在绘制纹理映射场景时,不仅要给每个顶点定义几何坐标,而且也要定义纹理坐标。经过多种变换后,几何坐标决定顶点在屏幕上绘制的位置,而纹理坐标决定纹理图像中的哪一个像素赋予该顶点。纹理的像素在[0,1]范围的一个矩形内,当纹理坐标值在[0,1]之外的时候,会根据设置的环绕模式来分配像素。
简单示例 ----- 可旋转的金字塔状纹理物体
- 带纹理的物体
- 旋转
- 自定义光照
我们设置顶点可以使用坐标
void Vertex3f(GLfloat x, GLfloat y, GLfloat z);
也可以使用向量void Vertex3fv(M3DVector3f vVertex);
法线
///求坐标点法线
glNormal3f (GLfloat nx, GLfloat ny, GLfloat nz);
///求向量法线
m3dFindNormal(M3DVector3f result, const M3DVector3f point1, const M3DVector3f point2,
const M3DVector3f point3);
设置法线
void Normal3fv(M3DVector3f vNormal);
设置纹理坐标
void MultiTexCoord2f(GLuint texture, GLclampf s, GLclampf t);
参数1:texture,纹理层次,对于使用存储着色器来进行渲染,设置为0
参数2:s:对应顶点坐标中的x坐标
参数3:t:对应顶点坐标中的y
void MultiTexCoord2fv(GLuint texture, M3DVector2f vTexCoord);
注意:设置法线、纹理坐标、顶点坐标的顺序不能错。顶点的法线、绑定的纹理坐标都可以理解为对当前顶点的配置,结合OpenGL状态机理解,所以要些设置好法线、纹理坐标,再设置顶点,这样法线和纹理坐标才能正确设置在顶点上
压缩纹理
压缩纹理格式 | 基本内部格式 |
---|---|
GL_COMPRESSED_RGB | GL_RGB |
GL_COMPRESSED_RGBA | GL_RGBA |
GL_COMPRESSED_SRGB | GL_RGB |
GL_COMPRESSED_SRGB_ALPHA | GL_RGBA |
GL_COMPRESSED_RED | GL_RED |
GL_COMPRESSED_RG | GL_RG |
根据选择的压缩纹理格式,选择最快、最优、⾃⾏选择的算法方式选择压缩格式。
glHint(GL_TEXTURE_COMPRESSION_HINT,GL_FASTEST);
glHint(GL_TEXTURE_COMPRESSION_HINT,GL_NICEST);
glHint(GL_TEXTURE_COMPRESSION_HINT,GL_DONT_CARE);
判断纹理理是否被成功压缩
GLint comFlag;
glGetTexLevelParameteriv(GL_TEXTURE_2D,0,GL_TEXTURE_COMPRESSED,&comFlag)
加载压缩纹理
void glCompressedTexImage1D(GLenum target,GLint level,GLenum internalFormat,GLsizei
width,GLint border,GLsizei imageSize,void *data);
void glCompressedTexImage2D(GLenum target,GLint level,GLenum internalFormat,GLsizei
width,GLint heigth,GLint border,GLsizei imageSize,void *data);
void glCompressedTexImage3D(GLenum target,GLint level,GLenum internalFormat,GLsizei
width,GLsizei heigth,GLsizei depth,GLint border,GLsizei imageSize,void *data);
参数:
target:`GL_TEXTURE_1D`、`GL_TEXTURE_2D`、`GL_TEXTURE_3D`。
Level:指定所加载的mip贴图层次。一般我们都把这个参数设置为0。 internalformat:每个纹理单元中存储多少颜色成分。
width、height、depth参数:指加载纹理的宽度、高度、深度。注意!这些值尽量是2的整数次⽅。(这是因为旧版本上的遗留下的一个要求。当然现在已经可以支持不是2的整数次⽅。但是开发者们还是习惯使用以2的整数次方。)
border参数:允许为纹理贴图指定一个边界宽度。
Mip贴图
在一个动态场景中,当贴了纹理的物体做远离视点的运动时,屏幕像素与纹理像素之间的比率会变得非常低,因此纹理的采样率也会变得非常低。这样会产生渲染图像上的瑕疵,这是因为纹理数据的下采样(undersampling)的缘故.举个例子,有一面墙正在做远离观察者直到它在屏幕上变成一个像素的的运动,那么纹理采样的结果可能会在某个过渡点上发生突然的变化。
为了降低这个效果的影响,可以对纹理贴图进行提前滤波,并且将滤波后的图像存储为连续的低分辨率的版本(原始图像为全分辨率)。这就叫做mipmap。开启Mip贴图,因为生成更多的图像,会消耗更多内存。
OpenGL中使用Mip时,会自动的判断当前应当使用纹理贴图的哪个分辨率层级,这是基于被映射的物体的尺寸(像素为单位)来决定的。通过这种方法,我们可以根据纹理贴图的细节层次(level of detail),找到当前绘制到屏幕的最合适的图像;如果物体的图像变得更小,那么纹理贴图层次的尺寸也会减小。mipmap需要一些额外的计算和纹理存储的区域。不过如果我们不使用mipmap的话,纹理被映射到较小的运动物体上之后可能会产生闪烁的问题。
设置mip贴图基层glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_BASE_LEVEL,0);
设置mip贴图最大层glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_BASE_LEVEL,0);
Mip贴图是进行缩小过滤的时候使用的,仅下面4种时候才能使用:
- GL_NEAREST_MIPMAP_NEAREST:在最邻近Mip层上执行最邻近过滤.具有非常好的性能,并且闪烁现象非常弱
- GL_NEAREST_MIPMAP_LINEAR:在Mip层之间执行线性插补,并执行最邻近过滤。以消除他们之间的过滤痕迹。
- GL_LINEAR_MIPMAP_NEAREST:选择最邻近Mip层,并执行线性过滤.常常用于对游戏进行加速,它使用了高质量的线性过滤器
- GL_LINEAR_MIPMAP_LINEAR:在Mip层之间执行线性插补,并执行线性过滤,又称为三线性Mip贴图。纹理过滤的黄金准则,具有最高的精度
各向异性&各向同性
各向同性和各向异性是指物理性质在不同的方向进行测量得到的结论。如果各个方向的测量结果是相同的,说明其物理性质与取向无关,就称为各向同性。如果物理性质和取向密切相关,不同取向的测量结果迥异,就称为各向异性。
各向异性纹理过滤(Anisotropic texture filtering)并不是OpenGL核心规范中的一部分。但是它是一种被广泛使用的扩展。可以极大的提高纹理过滤操作的质量。
在几何图形上进行纹理贴图时,如果它的观察方向刚好和观察点垂直,那么这个过程时相当完美的。但当我们倾斜一个角度去看这个几何图形的时候,对周围纹理单元进行常规采样会导致一些纹理信息丢失(看上去显得模糊).为了更加逼真和准确的采样应该沿着包含纹理的平面方向进行延伸。各向异性纹理过滤就是考虑了这个观察角度的过滤方法。在Min纹理过滤或其他基本纹理过滤中我们都可以应用各向异性过滤。 各向异性过滤的使用:
//第一步:获取各向异性过滤的最大数量
GLfloat fLargest;
glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &fLargest);
//第二步:设置各向异性过滤
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, fLargest);
当fLargest = 1.0f时,表示各向同性过滤(即关闭各向异性过滤)
各向异性过滤所应用的数量越大,沿着最大变化方向所采样的纹理单元就越多。值1.0表示常规的纹理过滤(各向同性过滤).各向异性过滤会增加额外的工作,包括其他纹理单元。很可能会对性能造成影响。但是,在现在硬件上,应用这个特性对速度造成的影响应该不大。最重要的是,目前它已经成为流行游戏、动画和模拟程序的一个标准特性了。
简单示例 ---- 通道
GL_NEAREST GL_NEAREST_MIPMAP_NEAREST + 未使用各异同性 GL_NEAREST_MIPMAP_NEAREST + 各向异性
带地面倒影的球
绘制思路:
- 先绘制倒影。把原来的绘制Y轴翻转
modelViewMatrix.Scale(1.0f, -1.0f, 1.0f);
,然后根据小球到地板的距离调整倒影的Y轴位置modelViewMatrix.Translate(0.0f, 1.0f, 0.0f)
得到倒影。 - 绘制地板。开启颜色混合,设置混合方程
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
,绘制地板,使地板与倒影颜色混合,绘制完成后关闭颜色混合。 - 绘制小球。正常的绘制就好了。
代码资源见:Github