以下是基于OpenGL ES 3.0的Android图像绘制实现方案,支持灰度图、RGB、YUV420和YUYV格式。核心思路是通过多纹理绑定和着色器转换处理不同格式,实现高效GPU渲染。
整体实现步骤
- 创建GLSurfaceView:作为OpenGL渲染容器
- 自定义Renderer:实现GLSurfaceView.Renderer接口
- 多纹理管理:根据格式创建1-3个纹理
- 着色器转换:在片段着色器中完成格式转换
核心代码实现
- MyGLRenderer.java
package com.example.openglimaging;
import android.opengl.GLES30;
import android.opengl.GLSurfaceView;
import android.opengl.Matrix;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;
public class MyGLRenderer implements GLSurfaceView.Renderer {
// 格式常量
public static final int FORMAT_GRAY = 0;
public static final int FORMAT_RGB = 1;
public static final int FORMAT_YUV420 = 2;
public static final int FORMAT_YUYV = 3;
// 顶点和纹理坐标
private static final float[] VERTEX_COORDS = {
-1.0f, 1.0f, 0.0f, // 左上
-1.0f, -1.0f, 0.0f, // 左下
1.0f, 1.0f, 0.0f, // 右上
1.0f, -1.0f, 0.0f // 右下
};
private static final float[] TEX_COORDS = {
0.0f, 0.0f, // 左下
0.0f, 1.0f, // 左上
1.0f, 0.0f, // 右下
1.0f, 1.0f // 右上
};
private FloatBuffer vertexBuffer, texCoordBuffer;
private int program;
private int[] textures = new int[3]; // 最多3个纹理
private int uFormatLocation;
// 图像参数
private ByteBuffer imageBuffer;
private int imageWidth, imageHeight;
private int imageFormat = FORMAT_RGB;
// 投影矩阵
private final float[] mvpMatrix = new float[16];
private int uMVPMatrixLocation;
public MyGLRenderer() {
// 初始化顶点缓冲区
vertexBuffer = ByteBuffer.allocateDirect(VERTEX_COORDS.length * 4)
.order(ByteOrder.nativeOrder())
.asFloatBuffer();
vertexBuffer.put(VERTEX_COORDS).position(0);
// 初始化纹理坐标缓冲区
texCoordBuffer = ByteBuffer.allocateDirect(TEX_COORDS.length * 4)
.order(ByteOrder.nativeOrder())
.asFloatBuffer();
texCoordBuffer.put(TEX_COORDS).position(0);
}
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
// 设置背景色
GLES30.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
// 编译着色器
int vertexShader = loadShader(GLES30.GL_VERTEX_SHADER, VERTEX_SHADER_CODE);
int fragmentShader = loadShader(GLES30.GL_FRAGMENT_SHADER, FRAGMENT_SHADER_CODE);
program = GLES30.glCreateProgram();
GLES30.glAttachShader(program, vertexShader);
GLES30.glAttachShader(program, fragmentShader);
GLES30.glLinkProgram(program);
GLES30.glUseProgram(program);
// 获取uniform位置
uMVPMatrixLocation = GLES30.glGetUniformLocation(program, "uMVPMatrix");
uFormatLocation = GLES30.glGetUniformLocation(program, "uFormat");
// 获取纹理位置
int tex0Location = GLES30.glGetUniformLocation(program, "uTexture0");
int tex1Location = GLES30.glGetUniformLocation(program, "uTexture1");
int tex2Location = GLES30.glGetUniformLocation(program, "uTexture2");
// 设置纹理单元
GLES30.glUniform1i(tex0Location, 0);
GLES30.glUniform1i(tex1Location, 1);
GLES30.glUniform1i(tex2Location, 2);
// 创建纹理
GLES30.glGenTextures(3, textures, 0);
for (int i = 0; i < 3; i++) {
GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, textures[i]);
// 设置纹理参数
GLES30.glTexParameteri(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_MIN_FILTER, GLES30.GL_LINEAR);
GLES30.glTexParameteri(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_MAG_FILTER, GLES30.GL_LINEAR);
GLES30.glTexParameteri(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_WRAP_S, GLES30.GL_CLAMP_TO_EDGE);
GLES30.glTexParameteri(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_WRAP_T, GLES30.GL_CLAMP_TO_EDGE);
}
// 初始化矩阵
Matrix.setIdentityM(mvpMatrix, 0);
}
@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
GLES30.glViewport(0, 0, width, height);
// 更新投影矩阵(这里使用正交投影)
Matrix.orthoM(mvpMatrix, 0, -1, 1, -1, 1, -1, 1);
}
@Override
public void onDrawFrame(GL10 gl) {
GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT);
// 传递MVP矩阵
GLES30.glUniformMatrix4fv(uMVPMatrixLocation, 1, false, mvpMatrix, 0);
// 传递格式
GLES30.glUniform1i(uFormatLocation, imageFormat);
// 绑定顶点坐标
int aPosition = GLES30.glGetAttribLocation(program, "aPosition");
GLES30.glEnableVertexAttribArray(aPosition);
GLES30.glVertexAttribPointer(aPosition, 3, GLES30.GL_FLOAT, false, 0, vertexBuffer);
// 绑定纹理坐标
int aTexCoord = GLES30.glGetAttribLocation(program, "aTexCoord");
GLES30.glEnableVertexAttribArray(aTexCoord);
GLES30.glVertexAttribPointer(aTexCoord, 2, GLES30.GL_FLOAT, false, 0, texCoordBuffer);
// 绑定纹理数据
bindTextureData();
// 绘制
GLES30.glDrawArrays(GLES30.GL_TRIANGLE_STRIP, 0, 4);
// 禁用顶点数组
GLES30.glDisableVertexAttribArray(aPosition);
GLES30.glDisableVertexAttribArray(aTexCoord);
}
// 更新图像数据
public void updateImage(byte[] data, int width, int height, int format) {
if (data == null) return;
imageWidth = width;
imageHeight = height;
imageFormat = format;
imageBuffer = ByteBuffer.allocateDirect(data.length);
imageBuffer.put(data).position(0);
}
// 根据格式绑定纹理
private void bindTextureData() {
if (imageBuffer == null) return;
switch (imageFormat) {
case FORMAT_GRAY:
bindGrayTexture();
break;
case FORMAT_RGB:
bindRGBTexture();
break;
case FORMAT_YUV420:
bindYUV420Texture();
break;
case FORMAT_YUYV:
bindYUYVTexture();
break;
}
}
private void bindGrayTexture() {
GLES30.glActiveTexture(GLES30.GL_TEXTURE0);
GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, textures[0]);
GLES30.glTexImage2D(GLES30.GL_TEXTURE_2D, 0, GLES30.GL_LUMINANCE,
imageWidth, imageHeight, 0,
GLES30.GL_LUMINANCE, GLES30.GL_UNSIGNED_BYTE, imageBuffer);
}
private void bindRGBTexture() {
GLES30.glActiveTexture(GLES30.GL_TEXTURE0);
GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, textures[0]);
GLES30.glTexImage2D(GLES30.GL_TEXTURE_2D, 0, GLES30.GL_RGB,
imageWidth, imageHeight, 0,
GLES30.GL_RGB, GLES30.GL_UNSIGNED_BYTE, imageBuffer);
}
private void bindYUV420Texture() {
// Y分量
GLES30.glActiveTexture(GLES30.GL_TEXTURE0);
GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, textures[0]);
GLES30.glTexImage2D(GLES30.GL_TEXTURE_2D, 0, GLES30.GL_LUMINANCE,
imageWidth, imageHeight, 0,
GLES30.GL_LUMINANCE, GLES30.GL_UNSIGNED_BYTE, imageBuffer);
// U分量 (宽度高度减半)
int uvSize = imageWidth * imageHeight / 4;
ByteBuffer uBuffer = ByteBuffer.allocateDirect(uvSize);
imageBuffer.position(imageWidth * imageHeight);
for (int i = 0; i < uvSize; i++) {
uBuffer.put(imageBuffer.get());
}
uBuffer.position(0);
GLES30.glActiveTexture(GLES30.GL_TEXTURE1);
GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, textures[1]);
GLES30.glTexImage2D(GLES30.GL_TEXTURE_2D, 0, GLES30.GL_LUMINANCE,
imageWidth/2, imageHeight/2, 0,
GLES30.GL_LUMINANCE, GLES30.GL_UNSIGNED_BYTE, uBuffer);
// V分量
ByteBuffer vBuffer = ByteBuffer.allocateDirect(uvSize);
imageBuffer.position(imageWidth * imageHeight + uvSize);
for (int i = 0; i < uvSize; i++) {
vBuffer.put(imageBuffer.get());
}
vBuffer.position(0);
GLES30.glActiveTexture(GLES30.GL_TEXTURE2);
GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, textures[2]);
GLES30.glTexImage2D(GLES30.GL_TEXTURE_2D, 0, GLES30.GL_LUMINANCE,
imageWidth/2, imageHeight/2, 0,
GLES30.GL_LUMINANCE, GLES30.GL_UNSIGNED_BYTE, vBuffer);
}
private void bindYUYVTexture() {
// 将YUYV数据作为RGBA纹理上传(每4字节表示两个像素)
GLES30.glActiveTexture(GLES30.GL_TEXTURE0);
GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, textures[0]);
GLES30.glTexImage2D(GLES30.GL_TEXTURE_2D, 0, GLES30.GL_RGBA,
imageWidth/2, imageHeight, 0,
GLES30.GL_RGBA, GLES30.GL_UNSIGNED_BYTE, imageBuffer);
}
// 加载着色器
private int loadShader(int type, String shaderCode) {
int shader = GLES30.glCreateShader(type);
GLES30.glShaderSource(shader, shaderCode);
GLES30.glCompileShader(shader);
return shader;
}
// 着色器代码
private static final String VERTEX_SHADER_CODE =
"#version 300 es\n" +
"uniform mat4 uMVPMatrix;\n" +
"layout(location=0) in vec4 aPosition;\n" +
"layout(location=1) in vec2 aTexCoord;\n" +
"out vec2 vTexCoord;\n" +
"void main() {\n" +
" gl_Position = uMVPMatrix * aPosition;\n" +
" vTexCoord = aTexCoord;\n" +
"}";
private static final String FRAGMENT_SHADER_CODE =
"#version 300 es\n" +
"precision mediump float;\n" +
"uniform int uFormat;\n" +
"uniform sampler2D uTexture0;\n" + // 灰度/RGB/Y分量
"uniform sampler2D uTexture1;\n" + // U分量(YUV420)
"uniform sampler2D uTexture2;\n" + // V分量(YUV420)
"in vec2 vTexCoord;\n" +
"out vec4 outColor;\n" +
// 格式常量(与Java中定义一致)
"#define FORMAT_GRAY 0\n" +
"#define FORMAT_RGB 1\n" +
"#define FORMAT_YUV420 2\n" +
"#define FORMAT_YUYV 3\n" +
"vec4 yuvToRgb(float y, float u, float v) {\n" +
" // YUV转RGB公式(BT.601标准)\n" +
" y = 1.1643*(y - 0.0625);\n" +
" u = u - 0.5;\n" +
" v = v - 0.5;\n" +
" return vec4(\n" +
" clamp(y + 1.5958*v, 0.0, 1.0),\n" +
" clamp(y - 0.39173*u - 0.81290*v, 0.0, 1.0),\n" +
" clamp(y + 2.017*u, 0.0, 1.0),\n" +
" 1.0\n" +
" );\n" +
"}\n" +
"vec4 processYUYV() {\n" +
" // 计算实际采样位置(纹理宽度是原图一半)\n" +
" vec2 realCoord = vec2(vTexCoord.x * 2.0, vTexCoord.y);\n" +
" \n" +
" // 采样RGBA纹理(每个纹素包含两个像素的数据)\n" +
" vec4 yuyv = texture(uTexture0, vec2(realCoord.x * 0.5, realCoord.y));\n" +
" \n" +
" float y, u, v;\n" +
" if (fract(realCoord.x) < 0.5) {\n" + // 左像素
" y = yuyv.r;\n" +
" u = yuyv.g;\n" +
" v = yuyv.a;\n" +
" } else { // 右像素\n" +
" y = yuyv.b;\n" +
" u = yuyv.g;\n" +
" v = yuyv.a;\n" +
" }\n" +
" return yuvToRgb(y, u, v);\n" +
"}\n" +
"void main() {\n" +
" switch(uFormat) {\n" +
" case FORMAT_GRAY:\n" +
" float gray = texture(uTexture0, vTexCoord).r;\n" +
" outColor = vec4(gray, gray, gray, 1.0);\n" +
" break;\n" +
" \n" +
" case FORMAT_RGB:\n" +
" outColor = texture(uTexture0, vTexCoord);\n" +
" break;\n" +
" \n" +
" case FORMAT_BGR:\n" +
" vec4 bgrColor = texture(uTexture0, vTexCoord);\n" +
" outColor = vec4(bgrColor.b, bgrColor.g, bgrColor.r, 1.0);\n" +
" break;\n" +
" \n" +
" case FORMAT_YUV420:\n" +
" float y = texture(uTexture0, vTexCoord).r;\n" +
" float u = texture(uTexture1, vTexCoord).r;\n" +
" float v = texture(uTexture2, vTexCoord).r;\n" +
" outColor = yuvToRgb(y, u, v);\n" +
" break;\n" +
" \n" +
" case FORMAT_YUYV:\n" +
" outColor = processYUYV();\n" +
" break;\n" +
" \n" +
" default:\n" + // 默认显示红色
" outColor = vec4(1.0, 0.0, 0.0, 1.0);\n" +
" }\n" +
"}";
}
- 使用示例
package com.example.openglimaging;
import android.opengl.GLSurfaceView;
import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;
public class MainActivity extends AppCompatActivity {
private GLSurfaceView glSurfaceView;
private MyGLRenderer renderer;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 创建OpenGL ES 3.0上下文
glSurfaceView = new GLSurfaceView(this);
glSurfaceView.setEGLContextClientVersion(3);
renderer = new MyGLRenderer();
glSurfaceView.setRenderer(renderer);
setContentView(glSurfaceView);
}
@Override
protected void onPause() {
super.onPause();
glSurfaceView.onPause();
}
@Override
protected void onResume() {
super.onResume();
glSurfaceView.onResume();
}
// 更新图像数据接口
public void updateImage(byte[] data, int width, int height, int format) {
renderer.updateImage(data, width, height, format);
glSurfaceView.requestRender(); // 请求重绘
}
}
关键点说明
- 多格式支持:
- 灰度图:使用单纹理的Luminance格式
- RGB:标准RGB三通道纹理
- YUV420:分离为三个纹理(Y、U、V平面)
- YUYV:作为RGBA纹理上传(每4字节表示两个像素)
- 性能优化:
- 使用ByteBuffer避免数据拷贝
- 在GPU端完成YUV转换
- 纹理使用GL_LINEAR过滤和CLAMP_TO_EDGE环绕模式
- 着色器处理:
- 根据uFormat统一变量切换处理逻辑
- YUV420使用三个独立的纹理采样器
- YUYV采用奇偶像素分离算法
- 内存管理:
- 在updateImage中更新图像数据
- 请求重绘requestRender()
1280

被折叠的 条评论
为什么被折叠?



