大家都知道,给图片加滤镜加特效,通常是对图像进行矩阵运算。通过颜色矩阵的乘法,我们可以对图像中的元素进行变换。
但是,如果需要对实时变化的图像进行实时处理,就不是每种图像变换的方式都可以用了。因为,实时变化的预览图像,会有帧率的压力,我们的处理一定要快。
在上一篇中,我们已经展示了Android中,通过OpenGL展示相机预览图片的方法。
这一篇主要展示,如何在预览的图片中,加入一些简单的特效。
QQ20180805-232214-HD.gif
这个特效的详细过程是,点击屏幕时,会在屏幕中间显示一个画中画,然后,画中画慢慢放大,逐渐透明。同时,原始预览图层的颜色不断随机变化。
为了方便浏览,我将代码写的比较简单,完全没有考虑扩展性和封装相关的问题。而且,只展示了onDrawFrame生命周期的代码。同时,用animValue控制动画的进度。
首先,我们来看一下着色器的代码。由于特效既需要形变,也需要颜色变化,我们在gl_Position和gl_FragColor中,都引入了一个变化矩阵。
private final String vertexShaderCode = "uniform mat4 textureTransform;\n" +
"attribute vec2 inputTextureCoordinate;\n" +
"attribute vec4 position; \n" +//NDK坐标点
"varying vec2 textureCoordinate; \n" +//纹理坐标点变换后输出
"uniform mat4 position_transform_matrix;\n" +
"\n" +
" void main() {\n" +
" gl_Position = position_transform_matrix * vec4(position.x,-position.y,position.z,position.w);\n" +
" textureCoordinate = vec2(inputTextureCoordinate.x,inputTextureCoordinate.y);\n" +
" }";
private final String fragmentShaderCode = "#extension GL_OES_EGL_image_external : require\n" +
"precision mediump float;\n" +
"uniform samplerExternalOES videoTex;\n" +
"varying vec2 textureCoordinate;\n" +
"uniform mat4 color_transform_matrix;\n" +
"\n" +
"void main() {\n" +
" vec4 tc = texture2D(videoTex, textureCoordinate);\n" +
" vec4 color = vec4(tc.r,tc.g,tc.b,tc.a); \n"+
" gl_FragColor = color_transform_matrix * color;\n" +
// " gl_FragColor = vec4(tc.r,tc.g,tc.b,1.0);\n" +
"}";
我们通过改变position_transform_matrix,来进行形变。通过改变color_transform_matrix,来进行色彩的变换。
以下是点击事件的代码,我们会在点击后,周期性地传入一个随机的颜色矩阵,用于颜色的变换。
mCameraGlsurfaceView.setOnClickListener {
val timer = Timer()
var i = 0;
timer.schedule(object : TimerTask() {
override fun run() {
i++
LogUtils.d("glsurfaceview frame update i : $i")
glRenderer.animValue = i/20.0f;
glRenderer.animing = true
glRenderer.setFilter(object :AbsFilter {
override fun getColorMatrix():FloatArray{
var v1:Float = Math.random().toFloat()
var v2:Float = Math.random().toFloat()
var v3:Float = Math.random().toFloat()
return floatArrayOf(
v1, 0f, 0f, 0f,
0f, v2, 0f, 0f,
0f, 0f, v3, 0f,
0f, 0f, 0f, i/20.0f)
}
})
if (i == 20){
glRenderer.animing = false
glRenderer.animValue = 0.001f
glRenderer.setFilter(object :AbsFilter {
override fun getColorMatrix():FloatArray{
return floatArrayOf(
1f, 0f, 0f, 0f,
0f, 1f, 0f, 0f,
0f, 0f, 1f, 0f,
0f, 0f, 0f, 1f)
}
})
timer.cancel()
}
}
}, 0, 20)
}
以下是onDrawFrame
原始预览图层的颜色变化:
@Override
public void onDrawFrame(GL10 gl) {
activeProgram();
if (mSurfaceTexture != null) {
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT);
mSurfaceTexture.updateTexImage();
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, mPosCoordinate.length / 2);
}
renderEffect();
}
private FloatBuffer convertFloatBuffer (float[] vectices , int sizeByte){
FloatBuffer floatBuffer;
floatBuffer = ByteBuffer.allocateDirect(
vectices.length*sizeByte)
.order(ByteOrder.nativeOrder()).asFloatBuffer();
floatBuffer.put(vectices).position(0);
return floatBuffer;
}
private float[] createColorTransVertices(){
if (mEffectFilter == null){
return new float[] {
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1
};
}
return mEffectFilter.getColorMatrix();
}
private void activeProgram() {
// 将程序添加到OpenGL ES环境
GLES20.glUseProgram(mProgram);
mSurfaceTexture.setOnFrameAvailableListener(mOnFrameAvailableListener);
// 获取顶点着色器的位置的句柄
uPosHandle = GLES20.glGetAttribLocation(mProgram, "position");
aTexHandle = GLES20.glGetAttribLocation(mProgram, "inputTextureCoordinate");
FloatBuffer mPosBuffer = convertToFloatBuffer(mPosCoordinate);
FloatBuffer mTexBuffer;
if(camera_status == 0){
mTexBuffer = convertToFloatBuffer(mTexCoordinateBackRight);
}else{
mTexBuffer = convertToFloatBuffer(mTexCoordinateForntRight);
}
GLES20.glVertexAttribPointer(uPosHandle, 2, GLES20.GL_FLOAT, false, 0, mPosBuffer);
GLES20.glVertexAttribPointer(aTexHandle, 2, GLES20.GL_FLOAT, false, 0, mTexBuffer);
// 启用顶点位置的句柄
GLES20.glEnableVertexAttribArray(uPosHandle);
GLES20.glEnableVertexAttribArray(aTexHandle);
FloatBuffer mColorTransMatrixBuffer = convertFloatBuffer(createColorTransVertices() , 4);
GLES20.glUniformMatrix4fv(mColorTransMatrixHandler , 1 , false , mColorTransMatrixBuffer);
GLES20.glEnableVertexAttribArray(mColorTransMatrixHandler);
float[] posTrans = new float[] {
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1
};
FloatBuffer mPosTransMatrixBuffer = convertFloatBuffer(posTrans , 4);
GLES20.glUniformMatrix4fv(mPosTransMatrixHandler , 1 , false , mPosTransMatrixBuffer);
GLES20.glEnableVertexAttribArray(mPosTransMatrixHandler);
}
可以看到,在原始画面的渲染中,我们的pos转换矩阵,使用了单位矩阵。而颜色转换矩阵,则使用了mEffectFilter.getColorMatrix(),即外部传入的颜色矩阵,进行随机颜色变换。
接下来,是画中画特效的渲染过程:
private void renderEffect(){
// --- for click effect
FloatBuffer mPosBuffer = convertToFloatBuffer(mPosCoordinate);
FloatBuffer mTexBuffer;
if(camera_status == 0){
mTexBuffer = convertToFloatBuffer(mTexCoordinateBackRight);
}else{
mTexBuffer = convertToFloatBuffer(mTexCoordinateForntRight);
}
GLES20.glVertexAttribPointer(uPosHandle, 2, GLES20.GL_FLOAT, false, 0, mPosBuffer);
GLES20.glVertexAttribPointer(aTexHandle, 2, GLES20.GL_FLOAT, false, 0, mTexBuffer);
// 启用顶点位置的句柄
GLES20.glEnableVertexAttribArray(uPosHandle);
GLES20.glEnableVertexAttribArray(aTexHandle);
float[] colorTrans = new float[] {
1.0f, 0, 0, 0,
0, 1.0f, 0, 0,
0, 0, 1.0f, 0,
0, 0, 0, 0.5f*(1- animValue /1.0f)
};
// 半透明显示,需要开启
GLES20.glEnable(GLES20.GL_BLEND);
GLES20.glBlendFunc(GLES20.GL_SRC_ALPHA, GLES20.GL_ONE_MINUS_SRC_ALPHA);
FloatBuffer mColorTransMatrixBuffer = convertFloatBuffer(colorTrans , 4);
GLES20.glUniformMatrix4fv(mColorTransMatrixHandler , 1 , false , mColorTransMatrixBuffer);
GLES20.glEnableVertexAttribArray(mColorTransMatrixHandler);
float[] posTrans = new float[] {
1.0f, 0, 0, 0,
0, 1.0f, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1
};
if (animing){
posTrans = new float[] {
0.5f+ animValue, 0, 0, 0,
0, 0.5f+ animValue, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1
};
}
FloatBuffer mPosTransMatrixBuffer = convertFloatBuffer(posTrans , 4);
GLES20.glUniformMatrix4fv(mPosTransMatrixHandler , 1 , false , mPosTransMatrixBuffer);
GLES20.glEnableVertexAttribArray(mPosTransMatrixHandler);
if (mSurfaceTexture != null) {
mSurfaceTexture.updateTexImage();
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, mPosCoordinate.length / 2);
}
}
与activeProgram方法的流程类似,唯一不同的只是位置矩阵和颜色矩阵。在画中画的特效中,颜色矩阵接近于一个单位矩阵,只是透明度会渐渐变小。而顶点坐标的矩阵,则会随着特效动画的进程不断变化。x
和y
值不对增大。
以上就是一个简单的基于OpenGL的动画特效。OpenGL动效的关键在于根据着色器的代码,插入需要变换的变量。如顶点变换矩阵和颜色变换矩阵,然后根据时间或其他参数,对矩阵进行变换,从而达到改变渲染的目的。
如有问题,欢迎指正。