作为 Android 美颜相机开发中的核心组件,GPUImageTwoInputFilter 承担着双纹理混合渲染的关键任务。这个类继承自 GPUImageFilter,在保留单纹理处理能力的基础上,新增了对第二纹理的支持。其架构设计体现了典型的 OpenGL ES 渲染管线思维,我们可以从三个维度来理解其设计哲学:
纹理处理流水线:
多纹理管理机制:
生命周期管控:
关键提示:在实际开发中,第二纹理单元的选择需要避开平台保留的纹理单元。某些设备上 GL_TEXTURE0-GL_TEXTURE2 可能被系统占用,这就是示例中选择 GL_TEXTURE3 的原因。
setBitmap() 方法的实现展示了 Android Bitmap 到 OpenGL 纹理的完整转换过程:
java复制public void setBitmap(final Bitmap bitmap) {
if (bitmap != null && bitmap.isRecycled()) {
return;
}
this.bitmap = bitmap;
if (this.bitmap == null) {
return;
}
runOnDraw(new Runnable() {
public void run() {
if (filterSourceTexture2 == OpenGlUtils.NO_TEXTURE) {
if (bitmap == null || bitmap.isRecycled()) {
return;
}
GLES20.glActiveTexture(GLES20.GL_TEXTURE3);
filterSourceTexture2 = OpenGlUtils.loadTexture(bitmap, OpenGlUtils.NO_TEXTURE, false);
}
}
});
}
这个流程包含几个关键技术点:
双输入滤镜的顶点着色器代码看似简单,却蕴含着重要的设计考量:
glsl复制attribute vec4 position;
attribute vec4 inputTextureCoordinate;
attribute vec4 inputTextureCoordinate2;
varying vec2 textureCoordinate;
varying vec2 textureCoordinate2;
void main()
{
gl_Position = position;
textureCoordinate = inputTextureCoordinate.xy;
textureCoordinate2 = inputTextureCoordinate2.xy;
}
关键设计要点:
onDrawArraysPre() 方法完成了渲染前的最后准备工作:
java复制@Override
protected void onDrawArraysPre() {
GLES20.glEnableVertexAttribArray(filterSecondTextureCoordinateAttribute);
GLES20.glActiveTexture(GLES20.GL_TEXTURE3);
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, filterSourceTexture2);
GLES20.glUniform1i(filterInputTextureUniform2, 3);
texture2CoordinatesBuffer.position(0);
GLES20.glVertexAttribPointer(filterSecondTextureCoordinateAttribute, 2, GLES20.GL_FLOAT, false, 0, texture2CoordinatesBuffer);
}
这个方法的操作顺序非常重要:
任何顺序错误都可能导致渲染异常,这是新手常见的错误点。
在移动设备上,纹理内存是非常宝贵的资源。GPUImageTwoInputFilter 提供了完善的内存管理机制:
java复制public void onDestroy() {
super.onDestroy();
GLES20.glDeleteTextures(1, new int[]{filterSourceTexture2}, 0);
filterSourceTexture2 = OpenGlUtils.NO_TEXTURE;
}
public void recycleBitmap() {
if (bitmap != null && !bitmap.isRecycled()) {
bitmap.recycle();
bitmap = null;
}
}
最佳实践建议:
OpenGL 操作必须发生在创建 OpenGL 上下文的线程。GPUImageTwoInputFilter 通过 runOnDraw 机制保证线程安全:
java复制runOnDraw(new Runnable() {
public void run() {
// OpenGL 操作
}
});
实际开发中需要注意:
虽然示例中使用的是默认纹理参数,但在实际项目中可以根据需求优化:
java复制// 在 loadTexture 后可以添加这些优化
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR);
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE);
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE);
参数选择建议:
在片段着色器中可以实现各种混合效果,以下是几种常见算法示例:
glsl复制gl_FragColor = color1 + color2;
glsl复制gl_FragColor = color1 * color2;
glsl复制gl_FragColor = 1.0 - (1.0 - color1) * (1.0 - color2);
glsl复制gl_FragColor = mix(color1, color2, color2.a);
glsl复制vec3 result;
if (color2.r > 0.5)
result.r = 1.0 - (1.0 - 2.0 * (color2.r - 0.5)) * (1.0 - color1.r);
else
result.r = 2.0 * color2.r * color1.r;
// 同样处理 g 和 b 分量
gl_FragColor = vec4(result, 1.0);
对于需要频繁更新的第二纹理(如视频帧),可以优化更新流程:
java复制public void updateBitmap(final Bitmap newBitmap) {
if (newBitmap == null || newBitmap.isRecycled()) {
return;
}
runOnDraw(new Runnable() {
public void run() {
if (filterSourceTexture2 == OpenGlUtils.NO_TEXTURE) {
// 首次创建纹理
GLES20.glActiveTexture(GLES20.GL_TEXTURE3);
filterSourceTexture2 = OpenGlUtils.loadTexture(newBitmap, OpenGlUtils.NO_TEXTURE, false);
} else {
// 更新现有纹理
GLES20.glActiveTexture(GLES20.GL_TEXTURE3);
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, filterSourceTexture2);
GLUtils.texSubImage2D(GLES20.GL_TEXTURE_2D, 0, 0, 0, newBitmap);
}
// 更新引用
if (bitmap != null && !bitmap.isRecycled()) {
bitmap.recycle();
}
bitmap = newBitmap;
}
});
}
这种实现避免了频繁创建/销毁纹理的开销,特别适合视频处理场景。
在实际项目中,我们需要监控双纹理渲染的性能表现。可以添加以下监控代码:
java复制// 在渲染循环开始前
long startTime = System.nanoTime();
// 在渲染循环结束后
long duration = (System.nanoTime() - startTime) / 1000000;
if (duration > 16) { // 超过16ms意味着可能掉帧
Log.w("Performance", "Frame rendering took " + duration + "ms");
}
// 检查GL错误
int error = GLES20.glGetError();
if (error != GLES20.GL_NO_ERROR) {
Log.e("GL Error", "OpenGL error: " + error);
}
常见性能优化手段:
当第二纹理显示不正常时,可以按照以下步骤排查:
检查纹理加载:
检查着色器:
检查渲染流程:
双纹理渲染中常见的内存问题包括:
Bitmap 未回收:
纹理未删除:
缓冲区未释放:
不同 Android 设备上的 OpenGL 实现可能有差异,需要注意:
纹理尺寸限制:
扩展支持:
精度问题:
在美颜相机开发中,GPUImageTwoInputFilter 有多种典型应用场景:
java复制// 创建贴纸滤镜
GPUImageTwoInputFilter stickerFilter = new GPUImageTwoInputFilter(
"precision mediump float;" +
"varying vec2 textureCoordinate;" +
"varying vec2 textureCoordinate2;" +
"uniform sampler2D inputImageTexture;" +
"uniform sampler2D inputImageTexture2;" +
"void main() {" +
" vec4 base = texture2D(inputImageTexture, textureCoordinate);" +
" vec4 sticker = texture2D(inputImageTexture2, textureCoordinate2);" +
" gl_FragColor = mix(base, sticker, sticker.a);" +
"}"
);
// 加载贴纸
Bitmap stickerBitmap = BitmapFactory.decodeResource(resources, R.drawable.sticker);
stickerFilter.setBitmap(stickerBitmap);
// 添加到处理链
gpuImage.setFilter(stickerFilter);
java复制// 创建遮罩滤镜
GPUImageTwoInputFilter maskFilter = new GPUImageTwoInputFilter(
"precision highp float;" +
"uniform sampler2D inputImageTexture;" +
"uniform sampler2D inputImageTexture2;" +
"varying vec2 textureCoordinate;" +
"varying vec2 textureCoordinate2;" +
"void main() {" +
" vec4 origin = texture2D(inputImageTexture, textureCoordinate);" +
" vec4 mask = texture2D(inputImageTexture2, textureCoordinate2);" +
" float intensity = mask.r * 0.3 + mask.g * 0.59 + mask.b * 0.11;" +
" origin.rgb = origin.rgb * (1.0 + intensity * 0.5);" + // 提亮效果
" gl_FragColor = origin;" +
"}"
);
// 加载遮罩图
Bitmap maskBitmap = createVignetteMask(width, height);
maskFilter.setBitmap(maskBitmap);
java复制// 创建混合滤镜
GPUImageTwoInputFilter blendFilter = new GPUImageTwoInputFilter(
"uniform sampler2D inputImageTexture;" +
"uniform sampler2D inputImageTexture2;" +
"uniform float mixRatio;" + // 动态混合比例
"varying vec2 textureCoordinate;" +
"varying vec2 textureCoordinate2;" +
"void main() {" +
" vec4 color1 = texture2D(inputImageTexture, textureCoordinate);" +
" vec4 color2 = texture2D(inputImageTexture2, textureCoordinate2);" +
" gl_FragColor = mix(color1, color2, mixRatio);" +
"}"
);
// 设置第二纹理
blendFilter.setBitmap(effectBitmap);
// 动态更新混合比例
blendFilter.setFloat("mixRatio", 0.5f); // 0-1之间变化
在实际项目中,我们可以组合多个 GPUImageTwoInputFilter 实现复杂的效果链,每个滤镜处理特定的效果,最终叠加出专业级的美颜相机效果。