(九)纹理
片段着色器的核心方面时对表面应用纹理。
纹理基础
3D图形渲染中最基本的操作之一时是对一个表面应用纹理。
纹理有多种形式:2D纹理,2D纹理数组,3D纹理和立方图纹理等。
纹理通常使用纹理坐标应用到一个表面,纹理坐标可以视为纹理数组数据中的索引。
2D纹理
2D纹理是OpenGL ES中最基本和常用的纹理形式。
一个纹理的单独数据元素称作纹素(Texel)。
纹理数据的基本格式如下表:
纹理基本格式.png
2D纹理的纹理坐标用一对2D坐标(s, t)指定。
立方图纹理
立方图就是一个由6个单独2D纹理面组成的纹理。常用于环境贴图特效。
立方图纹素的读取通过使用一个3D向量(s, t, r)作为纹理坐标。
立方图纹理.png
3D纹理
3D纹理可以看作2D纹理的多个切片。
3D纹理.png
2D纹理数组
2D纹理数组和3D纹理和类似,但是用途不同。2D纹理数据组常常用于存储2D图像的一个动画。
纹理对象和纹理的加载
下面例程实现生成纹理对象、绑定并加载图像数据的功能:
// Texture object handle
GLuint textureId;
// 2 x 2 Image, 3 bytes per pixel (R, G, B)
GLubyte pixels[4 * 3] = {
255, 0, 0, // Red
0, 255, 0, // Green
0, 0, 255, // Blue
255, 255, 0 // Yellow
};
// 使用打包数据
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
// 生成纹理对象
glGenTextures(1, &textureId);
// 绑定纹理对象
glBindTexture(GL_TEXTURE_2D, textureId);
// 加载纹理
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 2, 2, 0, GL_RGB,
GL_UNSIGNED_BYTE, pixels);
// 设置过滤模式
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER,
GL_NEAREST);
纹理过滤和mip贴图
纹理坐标用于生成一个2D索引,以从纹理贴图中读取。当缩小和放大过滤器设置为GL_NEAREST时,可能会发生伪像。
OpenGL ES 中解决这类伪像得方案被称为mip贴图。
mip贴图得思路时构建一个mip图像链,始于原来指定得图像,后续得每个图像在每个维度上是前一个图像得一半,一直持续到最后达到链底部得1×1纹理。
自动mip贴图生成
OpenGL Es 3.0还提供了用glGenerateMipmap自动生成mip贴图得机制。
void glGenerateMipmap(GLenum target)
纹理坐标包装
纹理包装模式用于指定纹理坐标超过[0.0, 1.0]范围时所发生的行为。有三种纹理包装模式:
纹理包装模式.png
纹理调配
纹理调配(Swizzle)控制输入的R、RG、RGB或RGBA纹理中的颜色分量在着色器中读取时如何映射到分量。
纹理细节级别
GL_TEXTURE_BASE_LEVEL设置用于纹理的最大mip贴图级别。
GL_TEXTURE_MAX_LEVEL设置使用的最小mip贴图级别。
深度纹理对比
GL_TEXTURE_COMPARE_FUNC和GL_TEXTURE_COMPARE_MODE两个参数是用来提供百分比渐进过滤(PCF)功能。
纹理格式
有效的未确定的那个大小内部格式组合:
有效的未确定的那个大小内部格式组合.png
规范化确定大小内部格式组合:
规范化确定大小内部格式组合.png
有效确定大小浮点内部格式组合:
有效确定大小浮点内部格式组合.png
有效确定大小内部整数纹理格式组合:
有效确定大小内部整数纹理格式组合.png 有效确定大小内部整数纹理格式组合-2.png
有效共享指数确定大小内部格式组合:
有效共享指数确定大小内部格式组合.png
有效确定大小内部格式组合:
有效确定大小内部格式组合.png
有效确定大小内部格式组合:
有效确定大小内部格式组合.png
在着色器中使用纹理
执行2D纹理的顶点和片段着色器:
// 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 );
}
使用立方图纹理的示例
加载立方图纹理:
GLuint CreateSimpleTextureCubemap() {
GLuint textureId;
// Six l x l RGB faces
GLubyte cubePixels[6][3] = {
// Face 0 - Red
255, 0, 0,
// Face 1 - Green,
0, 255, 0,
// Face 2 - Blue
0, 0, 255,
// Face 3 - Yellow
255, 255, 0,
// Face 4 - Purple
255, 0, 255,
// Face 5 - White
255, 255, 255
};
// Generate a texture object
glGenTextures(1, &textureId);
// Bind the texture object
glBindTexture(GL_TEXTURE_CUBE_MAP, textureId);
// Load the cube face - Positive X
glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, GL_RGB, 1, 1, 0, GL_RGB, GL_UNSIGNED_BYTE, &cubePixels[0]);
// Load the cube face - Negative X
glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, 0, GL_RGB, 1, 1, 0, GL_RGB, GL_UNSIGNED_BYTE, &cubePixels[1]);
// Load the cube face - Positive Y
glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, 0, GL_RGB, 1, 1, 0, GL_RGB, GL_UNSIGNED_BYTE, &cubePixels[2]);
// Load the cube face - Negative Y
glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, GL_RGB, 1, 1, 0, GL_RGB, GL_UNSIGNED_BYTE, &cubePixels[3]);
// Load the cube face - Positive Z
glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, 0, GL_RGB, 1, 1, 0, GL_RGB, GL_UNSIGNED_BYTE, &cubePixels[4]);
// Load the cube face - Negative Z
glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, GL_RGB, 1, 1, 0, GL_RGB, GL_UNSIGNED_BYTE, &cubePixels[5]);
// Set the filtering mode
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER,
GL_NEAREST);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER,
GL_NEAREST);
return textureId;
}
立方图纹理的顶点和片段着色器对
// Vertex shader
#version 300 es
layout(location = 0) in vec4 a_position;
layout(location = 1) in vec3 a_normal;
out vec3 v_normal;
void main() {
gl_Position = a_position;
v_normal = a_normal;
}
// Fragment shader
#version 300 es
precision mediump float;
in vec3 v_normal;
layout(location = 0) out vec4 outColor;
uniform samplerCube s_texture;
void main() {
outColor = texture( s_texture, v_normal );
}
加载3D纹理和2D纹理数组
加载3D纹理和2D纹理数组是glTexImage3D
void glTexImage3D(GLenum target,
GLint level,
GLenum internalFormat,
GLsizei width, GLsizei height,
GLsizei depth, GLint border,
GLenum format, GLenum type,
const void* pixels)
压缩纹理
OpenGL ES 3.0还支持压缩纹理图像数据的加载,优点:
1、减少纹理再设备上的内存占用
2、节约了着色器中读取纹理时消耗的内存带宽
3、减小应用程序的包大小
爱立信纹理压缩(Ericsson Texture Compression,ETC)提供给Khronos,作为OpenGL ES 3.0的标准纹理压缩格式。
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)
常用的生成ETC图像的免费工具有来自Khronos的开源程序库libKTX。
纹理子图像规范
用glTexImage2D上传纹理图像之后,可以更新图像的各个部分。如果只希望更新图像的一个子区域,可以使用函数glTexSubImage2D:
void glTexSubImage2D( GLenum target,
GLint level,
GLint xoffset,
GLint yoffset,
GLsizei width,
GLsizei height,
GLenum format,
GLenum type,
const void* pixels)
从颜色缓冲区赋值纹理数据
OpenGL ES 3.0中支持的另一个纹理功能就是从颜色缓冲区赋值数据到一个纹理。
作为复制图像数据来源的颜色缓冲区可以用glReadBuffer函数设置:
void glReadBuffer(GLenum mode)
从颜色缓冲区复制数据到纹理的函数有glCopyTexImage2D、glCopyTexSubImage2D和glCopyTexSubImage3D:
void glCopyTexImage2D(GLenum target,
GLint level,
GLenum internalFormat,
GLint x,
GLint y,
GLsizei width,
GLsizei height,
Glint border )
采样器对象
采样器对象可以用于许多纹理,从而降低API开销:
void glGenSamplers(GLsizei n, GLuint *samplers)
void glDeleteSamplers(GLsizei n, const GLuint *samplers)
void glBindSampler(GLenum unit, GLuint sampler)
不可变纹理
不可变纹理的思路很简单:应用程序在加载数据之前指定纹理的格式和大小。用
void glTexStorage2D( GLenum target,
GLsizei levels,
GLenum internalFormat,
GLsizei width,
GLsizei height)
void glTexStorage3D( GLenum target,
GLsizei levels,
GLenum internalFormat,
GLsizei width,
GLsizei height,
GLsizei depth)
分配不可变存储。
像素解包缓冲区对象
像素解包缓冲区对象可以用于将纹理数据流传输到GPU。
小结
本章介绍了OpenGL ES 3.0中纹理的方法,可以用于开发许多高级的渲染特效。