Android OpenGL ES:展示一张2d图片
Posted MichaelX_Blog
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android OpenGL ES:展示一张2d图片相关的知识,希望对你有一定的参考价值。
前言
本文的环境和条件:GLSurfaceView + OpenGL ES 2.0
代码结构
- Activity视图使用GLSurfaceView;
- GLSurfaceView使用自定义GLSurfaceView.Render实现,
(GLSurfaceView.setRender())
; - 在Render实现图片纹理化和渲染。
代码Show you the code
自定义的GLSurfaceView.Renderer实现。
public class BitmapRender implements GLSurfaceView.Renderer
private static final String VERTEX_SHADER =
"uniform mat4 uMVPMatrix;" +
"attribute vec4 aPosition;" +
"attribute vec2 aTexCoord;" +
"varying vec2 vTexCoord;" +
"void main() " +
" gl_Position = uMVPMatrix * aPosition;" +
" vTexCoord = aTexCoord;" +
"";
private static final String FRAGMENT_SHADER =
"precision lowp float;" +
"precision highp sampler2D;" +
"precision highp int;" +
"uniform sampler2D uTextureUnit;" +
"varying vec2 vTexCoord;" +
"void main() " +
" gl_FragColor = texture2D(uTextureUnit, vTexCoord);" +
"";
private static final int COORDS_PRE_VERTEX = 2;
private static final int COORDS_PRE_TEXTURE = 2;
// 顶点坐标:世界坐标系
private static final float[] VERTEX =
1, 1, // right, top
-1, 1, // left, top
1, -1, // right, bottom
-1, -1 // left, bottom
;
// 纹理坐标
private static final float[] TEXTUER =
1, 0.f, // right, top
0.f, 0.f, // left, top
1, 1, // right, bottom
0.f, 1, // left, bottom
;
private FloatBuffer mVertexBuffer;
private FloatBuffer mTextureBuffer;
private float[] mMVPMatrix = new float[16];
private int mProgram;
private int mMVPMatrixHandle;
private int mPositionHandle;
private int mTexCoordHandle;
private Bitmap bitmap;
private int mBitmapWidth;
private int mBitmapHeight;
private int texUnitHandle;
private int imageTexture = -1;
public BitmapRender(String imagePath)
mVertexBuffer = GLESUtils.createFloatBuffer(VERTEX);
mTextureBuffer = GLESUtils.createFloatBuffer(TEXTUER);
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config)
//GLES20.glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
int vertexShader = GLESUtils.createVertexShader(VERTEX_SHADER);
int fragmentShader = GLESUtils.createFragmentShader(FRAGMENT_SHADER);
mProgram = GLESUtils.createProgram(vertexShader, fragmentShader);
// GLES20.glGetAttribLocation方法:获取着色器程序中,指定为attribute类型变量的id。
mPositionHandle = GLES20.glGetAttribLocation(mProgram, "aPosition");
// GLES20.glGetUniformLocation方法:获取着色器程序中,指定为uniform类型变量的id
mMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix");
mTexCoordHandle = GLES20.glGetAttribLocation(mProgram, "aTexCoord");
texUnitHandle = GLES20.glGetUniformLocation(mProgram, "uTextureUnit");
int viewWidth;
int viewHeight;
@Override
public void onSurfaceChanged(GL10 gl, int width, int height)
GLES20.glViewport(0, 0, width, height);
this.viewWidth = width;
this.viewHeight = height;
public void setBitmap(Bitmap bitmap)
this.bitmap = bitmap;
mBitmapWidth = bitmap.getWidth();
mBitmapHeight = bitmap.getHeight();
mVertexBuffer = GLESUtils.createFloatBuffer(VERTEX);
mTextureBuffer = GLESUtils.createFloatBuffer(TEXTUER);
mMVPMatrix = GLESUtils.changeMVPMatrixInside(viewWidth, viewHeight, mBitmapWidth, mBitmapHeight);
@Override
public void onDrawFrame(GL10 gl)
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT);
drawBitmap();
// 绘制完毕,关闭顶点数据数组、解除纹理绑定等
GLES20.glDisableVertexAttribArray(mPositionHandle);
GLES20.glDisableVertexAttribArray(mTexCoordHandle);
GLES20.glUseProgram(0);
private void drawBitmap()
if (bitmap != null)
// 创建图片纹理
imageTexture = GLESUtils.createImageTexture(bitmap);
else
return;
GLES20.glUseProgram(mProgram);
// 设置顶点变换矩阵数据
GLES20.glUniformMatrix4fv(mMVPMatrixHandle, 1, false, mMVPMatrix, 0);
// 设置顶点坐标
GLES20.glEnableVertexAttribArray(mPositionHandle);
// 为顶点属性赋值
GLES20.glVertexAttribPointer(mPositionHandle, COORDS_PRE_TEXTURE, GLES20.GL_FLOAT, true, 0, mVertexBuffer);
// 设置纹理坐标
GLES20.glEnableVertexAttribArray(mTexCoordHandle);
// 纹理坐标数据
GLES20.glVertexAttribPointer(mTexCoordHandle, COORDS_PRE_TEXTURE, GLES20.GL_FLOAT, true, 0, mTextureBuffer);
// 激活纹理单元
GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
// 绑定纹理
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, imageTexture);
// 把选定的纹理单元传给片段着色器:0对应激活的GL_TEXTURE0
// 完成纹理id和采样器的关联绑定
GLES20.glUniform1i(texUnitHandle, 0);
// 图形绘制
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, VERTEX.length / COORDS_PRE_VERTEX);
工具类GLESUtils
/**
* @description: OpenGL ES工具类
* @author: MichaelX
* @date: 2021/1/14
*/
public class GLESUtils
public static final float[] IDENTITY_MATRIX = new float[16];
public static final int SIZE_OF_FLOAT = 4;
private static final String TAG = "GLESUtils";
// 根据坐标创建FloatBuffer数据
public static FloatBuffer createFloatBuffer(float[] coords)
ByteBuffer bb = ByteBuffer.allocateDirect(coords.length * SIZE_OF_FLOAT);
bb.order(ByteOrder.nativeOrder());
FloatBuffer fb = bb.asFloatBuffer();
fb.put(coords);
fb.position(0);
return fb;
// 创建顶点shader
public static int createVertexShader(String vertexShader)
return createShader(GLES20.GL_VERTEX_SHADER, vertexShader);
// 创建片元shader
public static int createFragmentShader(String fragmentShader)
return createShader(GLES20.GL_FRAGMENT_SHADER, fragmentShader);
// 创建shader的通用方法
private static int createShader(int type, String shaderScript)
int shader = GLES20.glCreateShader(type);
GLES20.glShaderSource(shader, shaderScript);
// 编译shader
GLES20.glCompileShader(shader);
int[] compileStatus = new int[1];
// 检测编译结果
GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compileStatus, 0);
if (compileStatus[0] == GLES20.GL_FALSE)
Log.e(TAG, "compile shader: " + type + ", error: " + GLES20.glGetShaderInfoLog(shader));
GLES20.glDeleteShader(shader);
shader = 0;
return shader;
// 创建program
public static int createProgram(int vertexShader, int fragmentShader)
if (vertexShader == 0 || fragmentShader == 0)
Log.e(TAG, "shader can't be 0!");
return 0;
int program = GLES20.glCreateProgram();
checkGlError("glCreateProgram");
// 关联顶点着色器
GLES20.glAttachShader(program, vertexShader);
checkGlError("glAttachShader");
// 管理片元着色器
GLES20.glAttachShader(program, fragmentShader);
checkGlError("glAttachShader");
// 链接整个管线流程
GLES20.glLinkProgram(program);
int linkStatus[] = new int[1];
// 检查链接状态
GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, linkStatus, 0);
if (linkStatus[0] != GLES20.GL_TRUE)
Log.e(TAG, "link program error: " + GLES20.glGetProgramInfoLog(program));
GLES20.glDeleteProgram(program);
program = 0;
return program;
private static void checkGlError(String op)
int error = GLES20.glGetError();
if (error != GLES20.GL_NO_ERROR)
String msg = op + ": glError 0x" + Integer.toHexString(error);
Log.e(TAG, "checkGlError: " + msg);
// 利用图片创建纹理id
public static int createImageTexture(Bitmap bitmap)
int[] texture = new int[1];
if (bitmap != null && !bitmap.isRecycled())
//生成纹理
GLES20.glGenTextures(1, texture, 0);
//绑定纹理
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, texture[0]);
/**配置纹理属性,主要是纹理采样边缘的处理**/
//设置缩小过滤为使用纹理中坐标最接近的一个像素的颜色作为需要绘制的像素颜色
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST);
//设置放大过滤为使用纹理中坐标最接近的若干个颜色,通过加权平均算法得到需要绘制的像素颜色
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
//设置环绕方向S,截取纹理坐标到[1/2n,1-1/2n]。将导致永远不会与border融合
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE);
//设置环绕方向T,截取纹理坐标到[1/2n,1-1/2n]。将导致永远不会与border融合
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE);
//根据以上指定的参数,生成一个2D纹理。上传图片数据到纹理对象中
GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bitmap, 0);
// 生成 MIP 贴图
GLES20.glGenerateMipmap(GLES20.GL_TEXTURE_2D);
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, GLES20.GL_NONE);
return texture[0];
return 0;
public static float[] changeMVPMatrixInside(float viewWidth, float viewHeight, float textureWidth, float textureHeight)
float scale = viewWidth * textureHeight / viewHeight / textureWidth;
float[] mvp = new float[16];
Matrix.setIdentityM(mvp, 0);
Matrix.scaleM(mvp, 0, scale > 1 ? (1F / scale) : 1F, scale > 1 ? 1F : scale, 1F);
return mvp;
public static float[] changeMvpMatrixCrop(float viewWidth, float viewHeight, float textureWidth, float textureHeight)
float scale = viewWidth * textureHeight / viewHeight / textureWidth;
float[] mvp = new float[16];
Matrix.setIdentityM(mvp, 0);
Matrix.scaleM(mvp, 0, scale > 1 ? 1F : (1F / scale), scale > 1 ? scale : 1F, 1F);
return mvp;
// 获取设备支持的OpenGL ES版本号
public static int getSupportGLVersion(Context context)
final ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
final ConfigurationInfo configurationInfo = activityManager.getDeviceConfigurationInfo();
int version = configurationInfo.reqGlEsVersion >= 0x30000 ? 3 : 2;
String glEsVersion = configurationInfo.getGlEsVersion();
Log.d(TAG, "reqGlEsVersion: " + Integer.toHexString(configurationInfo.reqGlEsVersion)
+ ", glEsVersion: " + glEsVersion + ", return: " + version);
return version;
注意事项
很多示例代码没有上面的mMVPMatrixHandle
和mMVPMatrix
,这个是为了不让图片展示变形的mvp矩阵,否则会因为坐标系的转换而显示图片宽高比例变形。
以上是关于Android OpenGL ES:展示一张2d图片的主要内容,如果未能解决你的问题,请参考以下文章
Android - 使用 openGL ES 绘制 3D 然后 2D
在 Android OpenGL ES 中将 2D 纹理保存为 png
我的OpenGL学习进阶之旅如何在Android中使用ARCore结合OpenGL ES来实现增强人脸Augmented Faces?
我的OpenGL学习进阶之旅如何在Android中使用ARCore结合OpenGL ES来实现增强人脸Augmented Faces?