纹理:
简单说来纹理就是一张图像或者照片能被加载进opengl。opengl中的纹理可以用来表示图像,照片,甚至由一个数学算法生成的分形数据。每个二维纹理都是由许多小的纹理元素组成,他们是小块数据类似像素。通常,直接去加载一个图像文件来作为纹理。每个二维纹理都有自己的坐标从(0,0)到(1,1)如下:
在opengl es2.0中规定每个纹理不必是正方形,但每个维度必须是2的幂。
加载纹理:
在本例代码中,TextureHelper类如下:
public class TextureHelper {
private static final String TAG = "TextureHelper";
/**
* Loads a texture from a resource ID, returning the OpenGL ID for that
* texture. Returns 0 if the load failed.
*
* @param context
* @param resourceId
* @return
*/
public static int loadTexture(Context context, int resourceId) {
final int[] textureObjectIds = new int[1];
glGenTextures(1, textureObjectIds, 0);
if (textureObjectIds[0] == 0) {
if (LoggerConfig.ON) {
Log.w(TAG, "Could not generate a new OpenGL texture object.");
}
return 0;
}
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inScaled = false;
// Read in the resource
final Bitmap bitmap = BitmapFactory.decodeResource(
context.getResources(), resourceId, options);
if (bitmap == null) {
if (LoggerConfig.ON) {
Log.w(TAG, "Resource ID " + resourceId + " could not be decoded.");
}
glDeleteTextures(1, textureObjectIds, 0);
return 0;
}
// Bind to the texture in OpenGL
glBindTexture(GL_TEXTURE_2D, textureObjectIds[0]);
// Set filtering: a default must be set, or the texture will be
// black.
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
// Load the bitmap into the bound texture.
texImage2D(GL_TEXTURE_2D, 0, bitmap, 0);
// Note: Following code may cause an error to be reported in the
// ADB log as follows: E/IMGSRV(20095): :0: HardwareMipGen:
// Failed to generate texture mipmap levels (error=3)
// No OpenGL error will be encountered (glGetError() will return
// 0). If this happens, just squash the source image to be
// square. It will look the same because of texture coordinates,
// and mipmap generation will work.
glGenerateMipmap(GL_TEXTURE_2D);
// Recycle the bitmap, since its data has been loaded into
// OpenGL.
bitmap.recycle();
// Unbind from the texture.
glBindTexture(GL_TEXTURE_2D, 0);
return textureObjectIds[0];
}
}
如参resourceId为图片资源id, glGenTextures(1, textureObjectIds, 0);是创建一个纹理对象,由于opengl不能读取png,jpeg这些图片压缩格式,需要读取图片的元素数据,所以需要先解码压缩图片,可以利用Android本身自带的图片解码工具,
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inScaled = false;
// Read in the resource
final Bitmap bitmap = BitmapFactory.decodeResource(
context.getResources(), resourceId, options);
options.inScaled = false;意思是不缩放,按原始数据。
// Bind to the texture in OpenGL
glBindTexture(GL_TEXTURE_2D, textureObjectIds[0]);
是告诉opengl后面的纹理使用这个纹理对象(纹理也是一种状态机)。
纹理过滤器:
当我们在使用纹理绘制渲染的时候,可能无法精确映射到opengl生成的片段上。有可能被放大或者缩小。通常有两种过滤模式:最近邻过滤和双线性插值。
最近邻过滤:这个方式为每个片段选择最邻近的纹理元素,当我们放大纹理的时候,他的锯齿效果会相当明显。如下,每个纹理单元都清楚的显示为一个小方块。
而当纹理缩放时,由于没有足够的片段来绘制所有的纹理单元,很多纹理细节会丢失。
双线性插值:双线性过滤使用双线性插值平滑像素之间的过滤,而不是每个片段使用最近的纹理元素,opengl会使用4个邻接的纹理元素,并在他们之间用一个线性插值算法做插值。双线性过滤很适合纹理放大,但缩小就会失去很多细节。
mip贴图:
当生成文理的时候,opengl会使用所有的纹理元素生成生成每个级别的纹理,当过滤时还要保证所有的纹理元素都被用上。当渲染时,opengl会根据每个片段的纹理元素的数量为每个片段选择合适的纹理级别。使用mip贴图会消耗更多的内存,但是渲染也会更快,这是因为较小级别的纹理在gpu的纹理缓存中占用更少的内存。如下图为不同级别的mip贴图:
三线性过滤:
在使用mip贴图级别之间来回切换有时候可能看到明显的跳跃或线条。这个时候可以告诉opengl先切换到三线性过滤,这样在两个最邻近的mip贴图之间也要插值,这样有利于每个mip之间的过度,得到一个更平滑的图像。
在代码中,
// Set filtering: a default must be set, or the texture will be
// black.
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri是设置过滤器,GL_TEXTURE_MIN_FILTER指是缩小情况用GL_LINEAR_MIPMAP_LINEAR,即告诉opengl使用三线性过滤。GL_TEXTURE_MAG_FILTER指放大情况,使用GL_LINEAR,即告诉opengl使用双线性过滤。其他情况可以参考下表:
最后使用android.opengl.GLUtils.texImage2D(GL_TEXTURE_2D, 0, bitmap, 0);告诉opengl读入bitmap定义的位图数据,并且赋值到当前绑定的纹理对象。既然这个位图数据已经被赋值到opengl了 我们在java层就不在需要了,调用 bitmap.recycle();立刻释放。如果我们想在opengl使用mip贴图也只需要 glGenerateMipmap(GL_TEXTURE_2D);告诉opengl生成所有纹理必要的级别。我们已经完成了纹理的加载,所以最后防止修改,最后我们解绑与这个纹理的绑定 // Unbind from the texture.
glBindTexture(GL_TEXTURE_2D, 0);只需传入0,最后返回纹理对象textureObjectIds[0]。注意这里虽然解绑,但是textureObjectIds依然是纹理对象,这是一种状态机,对状态机不了解的可以自己google之。
回到代码,为了使用纹理我们在texture_vertex_shader.glsl文件代码如下:
uniform mat4 u_Matrix;
attribute vec4 a_Position;
attribute vec2 a_TextureCoordinates;
varying vec2 v_TextureCoordinates;
void main()
{
v_TextureCoordinates = a_TextureCoordinates;
gl_Position = u_Matrix * a_Position;
}
引入了a_TextureCoordinates,他有两个分量,S坐标和T坐标,所以被定义为vec2 。我们把这些坐标传递给顶点着色器被插值的varying ,称为a_TextureCoordinates。
同理在片段着色器texture_fragment_shader.glsl,
precision mediump float;
uniform sampler2D u_TextureUnit;
varying vec2 v_TextureCoordinates;
void main()
{
gl_FragColor = texture2D(u_TextureUnit, v_TextureCoordinates);
}
为了把纹理绘制在物体上,opengl会把会为每个片段都调用这个片段着色器,并且每个调用都接收v_TextureCoordinates的纹理坐标,片段着色器通过
uniform sampler2D u_TextureUnit接收实际的纹理数据,u_TextureUnit被定义为一个sampler2D这个变量是指一个二维纹理数据的数组。
被插值的纹理坐标和纹理数据被传递给着色器函数 gl_FragColor = texture2D(u_TextureUnit, v_TextureCoordinates),他会读取特定纹理坐标处的颜色值,结果通过吧结果赋值给gl_FragColor 设置片段的颜色。
本例代码对之前代码进行了整理,VertexArray,Mallet,Table,ColorShaderProgram,TextureShaderProgram等类。
代码路径:
https://github.com/pangrui201/OpenGlesProject/tree/master/OpenGlesProject_lesson6