纹理的基础知识
2D 纹理
2d纹理是OpenGlES中最基础和普遍的一种纹理结构。一个2d纹理,就是图片的数据的一个二维数组。纹理中每一个独立的数据单元被称为texels
(“texture pixels”的缩写)。OpenGLES中纹理图像数据可变被许多种不同的基本格式描绘。
根据图像的基本类型和图像的数据类型,决定了图像中的每一个texel。
当渲染2D纹理是,纹理的坐标将成为索引。2D纹理的坐标轴别是(s,t)或者是(u,v)。这个坐标轴是规范化的,即坐标的大小在0.0~1.0之间。取纹理图像的左下角为坐标原点(0.0,0.0)
坐标超过[0.0,1.0]的范围是允许的,而对与超出范围的内容的操作,取决于纹理的包装方式(wrapping mode)
立方体贴图纹理(Cubemap Texttures
)
OpenGL ES 3.0 支持立方体贴图纹理。最基本的,一个立方体纹理由6个独立的2D纹理面组成。每一个2D纹理都是立方体的一个平面。虽然立方体贴图在3D渲染中有了多元化的进步,但是最普遍的还是被用在环境贴图中。典型的,在环境贴图中,将一台摄像机放置场景的中心点,拍摄6个方位的图像并保存。
立体贴图中的Texels通过使用一个3D的向量(s,t,r)来定位。定位时,先根据r确定是6个平面的哪一个平面,在根据(s,t)确定平面上的一点。
立体贴图纹理的每一个平面都必须是正方形的。
3D纹理
3D纹理可以看做是2D纹理多个切片的一个数组。将2D纹理当做的一个面,将面叠加起来,便是一个立体。这个立体就是3D纹理。3D纹理使用一个3元坐标(s,t,r),r坐标决定哪一个切面,(s,t)决定在这个切面中的坐标。
2D纹理数组
2D纹理数组和3D纹理很类似,当时一个3D纹理是表示一张图像,而一个2D纹理数组,则是表示一组2D图像形成的动画序列。
纹理对象和加载纹理
一个纹理对象是一个存储将要被渲染的纹理数据的容器,包括了图片数据,过滤方式和封装方式。在OpenGLES中,一个纹理对象通过一个非负整数来标识,这个数是这个纹理对象的一个句柄。生产纹理的函数是glGenTextures
void glGenTextures(GLsizei n, GLuint *textures)
n 指定生产的纹理对象的数量
textures 一个非负整数数组,用了存储n个纹理对象的ID
通过glGenTextures
创建了空的容器,将会被拿去加载纹理数据和参数。当程序不在需要时,需要将纹理对象删除。通过glDeleteTextures
实现。
void glDeleteTextures(GLsizei n, Gluint *textures)
n 指定删除的纹理对象的数量
textures 一个非负整数数组,用了存储n个纹理对象的ID
当一个纹理对象的id被glGenTextures
创建后,程序必须将该纹理进行绑定。只有该纹理被绑定后,后续的操作例如glTexImage2D
和glTexParameter
才能影响到绑定的纹理对象。执行绑定功能的函数是glBindTexture
void glBindTexture(GLenum target, GLuint texture)
target 将纹理对象和目标GL_TEXTURE_2D, GL_TEXTURE_3D,GL_TEXTURE_2D_ARRAY或GL_TEXTURE_CUBE_MAP进行绑 定
texture 进行绑定的纹理的句柄
一旦纹理被绑定到一个指定的纹理目标,这个纹理对象将会一直维持对这个目标的绑定直到被删除。在创建一个纹理目标并绑定后,下一步便是使用纹理去加载图片数据。一个基本的加载2D和立体贴图纹理的函数是glTexImage2D
。另外,在OpenGl ES 3.0 中有几个可以替代的函数,这些函数被用于指定的2D纹理,包括使用不可变纹理glTexStorage2D
和glTexSubImage2D
的结合。
void glTextImage2D (GLenum target, GLint level,
GLenum internalFormat, GLsizei width,
GLsizei height, GLint border,
Glenum formate, Glenum type
const void * pixels)
target 指定纹理的目标,包括GL_TEXTURE_2D或立体贴图面目标中的一个(GL_TEXTURE_CUBE_MAP_POSITIVE_X,GL_TEXTURE_CUBE_MAP_NEGATIVE_X,等等).
level 指定哪一个级别的mip将被加载。第一个等级被指定为0
internalFormat 纹理存储的内部格式。包括了无大小限制的内部格式和有大小限制的格式。无大小内部格式包括 GL_RGBA, GL_RGB, GL_LUMINANCE_ALPHA GL_LUMINANCE, GL_ALPHA 有大小的内部格式包括 GL_R8, GL_R8_SNORM, GL_R16F, GL_R32F
GL_R8UI, GL_R16UI, GL_R32UI, GL_R32I
GL_RG8, GL_RG8_SNORM, GL_RG16F, GL_RG32F GL_RG8UI, GL_RG8I, GL_RG16UI, GL_RG32UI GL_RG32I, GL_RGB8, GL_SRGB8, GL_RGB565 GL_RGB8_SNORM, GL_R11F_G11F_B10F
GL_RGB9_E5, GL_RGB16F, GL_RGB32F
GL_RGB8UI, GL_RGB16UI, GL_RGB16I, GL_RGB32UI GL_RGB32I, GL_RGBA8, GL_SRGB8_ALPHA8 GL_RGBA8_SNORM, GL_RGB5_A1, GL_RGBA4 GL_RGB10_A2, GL_RGBA16F, GL_RGBA32F GL_RGBA8UI, GL_RGBA8I, GL_RGB10_A2UI GL_RGBA16UI, GL_RGBA16I, GL_RGBA32I GL_RGBA32UI, GL_DEPTH_COMPONENT16 GL_DEPTH_COMPONENT24, GL_DEPTH_COMPONENT32F GL_DEPTH24_STENCIL8, GL_DEPTH24F_STENCIL8
width 图片的像素宽度
heigth 图片的像素高度
border 必须为0
formate 输入纹理数据的格式,可能是GL_RED
GL_RED_INTEGER
GL_RG
GL_RG_INTEGER
GL_RGB
GL_RGB_INTEGER
GL_RGBA
GL_RGBA_INTEGER
GL_DEPTH_COMPONENT
GL_DEPTH_STENCIL
GL_LUMINANCE_ALPHA
GL_ALPHA
type 输入的像素数据的类型;可能是
GL_UNSIGNED_BYTE
GL_BYTE
GL_UNSIGNED_SHORT
GL_SHORT
GL_UNSIGNED_INT
GL_INT
GL_HALF_FLOAT
GL_FLOAT
GL_UNSIGNED_SHORT_5_6_5
GL_UNSIGNED_SHORT_4_4_4_4
GL_UNSIGNED_SHORT_5_5_5_1
GL_UNSIGNED_INT_2_10_10_10_REV
GL_UNSIGNED_INT_10F_11F_11F_REV
GL_UNSIGNED_INT_5_9_9_9_REV
GL_UNSIGNED_INT_24_8
GL_FLOAT_32_UNSIGNED_INT_24_8_REV
GL_UNSIGNED_SHORT_5_6_5
pixels 包含了图片的像素数据。这个数据必须包含了(width*height)个像素,每个像素根据指定的格式和类型占用一定的字节,像素行必须用GL_UNPACK_ALIGNMENT设置glPixelStorei设置对齐
和纹理加载相关的函数,还有一个glPixelStorei
,该函数决定了像素数据存储方式。
void glPixelStorei(GLenum pname , GLint param)
pname 指定像素存储类型。下面的可选参数将影响当glTexImage2D, glTexImage3D, glTexSubImage2D, 和 glTexSubImage3D被调用时,数据如何从内存中被解包:GL_UNPACK_ROW_LENGTH, GL_UNPACK_IMAGE_HEIGHT, GL_UNPACK_SKIP_PIXELS, GL_UNPACK_SKIP_ROWS, GL_UNPACK_SKIP_IMAGES, GL_UNPACK_ALIGNMENT
下列可选参数将影响glReadPixels被调用时,数据如何被打包读入内存:GL_PACK_ROW_LENGTH, GL_PACK_IMAGE_HEIGHT, GL_PACK_SKIP_PIXELS, GL_PACK_SKIP_ROWS, GL_PACK_SKIP_IMAGES, GL_PACK_ALIGNMENT
param 为打包或解包指定的整形数值。
GL_PACK_xxxxx对纹理的更新没有任何影响。实际上,除了GL_UNPACK_ALIGNMENT
,其他的可选选项很少被使用
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
意味着每一个像素行从字节边界开始,换而言之,数据被紧密打包。该值的默认值是4,表示像素行被认定从4字节的边界开始。
纹理过滤和mip贴图
纹理坐标用于生成一个2D索引,从纹理贴图中读取。当缩小和放大过滤器设置为GL_NEAREST
时,就会发生这样的情况:一个texels将在提供的纹理坐标位置上读取。这样称作点采样或最近采样。
然而,最近采样可能产生视觉伪像。一个伪像的产生是因为一个图像在屏幕中变小,导致像素之间的纹理坐标产生了巨大跳跃。这造成了从一个张巨大的纹理图中读取了少量的采用点,从而导致了锯齿伪像的产生。
解决这一问题的方法是,采用mip贴图。所谓的mip贴图,就是通过当前的屏幕分辨率,生成较低的屏幕分辨率的图片,形成一个图片链。比如现在有一张64*64的图片,采用mip贴图,会生成32*32,16*16,8*8,4*4,2*2,1*1,等低分辨率的图片。这些图片中,最原始的被称为0级层。每低一个低级的层,都是由上等级的图片生成的,像素合并规则是,将每个图层的2*2的像素点相加取平均值,作为下一层的一个像素点。
缩小过滤发生在屏幕中的多边形小于纹理大大小时。放大过滤发生在屏幕中多边大于纹理的大小时。对于放大过滤,mip贴图没有影响。对于缩小过滤,mip贴图可以保证像素的平滑
通过glTexParameter[i|f][v]
来设置纹理的的参数
void glTexParameteri(GLenum target, GLenum pname, GLint param)
void glTexParameteriv(GLenum target, GLenum pname, const GLint *params)
void glTexParameterf(GLenum target, GLenum pname, GLfloat param)
void glTexParameterfv(GLenum target, GLenum pname, const GLfloat *params)
target 纹理目标
pname 进行设置的纹理属性,可以包括
GL_TEXTURE_BASE_LEVEL
GL_TEXTURE_COMPARE_FUNC
GL_TEXTURE_COMPARE_MODE
GL_TEXTURE_MIN_FILTER
GL_TEXTURE_MAG_FILTER
GL_TEXTURE_MIN_LOD
GL_TEXTURE_MAX_LOD
GL_TEXTURE_MAX_LEVEL
GL_TEXTURE_SWIZZLE_R
GL_TEXTURE_SWIZZLE_G
GL_TEXTURE_SWIZZLE_B
GL_TEXTURE_SWIZZLE_A
GL_TEXTURE_WRAP_S
GL_TEXTURE_WRAP_T
GL_TEXTURE_WRAP_R
params 指定属性的参数
对于放大过滤GL_TEXTURE_MAG_FILTER
,可以设置为GL_NEAREST
或者是GL_LINEAR
。
对于缩小过滤GL_TEXTURE_MIN_FILTER
,可以设置的值如下
-
GL_NEAREST
表示根据纹理坐标,获取最近的一个纹理样本 -
GL_LINEAR
根据纹理坐标,采用2次采样方法,获取纹理样本 -
GL_NEAREST_MIPMAP_NEAREST
根据纹理坐标,从最近的mip贴图层中获取一个点样本 -
GL_NEAREST_MIPMAP_LINEAR
根据纹理坐标,获取2个最近的mip图层的采样点,并取2个样本之间的插值 -
GL_LINEAR_MIPMAP_NEAREST
根据纹理坐标,采用二次线性插值从最近的mip层中获取一个点样本 -
GL_LINEAR_MIPMAP_LINEAR
根据纹理坐标,获取最近2个mip图层中采用二次线性获取的采用点,并取2个采用点之间的插值
自动生成Mip贴图
OpenGL ES 3.0 中提供了一个自动生成mip贴图的方法glGenerateMipmap
;
纹理坐标的封装
纹理封装方式(Texture wrap modes
)被用于指定超出纹理坐标限定范围[0.0,1.0]的行为。通过glTexParameter[i|f][v]
可以设置纹理封装方式。这些mode可以独立设置s坐标轴,t坐标轴和r坐标轴。例如GL_TEXTURE_WRAP_S
mode定义了s坐标超出[0.0,1.0]的行为。同样的GL_TEXTURE_WRAP_T
和GL_TEXTURE_WRAP_R
分别定义了t坐标和r坐标的超出[0.0,1.0]的行为。
在OpenGL ES 3.0 中,有3中mode值可以设置。
GL_REPEAT 重复纹理
GL_CLAMP_TO_EDGE 限定读取纹理的边缘
GL_MIRRORED_REPEAT 重复纹理和镜像
纹理的调配
纹理调配控制输入的R,RG,RGB或RBGA纹理中的颜色分量在着色器中读取时如何映射到分量。通过调用glTexParameter[i|f][v]
来设置纹理调配,key可以设置为GL_TEXTURE_SWIZZLE_R, GL_TEXTURE_SWIZZLE_G, GL_TEXTURE_ SWIZZLE_B, 或 GL_TEXTURE_SWIZZLE_A,而value可能分别从R,G,B,A分量读取的GL_RED, GL_GREEN, GL_BLUE, 或 GL_ALPHA。也可以通过GL_ZERO和GL_ONE将值设置为0或1
纹理细节级别
在一些程序中,在所有的纹理mip贴图都可以使用前就能够开始在屏幕上进行显示的功能是很有用的。比如GPS的程序,可以先显示低等级的mip贴图,等到所有的mip贴图都下载后,在显示高清的mip贴图。可以通过glTexParameter[i|f][v]
中的GL_TEXTURE_BASE_LEVEL
来设置可以被使用的最大mip贴图等级。默认是0。同样的,GL_TEXTURE_MAX_LEVEL
设置了最小的mip贴图等级,默认是1000。
为了选择用于渲染的mip贴图级别,OpenGL ES自动计算一个细节级别(LOD)的值。这个浮点值决定了哪一mip贴图级别被筛选出来。一个程序能够控制LOD值的最大和最小值。通过设置GL_TEXTURE_MIN_LOD
和GL_TEXTURE_MAX_LOD
。
在着色器中使用纹理
// Vertex shader
#version 300 es
layout(location = 0) in vec4 a_position;
layout(location = 1) in vec2 a_texCoord;
out vec2 v_texCoord;
void main()
{
gl_Position = a_position;
v_texCoord = a_texCoord;
}
// Fragment shader
#version 300 es
precision mediump float;
in vec2 v_texCoord;
layout(location = 0) out vec4 outColor;
uniform sampler2D s_texture;
void main() {
outColor = texture( s_texture, v_texCoord );
}
碎片着色器中的texture
函数,表示从指定的纹理单元中,根据输入的坐标,映射出纹理中相应的vec4
颜色。
vec4 texture(sampler2D sampler, vec2 coord[, float bias])
sampler 采样器,指定的纹理单元的编号
coord 2D纹理的坐标
bias 可选参数,提供用于纹理读取的mip贴图偏值。这运行着色器明确偏置用于mip贴图选择的LOD计算值
在sampler参数需要传入的纹理单元编号前,需要先激活一个纹理单元,并将纹理绑定到该纹理单元上。使用glActiveTexture
进行激活
void glActiveTexture(GLenum texture)
texture 激活纹理单元,GL_TEXTURE0, GL_TEXTURE1, ... , GL_TEXTURE31分别代表纹理单元0到31
使用glActiveTexture
激活一个纹理单元后,便可使用glBindTexture
将指定的纹理绑定到该纹理单元上。具体代码如下:
// Get the sampler locations
userData->samplerLoc = glGetUniformLocation(
userData->programObject,
“s_texture”);
// ...
// Bind the texture
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, userData->textureId);
// Set the sampler texture unit to 0
glUniformli(userData->samplerLoc, 0);
加载立体贴图,3D纹理和2D纹理数组,也是同样的操作,对应的texture
分别为
vec4 texture(samplerCube sampler, vec3 coord[, float bias])
vec4 texture(sampler3D sampler, vec3 coord[, float bias])
vec4 texture(sampler2DArray sampler, vec3 coord[, float bias])
压缩纹理
OpenGL ES 3.0 支持2中压缩算法-EAC和ETC2。EAC版本用于压缩1到2频道的数据。ETC2版本用于压缩3到4频道的数据。使用glCompressedTexImage2D
可以读取压缩的2D纹理和立体贴图纹理。使用glCompressedTexImage3D
读取已压缩的2D纹理数组。注意:ETC2/EAC不支持3D纹理压缩,但是使用glCompressedTexImage3D可以加载指定供应商的3D纹理压缩格式
void glCompressedTexImage2D(GLenum target, GLint level,
GLenum internalFormat,
GLsizei width, GLsizei height,
GLint border, GLsizei imageSize,
const void *data)
void glCompressedTexImage3D(GLenum target, GLint level,
GLenum internalFormat,
GLsizei width, GLsizei height,
GLsizei depth, GLint border,
GLsizei imageSize,
const void *data)
target 指定纹理目标
level 指定加载的mip级别,默认是0
internalFormat 纹理的内部存储格式
width 图片的像素宽度
height 图片的像素高度
depth 图片的像素深度
border 必须设置为0
imageSize 图片的字节大小
data 压缩后的图片数据
可以通过glGetIntegerv
传入GL_COMPRESSED_TEXTURE_FORMATS
进行查询支持的压缩格式,该函数会返回一个GLenum
的数组。
纹理子图像规范
在使用glTexImage2D
更新一张纹理图片后,可能需要更新图片的一部分。这时候可以使用glTexSubImage2D
来来自2D纹理图像的一部分。
void glTexSubImage2D (GLenum target, GLint level,
GLInt xoffset, GLint yoffset
GLsizei width, GLsizei height,
GLenum format, Glenum type,
const void *pixels)
target 纹理目标
level mip贴图级别
xoffset 开始进行更新的x序列号
yoffset 开始进行更新的y序列号
width 进行更新的图像子区域的宽度
heigth 进行更新的图像子区域的高度
formate 输入纹理数据的格式
type 输入像素数据的类型
pixels 包含图像子区域的像素数据
这个函数将会更新区域(xoffset,yoffset)到(xoffset+width-1,yoffset+height-1)。注意:使用这个函数,纹理必须依据被指定,而且子图片的范围必须在指定的纹理图片之内,pixels的对其方式必须被指定为GL_UNPACK_ALIGNMENT
同样的原理,可以使用glCompressedTexSubImage2D
更新压缩过的图片,使用它glTexSubImage3D
更新3D纹理或2D纹理数组,使用glCompressedTexSubImage3D
更新压缩过的2D纹理数组。
从颜色缓冲区复制纹理数据
glReadBuffer
指定被复制的颜色缓存区。然后通过glCopyTexImage2D, glCopyTexSubImage2D,和 glCopyTexSubImage3D
从指定的颜色缓冲区张读取纹理数据
采样器对象
为了减少大量纹理上使用相同的设置的开销。引入了采样器对象,将采样器状态与纹理状态分离。简而言之,所有可用glTexParameter[i|f][v]
设置都可以对采样器对象进行设置。可以在一次函数调用中与纹理单元绑定使用。
glGenSamplers
用于创建采用器,使用glDeleteSamplers
删除采样器。使用glBindSampler
将采样器与纹理单元进行绑定。使用glSamplerParameter[f|i][v]
设置采样器参数。
不可变纹理
由于应用程序使用glTexImage2D
和glTexImage3D
等函数独立指定纹理的每个mip贴图的级别。这导致了驱动程序无法在绘图之前确定纹理是否完全指定。也就是说,它必须检查每一个mip贴图级别或者子图像的格式是否相符、每一个级别的大小是否正确以及是否有足够的内存。这种绘图时检查可能代价很高,而使用不可变纹理可以避免这种情形。
不可变纹理的思路很简单:程序在加载数据之前指定纹理的格式和大小。OpenGL ES的驱动可以提前进行一致性和内存检查。一旦纹理不可变,它的格式和尺寸也就不能改变。然而程序仍然可以通过使用glTexSubImage2D, glTexSubImage3D,或 glGenerateMipMap
来加载纹理。
为了创建不可变纹理,程序必须先使用glBindTexture
绑定当前纹理,在调用glTexStorage2D
或glTexStorage3D
创建不可变的存储空间。