Android(安卓)OpenGL纹理
16lz
2021-01-26
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纹理的IDint[] 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) 绘制纹理 // 把活动的纹理单元设置为纹理单元0GLES20.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;@Overridepublic 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);}@Overridepublic 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.8Matrix.translateM(modelMatrix, 0, 0, 0, -2.8f);Matrix.rotateM(modelMatrix, 0, -60, 1f, 0f, 0f);// 把投影矩阵和模型矩阵相乘Matrix.multiplyMM(modelProjectionMatrix, 0, projectionMatrix, 0, modelMatrix, 0);}@Overridepublic 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();}}
显示如下 更多相关文章
- 【Android开发精要笔记】Android的Intent机制
- android内存问题详解--重要
- Android中的Cursor到底是什么?如何理解Cursor的方法都在做什么事
- 2016年末,Android岗位BAT等大厂面试题知识点小结(一)Android基础部
- Android监听ListView停止的时候是不是滑动到底部
- Android内存溢出整理总结
- [Android] Android开发优化之——从代码角度进行优化
- Android(安卓)用户界面---拖放(Drag and Drop)
- 面试时的那些坑之内存泄漏和内存溢出