Android OpenGL纹理
首先申明下,本文为笔者学习《OpenGL ES应用开发实践指南》的笔记,并加入笔者自己的理解和归纳总结。
1、纹理
纹理就是一个图像或照片,它们可以被加载进OpenGL中。每个二维的纹理都有其自己的坐标空间,按照惯例,一个维度叫做S,而另一个叫做T。
大多数计算机图像都有一个默认的方法,通常是y轴向下,y随着向图像的底部移动而增加。
2、纹理过滤
当纹理大小被扩大或缩小时,我们还需要使用纹理过滤。(1) 最近邻过滤
这个方式为每个片段选择最近的纹理元素。
(2) 双线性过滤
使用双线性插值平滑像素之间的过渡,使用四个邻接的纹理元素,并在它们之间用一个线性插值算法做插值。
双线性过滤很适合处理放大。
(3) MIP贴图
对于缩小处理,双线性过滤值给每个片段使用四个纹理元素,将会失去很多细节。
MIP贴图,可以生成一组优化过的不同大小的纹理。当生成这组纹理的时候,OpenGL会使用所有的纹理元素生成每个级别的纹理,当过滤纹理时,还要确保所有的纹理元素都能被使用。在渲染时,OpenGL会根据每个片段的纹理元素为每个片段选择最适合的解绑。
(4) 三线性过滤
三线性过滤,在最邻近的MIP贴图级别之间也要插值,这样,每个片段总共使用8个纹理元素插值,有助于消除每个MIP贴图级别之间的过渡,并且得到一个更平滑地图像。
3、加载纹理
(1) 生成新的纹理对象int[] textureObjectIds = new int[1];
GLES20.glGenTextures(1, textureObjectIds, 0);
(2) 设置过滤参数GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER,
GLES20.GL_LINEAR_MIPMAP_LINEAR);
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER,
GLES20.GL_LINEAR);
OpenGL纹理过滤模式
GL_NEAREST | 最近邻过滤 |
GL_NEAREST_MIPMAP_NEAREST | 使用MIP贴图的最近邻过滤 |
GL_NEAREST_MIPMAP_LINEAR | 使用MIP贴图级别之间插值的最近邻过滤 |
GL_LINEAR | 双线性过滤 |
GL_LINEAR_MIPMAP_NEAREST | 使用MIP贴图的双线性过滤 |
GL_LINEAR_MIPMAP_LINEAR | 三线性过滤(使用MIP贴图级别之间插值的双线性过滤) |
缩小 |
GL_NEAREST
GL_NEAREST_MIPMAP_NEAREST
GL_NEAREST_MIPMAP_LINEAR
GL_LINEAR
GL_LINEAR_MIPMAP_NEAREST
GL_LINEAR_MIPMAP_LINEAR
|
放大 |
GL_NEAREST
GL_LINEAR
|
GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bitmap, 0);
GLES20.glGenerateMipmap(GLES20.GL_TEXTURE_2D);
(4) 加载纹理
protected int loadTexture(int resId) {
// 生成一个新的OpenGL纹理的ID
int[] textureObjectIds = new int[1];
GLES20.glGenTextures(1, textureObjectIds, 0);
if (textureObjectIds[0] == 0) {
LogUtil.log("OpenGL", "Could not generate a new OpenGL texture object.");
return 0;
}
BitmapFactory.Options options = new BitmapFactory.Options();
// 需要原始的图像数据,而不是缩放版
options.inScaled = false;
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), resId, options);
if (bitmap == null) {
LogUtil.log("OpenGL", "Resource ID " + resId + " could not be decoded.");
// 如果失败,删除纹理对象
GLES20.glDeleteTextures(1, textureObjectIds, 0);
return 0;
}
// 我们需要告诉后面的纹理调用,都应该应用于这个纹理对象
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureObjectIds[0]);
// 设置过滤参数
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER,
GLES20.GL_LINEAR_MIPMAP_LINEAR);
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER,
GLES20.GL_LINEAR);
// 加载纹理到OpenGL,并生成MIP贴图
GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bitmap, 0);
GLES20.glGenerateMipmap(GLES20.GL_TEXTURE_2D);
// 回收图片
bitmap.recycle();
// 与当前纹理解除绑定,防止被修改
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0);
return textureObjectIds[0];
}
4、着色器文件
(1) 顶点着色器,texture_vertex_shader.glsl文件a_TextureCoordinates是纹理坐标,有两个分量:S坐标和T坐标
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;
}
(2) 片段着色器,texture_fragment_shader文件u_textureUnit被定义为一个sampler2D,这个变量类型指的是一个二维纹理数据的数组。
precision mediump float;
uniform sampler2D u_textureUnit;
varying vec2 v_TextureCoordinates;
void main()
{
gl_FragColor = texture2D(u_textureUnit, v_TextureCoordinates);
}
4、绘制着色器
(1) 顶点数据纹理数据采用不同的坐标方式
float[] tableVertices = {
0f, 0f, 0.5f, 0.5f,
-0.5f, -0.8f, 0f, 0.9f,
0.5f, -0.8f, 1f, 0.9f,
0.5f, 0.8f, 1f, 0.1f,
-0.5f, 0.8f, 0f, 0.1f,
-0.5f, -0.8f, 0f, 0.9f,
};
(2) 绘制纹理
// 把活动的纹理单元设置为纹理单元0
GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
// 把纹理绑定到这个单元
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextureId);
// 把被选择的纹理单元传递给片段着色器中的
GLES20.glUniform1i(uTextureUnitLocation, 0);
(3) 绘制不同着色器Resource类,包含一个顶点数据类VertexArray,bindData方法用来绑定属性,draw方法用来绘制界面。
public class Resource {
protected VertexArray mVertextArray;
public Resource(float[] vertexData) {
mVertextArray = new VertexArray(vertexData);
}
public void bindData(int attributeLocation, int dataOffset,
int componentCount, int stride) {
mVertextArray.setVertexAttribPointer(attributeLocation,
dataOffset, componentCount, stride);
}
public void draw() {
}
}
VertexArray类,保存顶点数据
public class VertexArray {
public static final int BYTES_PER_FLOAT = 4;
private final FloatBuffer floatBuffer;
public VertexArray(float[] vertexData) {
floatBuffer = ByteBuffer
.allocateDirect(vertexData.length * BYTES_PER_FLOAT)
.order(ByteOrder.nativeOrder())
.asFloatBuffer();
floatBuffer.put(vertexData);
}
public void setVertexAttribPointer(int attributeLocation, int dataOffset,
int componentCount, int stride) {
floatBuffer.position(dataOffset);
GLES20.glVertexAttribPointer(attributeLocation, componentCount,
GLES20.GL_FLOAT, false, stride, floatBuffer);
floatBuffer.position(0);
GLES20.glEnableVertexAttribArray(attributeLocation);
}
}
Table类,绘制桌子
public class Table extends Resource {
private final static int POSITION_COMPONENT_COUNT = 2;
private final static int TEXTURE_COMPONENT_COUNT = 2;
private final static int STRIDE = (POSITION_COMPONENT_COUNT + TEXTURE_COMPONENT_COUNT)
* VertexArray.BYTES_PER_FLOAT;
private final static float[] vertexData = new float[]{
0f, 0f, 0.5f, 0.5f,
-0.5f, -0.8f, 0f, 0.9f,
0.5f, -0.8f, 1f, 0.9f,
0.5f, 0.8f, 1f, 0.1f,
-0.5f, 0.8f, 0f, 0.1f,
-0.5f, -0.8f, 0f, 0.9f,
};
public Table() {
super(vertexData);
}
public void bindData(TextureProgram program) {
bindData(program.getPositionLocation(), 0, POSITION_COMPONENT_COUNT, STRIDE);
bindData(program.getTextureCoordLocation(),
POSITION_COMPONENT_COUNT, TEXTURE_COMPONENT_COUNT, STRIDE);
}
@Override
public void draw() {
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_FAN, 0, 6);
}
}
Mallet类,暂用点代替
public class Mallet extends Resource {
private final static int POSITION_COMPONENT_COUNT = 2;
private final static int COLOR_COMPONENT_COUNT = 3;
private final static int STRIDE = (POSITION_COMPONENT_COUNT + COLOR_COMPONENT_COUNT)
* VertexArray.BYTES_PER_FLOAT;
private final static float[] vertexData = new float[]{
0f, -0.25f, 0f, 0f, 1f,
0f, 0.25f, 1f, 0f, 0f
};
public Mallet() {
super(vertexData);
}
public void bindData(ColorProgram program) {
bindData(program.getPositionLocation(), 0,
POSITION_COMPONENT_COUNT, STRIDE);
bindData(program.getColorLocation(), POSITION_COMPONENT_COUNT,
COLOR_COMPONENT_COUNT, STRIDE);
}
@Override
public void draw() {
GLES20.glDrawArrays(GLES20.GL_POINTS, 0, 2);
}
}
Program类,保留顶点数据,加载着色器程序
public class Program {
protected int mProgramId;
public Program(Context context, int vertexShaderResId,
int fragmentShaderResId) {
mProgramId = useProgram(context, vertexShaderResId, fragmentShaderResId);
}
public void setUniform(float[] projectionMatrix) {
}
protected String readShaderFromRaw(Context context, int resId) {
BufferedReader br = null;
StringBuffer stringBuffer = new StringBuffer();
try {
br = new BufferedReader(new InputStreamReader(
context.getResources().openRawResource(resId)));
String line = null;
while ((line = br.readLine()) != null) {
stringBuffer.append(line + "\n");
}
} catch (IOException e) {
} finally {
if (br != null) {
try {
br.close();
} catch (IOException e) {
}
}
}
return stringBuffer.toString();
}
protected int compileShader(int type, String shaderCode) {
// 创建一个新的着色器对象
int shaderObjectId = GLES20.glCreateShader(type);
if (shaderObjectId == 0) {
// 创建失败
return 0;
}
// 上传和编译着色器代码
GLES20.glShaderSource(shaderObjectId, shaderCode);
GLES20.glCompileShader(shaderObjectId);
// 获取编译状态
int[] compileStatus = new int[1];
GLES20.glGetShaderiv(shaderObjectId, GLES20.GL_COMPILE_STATUS, compileStatus, 0);
// 获取着色器信息日志
LogUtil.log("OpenGL", GLES20.glGetShaderInfoLog(shaderObjectId));
if (compileStatus[0] == 0) {
// 如果失败,删除着色器对象
GLES20.glDeleteShader(shaderObjectId);
return 0;
}
return shaderObjectId;
}
protected int linkProgram(int vertexShaderId, int fragmentShaderId) {
// 创建一个新的程序对象
int programId = GLES20.glCreateProgram();
if (programId == 0) {
return 0;
}
// 新建程序对象附上着色器,并链接程序
GLES20.glAttachShader(programId, vertexShaderId);
GLES20.glAttachShader(programId, fragmentShaderId);
GLES20.glLinkProgram(programId);
// 获取链接状态
int[] linkStatus = new int[1];
GLES20.glGetProgramiv(programId, GLES20.GL_LINK_STATUS, linkStatus, 0);
// 获取着程序链接信息日志
LogUtil.log("OpenGL", GLES20.glGetProgramInfoLog(programId));
if (linkStatus[0] == 0) {
// 如果链接失败,删除程序对象
GLES20.glDeleteProgram(programId);
return 0;
}
return programId;
}
protected boolean validateProgram(int programId) {
// 验证程序,只在开发阶段需要
GLES20.glValidateProgram(programId);
LogUtil.log("OpenGL", GLES20.glGetProgramInfoLog(programId));
int[] validateStatus = new int[1];
GLES20.glGetProgramiv(programId, GLES20.GL_VALIDATE_STATUS, validateStatus, 0);
return validateStatus[0] != 0;
}
protected int useProgram(Context context, int vertexShaderResId, int fragmentShaderResId) {
String vertexShaderCode = readShaderFromRaw(context, vertexShaderResId);
String fragmentShaderCode = readShaderFromRaw(context, fragmentShaderResId);
int vertexShaderId = compileShader(GLES20.GL_VERTEX_SHADER, vertexShaderCode);
int fragmentShaderId = compileShader(GLES20.GL_FRAGMENT_SHADER, fragmentShaderCode);
int programId = linkProgram(vertexShaderId, fragmentShaderId);
validateProgram(programId);
GLES20.glUseProgram(programId);
return programId;
}
}
ColorProgram类,绘制颜色着色器
public class ColorProgram extends Program {
private final static String A_POSITION = "a_Position";
private final static String A_COLOR = "a_Color";
private final static String U_MATRIX = "u_Matrix";
private int aPositionLocation, aColorLocation, uMatrixLocation;
public ColorProgram(Context context) {
super(context, R.raw.ortho_vertex_shader, R.raw.ortho_fragment_shader);
aPositionLocation = GLES20.glGetAttribLocation(mProgramId, A_POSITION);
aColorLocation = GLES20.glGetAttribLocation(mProgramId, A_COLOR);
uMatrixLocation = GLES20.glGetUniformLocation(mProgramId, U_MATRIX);
}
@Override
public void setUniform(float[] projectionMatrix) {
GLES20.glUseProgram(mProgramId);
GLES20.glUniformMatrix4fv(uMatrixLocation, 1, false, projectionMatrix, 0);
}
public int getPositionLocation() {
return aPositionLocation;
}
public int getColorLocation() {
return aColorLocation;
}
}
TextureProgram类,绘制纹理着色器
public class TextureProgram extends Program {
private final static String A_POSITION = "a_Position";
private final static String A_TEXTURRE_COORDINATES = "a_TextureCoordinates";
private final static String U_MATRIX = "u_Matrix";
private final static String U_TEXTURE_UNIT = "u_TextureUnit";
private int aPositionLocation, aTextureCoordLocation, uMatrixLocation, uTextureUnitLocation;
private int mTextureId;
public TextureProgram(Context context, int resId) {
super(context, R.raw.texture_vertex_shader, R.raw.texture_fragment_shader);
aPositionLocation = GLES20.glGetAttribLocation(mProgramId, A_POSITION);
aTextureCoordLocation = GLES20.glGetAttribLocation(mProgramId, A_TEXTURRE_COORDINATES);
uMatrixLocation = GLES20.glGetUniformLocation(mProgramId, U_MATRIX);
uTextureUnitLocation = GLES20.glGetUniformLocation(mProgramId, U_TEXTURE_UNIT);
mTextureId = loadTexture(context, resId);
}
protected int loadTexture(Context context, int resId) {
// 生成一个新的OpenGL纹理的ID
int[] textureObjectIds = new int[1];
GLES20.glGenTextures(1, textureObjectIds, 0);
if (textureObjectIds[0] == 0) {
LogUtil.log("OpenGL", "Could not generate a new OpenGL texture object.");
return 0;
}
BitmapFactory.Options options = new BitmapFactory.Options();
// 需要原始的图像数据,而不是缩放版
options.inScaled = false;
Bitmap bitmap = BitmapFactory.decodeResource(context.getResources(), resId, options);
if (bitmap == null) {
LogUtil.log("OpenGL", "Resource ID " + resId + " could not be decoded.");
// 如果失败,删除纹理对象
GLES20.glDeleteTextures(1, textureObjectIds, 0);
return 0;
}
// 我们需要告诉后面的纹理调用,都应该应用于这个纹理对象
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureObjectIds[0]);
// 设置过滤参数
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER,
GLES20.GL_LINEAR_MIPMAP_LINEAR);
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER,
GLES20.GL_LINEAR);
// 加载纹理到OpenGL,并生成MIP贴图
GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bitmap, 0);
GLES20.glGenerateMipmap(GLES20.GL_TEXTURE_2D);
// 回收图片
bitmap.recycle();
// 与当前纹理解除绑定,防止被修改
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0);
return textureObjectIds[0];
}
@Override
public void setUniform(float[] projectionMatrix) {
GLES20.glUseProgram(mProgramId);
GLES20.glUniformMatrix4fv(uMatrixLocation, 1, false, projectionMatrix, 0);
// 把活动的纹理单元设置为纹理单元0
GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
// 把纹理绑定到这个单元
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextureId);
// 把被选择的纹理单元传递给片段着色器中的
GLES20.glUniform1i(uTextureUnitLocation, 0);
}
public int getPositionLocation() {
return aPositionLocation;
}
public int getTextureCoordLocation() {
return aTextureCoordLocation;
}
}
(4) OpenGLTextureShaderRender类
class OpenGLTextureShaderRender implements GLSurfaceView.Renderer {
private float[] projectionMatrix = new float[16];
private float[] modelMatrix = new float[16];
private float[] modelProjectionMatrix = new float[16];
private TextureProgram mTextureProgram;
private ColorProgram mColorProgram;
private Table mTable;
private Mallet mMallet;
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
GLES20.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
mTable = new Table();
mMallet = new Mallet();
mTextureProgram = new TextureProgram(OpenGLTextureShaderActivity.this,
R.drawable.air_hockey_surface);
mColorProgram = new ColorProgram(OpenGLTextureShaderActivity.this);
}
@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
GLES20.glViewport(0, 0, width, height);
// 创建透视投影
Matrix.perspectiveM(projectionMatrix, 0, 45, (float)width / (float)height, 1, 10);
// 定义模型矩阵
Matrix.setIdentityM(modelMatrix, 0);
// z轴平移-2.8
Matrix.translateM(modelMatrix, 0, 0, 0, -2.8f);
Matrix.rotateM(modelMatrix, 0, -60, 1f, 0f, 0f);
// 把投影矩阵和模型矩阵相乘
Matrix.multiplyMM(modelProjectionMatrix, 0, projectionMatrix, 0, modelMatrix, 0);
}
@Override
public void onDrawFrame(GL10 gl) {
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
mTextureProgram.setUniform(modelProjectionMatrix);
mTable.bindData(mTextureProgram);
mTable.draw();
mColorProgram.setUniform(modelProjectionMatrix);
mMallet.bindData(mColorProgram);
mMallet.draw();
}
}
显示如下