完整代码位置:AndroidShaderDemo
Camera的预览方向和拍照方向
YUV 数据格式完全解析
MagicCamera
这里讲下Shader在相机开发里怎么应用。
相机预览的图像通过GLSurfaceView来展示,自定义一个CameraView来显示图像:
public class CameraView extends GLSurfaceView implements GLSurfaceView.Renderer{
Renderer接口的三个方法要在CameraView里实现。因为要使用Shader来处理预览图像,这里就需要先将预览图像保存在一个地方,然后把该图像作为纹理对象来处理。Android提供了SurfaceTexture来保存预览图像,SurfaceTexture和一个纹理对象绑定在一起。因为预览图像的格式是YUV,而不是普通RGB格式,所以生成这个纹理对象方式和一般不一样,生成纹理方法如下:
public static int getExternalOESTextureID() {
int[] texture = new int[1];
GLES20.glGenTextures(1, texture, 0);
GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, texture[0]);//重点看这行
GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,
GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_LINEAR);
GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,
GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_LINEAR);
GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,
GL10.GL_TEXTURE_WRAP_S, GL10.GL_CLAMP_TO_EDGE);
GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,
GL10.GL_TEXTURE_WRAP_T, GL10.GL_CLAMP_TO_EDGE);
return texture[0];
}
glBindTexture绑定的是GLES11Ext.GL_TEXTURE_EXTERNAL_OES,猜测是硬件来解析图像格式,生成纹理。
有了纹理对象后,将该纹理和SurfaceTexture绑定在一起,代码如下:
private void initSurfaceTexture(){
if (textureId == TextureHelper.NO_TEXTURE) {
textureId = TextureHelper.getExternalOESTextureID();//生成纹理
if (textureId != TextureHelper.NO_TEXTURE) {
surfaceTexture = new SurfaceTexture(textureId);//两个绑定
surfaceTexture.setOnFrameAvailableListener(onFrameAvailableListener);
}
}
}
接收预览图像的地方有了,现在可以在onSurfaceChanged回调里打开相机了:
@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
glViewport(0, 0, width, height);
surfaceWidth = width;
surfaceHeight = height;
openCamera();
}
在openCamera()方法里,首先配置相机参数,然后将之前接收图像的SurfaceTexture传给相机,代码如下:
camera.setPreviewTexture(surfaceTexture);
现在相关配置都好了,开始接收图像到手机上显示。这里有两种显示方法,一种是直接显示相机预览图像,一种是对预览图像处理后再显示(就是相机滤镜,特效等)。先说下直接显示预览图像。
在onDrawFrame里首先要先调用surfaceTexture.updateTexImage();更新预览的图像到纹理,然后调用surfaceTexture.getTransformMatrix(mtx);获得一个矩阵,用于之后的图像变换(图像翻转之类)。有了纹理,之后的渲染就和之前文章中的各种render方式一样了。所不同的还是因为图像是YUV格式,所以使用该纹理时要用 glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, textureId);这里封装成CameraInputFilter和SimpleTextureOESProgram来专门出来预览图像渲染。完整的onDrawFrame代码如下:
@Override
public void onDrawFrame(GL10 gl) {
GLES20.glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT);
if(surfaceTexture == null)
return;
surfaceTexture.updateTexImage();
float[] mtx = new float[16];
surfaceTexture.getTransformMatrix(mtx);
if(cameraInputFilter == null){
cameraInputFilter = new CameraInputFilter(context, vertexArray);
}
cameraInputFilter.setTextureTransformMatrix(mtx);
cameraInputFilter.onDrawFrame(textureId);
}
}
程序跑下,发现图像显示不对,图像是反的,还记得前面提到的 surfaceTexture.getTransformMatrix(mtx);吗?将该 mtx传给Vertex Shader,将纹理坐标变换下,就可以得到正确的图像了。
void main()
{
v_TextureCoordinates = (u_textureTransform * a_TextureCoordinates).xy;
gl_Position = a_Position;
}
下面讲下将预览图像处理后再显示。
有时候需要将预览图像当做纹理,然后再将普通纹理传给各种滤镜Shader。转化的方式就是使用帧缓存,过程就是不把SurfaceTexture的纹理显示到屏幕上,而是先显示在帧缓存里,然后再将帧缓存绑定的纹理传个各个Shader。
生成帧缓存对象的方法,前面文章有提到,这里不再说。在CameraInputFilter里通过onDrawToTexture方法将预览图像渲染到帧缓存。然后将返回的纹理id交给各种Shader,Demo里用ImageFilter来接收这个纹理id,并使用GrayTextureProgram把预览图像变成灰度图显示出来。
以上就是处理相机预览图像的流程。但是很多情况下,因为相机的翻转,纹理的坐标不匹配,导致显示图像不对。这里参考了MagicCamera项目,在openCamera里,根据相机的一些参数,先生成纹理坐标,代码如下:
protected void adjustSize(int rotation, boolean flipHorizontal, boolean flipVertical){
float[] textureCords = TextureRotationUtil.getRotation(Rotation.fromInt(rotation),
flipHorizontal, flipVertical);
float[] cube = TextureRotationUtil.CUBE;
float[] newCube = new float[]{
cube[0], cube[1], textureCords[0],textureCords[1],
cube[2], cube[3], textureCords[2],textureCords[3],
cube[4], cube[5], textureCords[4],textureCords[5],
cube[6], cube[7], textureCords[6],textureCords[7]
};
vertexArray = new VertexArray(newCube);
}
网友评论