《OpenGL從入門到放棄08》相機預覽滑进,這樣講就好理解了

這篇文章將介紹相機預覽的實現(xiàn),網(wǎng)上可以找到很多關于Camera2預覽的文章募谎,Camera1已經(jīng)過時扶关,所以暫時不考慮什么兼容性,就用Camera2來搞数冬〗诨保可以使用TextureView作為載體來實現(xiàn)預覽功能,但是我們后面要在相機上添加濾鏡吉执、貼紙等功能疯淫,所以載體就選擇了GLSurfaceView

之前的文章
《OpenGL從入門到放棄01 》一些基本概念
《OpenGL從入門到放棄02 》GLSurfaceView和Renderer
《OpenGL從入門到放棄03 》相機和視圖
《OpenGL從入門到放棄04 》畫一個長方形
《OpenGL從入門到放棄05 》著色器語言
《OpenGL從入門到放棄06 》紋理圖片顯示
《OpenGL從入門到放棄07》濾鏡戳玫,一定要看熙掺,不虧

開始擼擼代碼...

1、簡單的布局文件

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <android.opengl.GLSurfaceView
        android:id="@+id/glsurfaceview"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />


    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_centerHorizontal="true"
        android:layout_margin="16dp"
        android:gravity="center"
        android:onClick="takePicture"
        android:text="拍照" />
</RelativeLayout>

預留個拍照按鈕咕宿,后面文章會講拍照功能

2币绩、Activity代碼

public class Camera2Demo_SurfaceView_Activity extends AppCompatActivity {

    private static final String TAG = "Camera2Demo_SurfaceView";

    private GLSurfaceView mCameraV2GLSurfaceView;
    private CameraV2Renderer mCameraV2Renderer;
    private CameraV2 mCamera;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.camera2_surfaceview_activity);
        initView();
    }

    private void initView() {
        //Camera2 API 進行封裝
        mCamera = new CameraV2(this);

        mCameraV2GLSurfaceView = findViewById(R.id.glsurfaceview);
        mCameraV2GLSurfaceView.setEGLContextClientVersion(2);

        mCameraV2Renderer = new CameraV2Renderer();
        mCameraV2Renderer.init(mCameraV2GLSurfaceView, mCamera, this);
        mCameraV2GLSurfaceView.setRenderer(mCameraV2Renderer);

    }

    @Override
    protected void onResume() {
        super.onResume();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (mCamera != null) {
            mCamera.closeCamea();
        }
    }

    public void takePicture(View view) {
        Log.d(TAG, "onclick takePicture: ");
    }
}

說明:
initView 中代碼不多蜡秽,因為Camera2 的API被封裝在 CameraV2 這個類里了,接下來將主要分析 CameraV2CameraV2Renderer 這兩個類缆镣。

3芽突、CameraV2

CameraV2 封裝了Camera2 的API,聽我細細道來~

3.1 構(gòu)造方法

    public CameraV2(Activity activity) {
        mActivity = activity;
        //1.啟動Camera線程
        startCameraThread();

        //2.準備Camera董瞻,獲取cameraId寞蚌,獲取Camera預覽大小
        setupCamera();

        //打開Camera
        openCamera();
    }

3.2 啟動Camera線程

public void startCameraThread() {
        mCameraThread = new HandlerThread("CameraThread");
        mCameraThread.start();
        mCameraHandler = new Handler(mCameraThread.getLooper());
    }

openCamera方法里面需要傳一個Handler,這個Handler必須是子線程的Handler钠糊,等下會介紹挟秤。

3.2 setupCamera

setupCamera 中主要是獲取cameraId和camera 預覽大小,這個預覽大小什么意思呢抄伍?

public String setupCamera() {
        CameraManager cameraManager = (CameraManager) mActivity.getSystemService(Context.CAMERA_SERVICE);
        try {
            mCameraIdList = cameraManager.getCameraIdList();
            Log.d(TAG, "setupCamera: mCameraIdList:"+mCameraIdList.length);
            for (String id : mCameraIdList) {
                CameraCharacteristics characteristics = cameraManager.getCameraCharacteristics(id);
                if (characteristics.get(CameraCharacteristics.LENS_FACING) == CameraCharacteristics.LENS_FACING_FRONT) {
                    continue;
                }
                StreamConfigurationMap map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
                mPreviewSize = map.getOutputSizes(SurfaceTexture.class)[0];
                mCameraId = id;
                Log.d(TAG, "setupCamera: preview width = " + mPreviewSize.getWidth() + ", height = " + mPreviewSize.getHeight() + ", cameraId = " + mCameraId);

            }
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
        return mCameraId;
    }
  1. 通過CameraManager獲取cameraIdList艘刚,如果有前后攝像頭,那么cameraIdList這個數(shù)組大小就是2了截珍,大家可以點擊getCameraIdList這個方法進去看源碼攀甚,這里就先不說源碼了。

  2. for 循環(huán)里面過濾掉前置攝像頭岗喉,最終 mCameraId 對應后置攝像頭的id;

  3. 預覽大小mPreviewSize的獲取都是模板代碼秋度,知道就行

準備好了,可以打開相機了

3.3 openCamera 打開相機

public boolean openCamera() {
        CameraManager cameraManager = (CameraManager) mActivity.getSystemService(Context.CAMERA_SERVICE);
        try {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && ActivityCompat.checkSelfPermission(mActivity, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
                mActivity.requestPermissions(new String[]{Manifest.permission.CAMERA, Manifest
                        .permission.WRITE_EXTERNAL_STORAGE}, 1);
                return false;
            }
            cameraManager.openCamera(mCameraId, mStateCallback, mCameraHandler);
        } catch (CameraAccessException e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }

打開相機就是通過獲取 CameraManager 對象钱床,然后調(diào)用它的 openCamera方法即可静陈,這里要說下三個參數(shù)

  1. mCameraId 在上一步 setupCamera方法已經(jīng)獲取了
  2. mStateCallback 是一個 CameraDevice.StateCallback 對象
public CameraDevice.StateCallback mStateCallback = new CameraDevice.StateCallback() {
        @Override
        public void onOpened(@NonNull CameraDevice camera) {
            mCameraDevice = camera;
            startPreview();
        }

        @Override
        public void onDisconnected(@NonNull CameraDevice camera) {
            camera.close();
            mCameraDevice = null;
        }

        @Override
        public void onError(@NonNull CameraDevice camera, int error) {
            camera.close();
            mCameraDevice = null;
        }
    };

主要是接收相機打開狀態(tài),相機打開成功诞丽,斷開連接、出現(xiàn)錯誤拐格,會分別回調(diào)上面三個方法僧免,最重要的是 onOpened 回調(diào),我們可以在onOpened 回調(diào)中獲取 CameraDevice 對象捏浊,一般的做法都是獲取 CameraDevice 對象之后懂衩,打開預覽。

  1. mCameraHandler金踪,在startCameraThread方法中初始化好了

3.4 startPreview 開始預覽

public void startPreview() {
        if (mStartPreview || mSurfaceTexture == null || mCameraDevice == null){
            return;
        }
        mStartPreview = true;
        
        //給 SurfaceTexture 設置默認大小浊洞,mPreviewSize是相機預覽大小
        mSurfaceTexture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());
        //Surface 需要接收一個 SurfaceTexture
        Surface surface = new Surface(mSurfaceTexture);
        try {
            // 通過CameraDevice創(chuàng)建request
            mCaptureRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
            //surface 作為輸出的目標,預覽的數(shù)據(jù)會傳到 Surface 中
            mCaptureRequestBuilder.addTarget(surface);
            //創(chuàng)建會話胡岔, surface 這里傳進去法希,然后只需關心回調(diào)
            mCameraDevice.createCaptureSession(Arrays.asList(surface), new CameraCaptureSession.StateCallback() {
                @Override
                public void onConfigured(@NonNull CameraCaptureSession session) {
                    try {
                        CaptureRequest mCaptureRequest = mCaptureRequestBuilder.build();
                        //設置一直重復捕獲圖像,不設置就只有一幀靶瘸,沒法預覽
                        session.setRepeatingRequest(mCaptureRequest, null, mCameraHandler);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }

                @Override
                public void onConfigureFailed(@NonNull CameraCaptureSession session) {

                }
            }, mCameraHandler);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }

注釋應該很清楚了~
mCameraDevice 上面說過了苫亦,在openCamera方法的回調(diào)拿到的毛肋。
主要就是通過 mCameraDevice.createCaptureSession 創(chuàng)建一個Session,然后傳入 surface屋剑,預覽的圖片數(shù)據(jù)會輸出到 surface 中>

需要注意的是:在 onConfigured 回調(diào)方法中润匙,需要調(diào)用 session.setRepeatingRequest 設置重復捕獲圖像,不然只能拿到一幀唉匾,不能實時預覽孕讳。

surface中有一個 SurfaceTexture,這個是重點:

public void setSurfaceTexture(SurfaceTexture surfaceTexture) {
       this.mSurfaceTexture = surfaceTexture;
   }
...
Surface surface = new Surface(mSurfaceTexture);

SurfaceTexture 并不是在這里隨意創(chuàng)建的巍膘,而是跟OpenGL掛鉤起來的厂财,setSurfaceTexture 方法在 CameraV2Renderer 中調(diào)用的,繼續(xù)看~

4典徘、CameraV2Renderer

老規(guī)矩蟀苛,先貼代碼再解釋

public class CameraV2Renderer implements GLSurfaceView.Renderer {
    public static final String TAG = "CameraV2Renderer";
    private Context mContext;
    GLSurfaceView mGLSurfaceView;
    CameraV2 mCamera;
    private int mTextureId = -1;
    private SurfaceTexture mSurfaceTexture;
    private float[] mTransformMatrix = new float[16];

    Camera2BaseFilter mCamera2BaseFilter;

    public void init(GLSurfaceView surfaceView, CameraV2 camera, Context context) {
        mContext = context;
        mGLSurfaceView = surfaceView;
        mCamera = camera;
    }

    @Override
    public void onSurfaceCreated(GL10 gl, EGLConfig config) {
        ShaderManager.init(mContext);
        initSurfaceTexture();
        mCamera2BaseFilter = new Camera2FilterNone(mContext, mTextureId);
    }

    @Override
    public void onSurfaceChanged(GL10 gl, int width, int height) {
        glViewport(0, 0, width, height);
        Log.i(TAG, "onSurfaceChanged: " + width + ", " + height);
    }

    @Override
    public void onDrawFrame(GL10 gl) {

        //mSurfaceTexture.updateTexImage()更新預覽上的圖像
        mSurfaceTexture.updateTexImage();
        //直接通過 SurfaceTexture 獲取變換矩陣
        mSurfaceTexture.getTransformMatrix(mTransformMatrix);

        glClearColor(1.0f, 0.0f, 0.0f, 0.0f);
        mCamera2BaseFilter.draw(mTransformMatrix);

    }

    //創(chuàng)建 SurfaceTexture,CameraV2.startPreview()方法需要SurfaceTexture
    public boolean initSurfaceTexture() {
        //1逮诲、獲取一個紋理id
        mTextureId = GLUtil.getOESTextureId();
        //2帜平、紋理id設置到 SurfaceTexture 中,
        mSurfaceTexture = new SurfaceTexture(mTextureId);
        //圖片數(shù)據(jù)固定的梅鹦,而攝像頭數(shù)據(jù)是變換的裆甩,所以每當攝像頭有新的數(shù)據(jù)來時,我們需要通過surfaceTexture.updateTexImage()更新預覽上的圖像
        // updateTexImage 不應該在OnFrameAvailableLister的回調(diào)方法中直接調(diào)用齐唆,而應該在onDrawFrame中執(zhí)行嗤栓。而調(diào)用requestRender,可以觸發(fā)onDrawFrame
        mSurfaceTexture.setOnFrameAvailableListener(new SurfaceTexture.OnFrameAvailableListener() {
            @Override
            public void onFrameAvailable(SurfaceTexture surfaceTexture) {
                Log.d(TAG, "onFrameAvailable: ");
                mGLSurfaceView.requestRender();
            }
        });

        //將 SurfaceTexture 設置給CameraV2,然后調(diào)用startPreview
        mCamera.setSurfaceTexture(mSurfaceTexture);
        mCamera.startPreview();
        return true;
    }

}

CameraV2Renderer代碼不多箍邮,說一下流程:

4.1 onSurfaceCreated 中初始化數(shù)據(jù)

4.1.1 ShaderManager.init(mContext)

ShaderManager.init(mContext);
這個其實是后面一節(jié)濾鏡功能加的茉帅,加了幾個相機的著色器

/**相機部分*/
        insertParam(CAMERA_BASE_SHADER, GLUtil.loadFromAssetsFile(context, "shader/camera2/camera2_vertex_shader_base.glsl")
                , GLUtil.loadFromAssetsFile(context, "shader/camera2/camera2_fragment_shader_base.glsl"));
        //黑白
        insertParam(CAMERA_GRAY_SHADER, GLUtil.loadFromAssetsFile(context, "shader/camera2/camera2_vertex_shader_base.glsl")
                , GLUtil.loadFromAssetsFile(context, "shader/camera2/camera2_fragment_shader_gray.glsl"));

        insertParam(CAMERA_COOL_SHADER, GLUtil.loadFromAssetsFile(context, "shader/camera2/camera2_vertex_shader_base.glsl")
                , GLUtil.loadFromAssetsFile(context, "shader/camera2/camera2_fragment_shader_cool.glsl"));

        insertParam(CAMERA_WARM_SHADER, GLUtil.loadFromAssetsFile(context, "shader/camera2/camera2_vertex_shader_base.glsl")
                , GLUtil.loadFromAssetsFile(context, "shader/camera2/camera2_fragment_shader_warm.glsl"));

        insertParam(CAMERA_FOUR_SHADER, GLUtil.loadFromAssetsFile(context, "shader/camera2/camera2_vertex_shader_base.glsl")
                , GLUtil.loadFromAssetsFile(context, "shader/camera2/camera2_fragment_shader_four.glsl"));

        insertParam(CAMERA_ZOOM_SHADER, GLUtil.loadFromAssetsFile(context, "shader/camera2/camera2_vertex_shader_base.glsl")
                , GLUtil.loadFromAssetsFile(context, "shader/camera2/camera2_fragment_shader_zoom.glsl"));


這個是下一節(jié)的內(nèi)容,這里簡單說一下即可

4.1.2 initSurfaceTexture

這個方法很重要锭弊,一定要好好理解

    //創(chuàng)建 SurfaceTexture堪澎,CameraV2.startPreview()方法需要SurfaceTexture
    public void initSurfaceTexture() {
        //1、獲取一個紋理id
        mTextureId = GLUtil.getOESTextureId();
        //2味滞、紋理id設置到 SurfaceTexture 中樱蛤,
        mSurfaceTexture = new SurfaceTexture(mTextureId);
        //圖片數(shù)據(jù)固定的,而攝像頭數(shù)據(jù)是變換的剑鞍,所以每當攝像頭有新的數(shù)據(jù)來時昨凡,我們需要通過surfaceTexture.updateTexImage()更新預覽上的圖像
        // updateTexImage 不應該在OnFrameAvailableLister的回調(diào)方法中直接調(diào)用,而應該在onDrawFrame中執(zhí)行蚁署。而調(diào)用requestRender便脊,可以觸發(fā)onDrawFrame
        mSurfaceTexture.setOnFrameAvailableListener(new SurfaceTexture.OnFrameAvailableListener() {
            @Override
            public void onFrameAvailable(SurfaceTexture surfaceTexture) {
                Log.d(TAG, "onFrameAvailable: ");
                mGLSurfaceView.requestRender();
            }
        });

        //將 SurfaceTexture 設置給CameraV2,然后調(diào)用startPreview
        mCamera.setSurfaceTexture(mSurfaceTexture);
        mCamera.startPreview();
    }

    //class : GLUtil
    /**
     * 相機預覽使用EXTERNAL_OES紋理,創(chuàng)建方式與2D紋理創(chuàng)建基本相同
     * @return
     */
    public static int getOESTextureId() {
        int[] texture = new int[1];
        GLES20.glGenTextures(1, texture, 0);
        //于我們創(chuàng)建的是擴展紋理光戈,所以綁定的時候我們也需要綁定到擴展紋理上才可以正常使用就轧,
        GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, texture[0]);
        GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,
                GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_NEAREST);
        GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,
                GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_LINEAR);
        GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,
                GL10.GL_TEXTURE_WRAP_S, GL10.GL_CLAMP_TO_EDGE);
        GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,
                GL10.GL_TEXTURE_WRAP_T, GL10.GL_CLAMP_TO_EDGE);
        GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, 0);
        return texture[0];
    }

這個方法就是創(chuàng)建 SurfaceTexture证杭,然后傳給 CameraV2,再開啟預覽妒御。
分為兩步:

  1. 創(chuàng)建一個擴展紋理解愤,getOESTextureId方法返回紋理id :mTextureId
  2. 有了紋理id,創(chuàng)建 SurfaceTexture
  3. mSurfaceTexture 設置監(jiān)聽乎莉,在預覽數(shù)據(jù)更新的時候送讲,調(diào)用 mGLSurfaceView.requestRender() ,可以觸發(fā)onDrawFrame`
  4. 把SurfaceTexture設置給CameraV2惋啃,然后就可以愉快地開啟預覽了哼鬓,
    然后預覽就會不斷回調(diào) onFrameAvailable,不斷調(diào)用
    mGLSurfaceView.requestRender()边灭,不斷觸發(fā) onDrawFrame ...

4.2 onDrawFrame

這個方法里很清晰

        //mSurfaceTexture.updateTexImage()更新預覽上的圖像
        mSurfaceTexture.updateTexImage();
        //直接通過 SurfaceTexture 獲取變換矩陣
        mSurfaceTexture.getTransformMatrix(mTransformMatrix);

更新預覽圖像异希,更新變換矩陣,然后其它的繪制過程都交給 Camera2BaseFilter

為什么命名為 xxFilter绒瘦,因為下一節(jié)要介紹濾鏡称簿,所以將不同濾鏡共有的代碼抽取到 Camera2BaseFilter中。

5惰帽、Camera2BaseFilter

這個類代碼注釋非常清晰了

public abstract class Camera2BaseFilter {
   private static final String TAG = "Camera2BaseFilter";

   private FloatBuffer mVertexBuffer;  //頂點坐標數(shù)據(jù)要轉(zhuǎn)化成FloatBuffer格式
   private FloatBuffer mTexCoordBuffer;//頂點紋理坐標緩存

   //當前繪制的頂點位置句柄
   protected int vPositionHandle;
   //變換矩陣句柄
   protected int mMVPMatrixHandle;
   //這個可以理解為一個OpenGL程序句柄
   protected int mProgram;
   //紋理坐標句柄
   protected int mTexCoordHandle;
   //紋理id
   protected int mTextureId;

   private Context mContext;


   public Camera2BaseFilter(Context context, int textureId) {
       this.mContext = context;
       this.mTextureId = textureId;
       //初始化Buffer憨降、Shader、紋理
       initBuffer();
       initShader();
   }

   //數(shù)據(jù)轉(zhuǎn)換成Buffer
   private void initBuffer() {
       float vertices[] = new float[]{
               -1, 1, 0,
               -1, -1, 0,
               1, 1, 0,
               1, -1, 0,

       };//頂點位置

       //這里經(jīng)過測試该酗,左下角才是紋理坐標的原點授药,右上角是(1,1)
       float[] colors = new float[]{
               0, 1,
               0, 0,
               1, 1,
               1, 0,

       };//紋理頂點數(shù)組

       mVertexBuffer = GLUtil.floatArray2FloatBuffer(vertices);
       mTexCoordBuffer = GLUtil.floatArray2FloatBuffer(colors);
   }

   /**
    * 著色器
    */
   private void initShader() {
       //獲取程序呜魄,封裝了加載悔叽、鏈接等操作
       ShaderManager.Param param = getProgram();
       mProgram = param.program;
       vPositionHandle = param.positionHandle;
       // 獲取變換矩陣的句柄
       mMVPMatrixHandle = param.mMVPMatrixHandle;
       //紋理位置句柄
       mTexCoordHandle = param.mTexCoordHandle;


       Log.d(TAG, "initShader: mProgram = " + mProgram + ",vPositionHandle="+vPositionHandle +",mMVPMatrixHandle="+mMVPMatrixHandle+
               ",mTexCoordHandle="+mTexCoordHandle);
   }

   /**
    * 濾鏡子類重寫這個方法,加載不同的OpenGL程序
    *
    * @return
    */
   protected abstract ShaderManager.Param getProgram();


   public void draw( float[] transformMatrix) {
       // 將程序添加到OpenGL ES環(huán)境
       GLES20.glUseProgram(mProgram);

       // 啟用頂點屬性爵嗅,最后對應禁用
       GLES20.glEnableVertexAttribArray(vPositionHandle);
       GLES20.glEnableVertexAttribArray(mTexCoordHandle);

       //綁定紋理骄蝇,跟圖片不同的是,這里是擴展紋理
       glActiveTexture(GL_TEXTURE_EXTERNAL_OES);
       glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, mTextureId);

       //設置轉(zhuǎn)換矩陣數(shù)據(jù)
       glUniformMatrix4fv(mMVPMatrixHandle, 1, false, transformMatrix, 0);
       //設置三角形坐標數(shù)據(jù)(一個頂點三個坐標)
       GLES20.glVertexAttribPointer(vPositionHandle, 3,
               GLES20.GL_FLOAT, false,
               3 * 4, mVertexBuffer);
       //設置紋理坐標數(shù)據(jù)
       GLES20.glVertexAttribPointer(mTexCoordHandle, 2,
               GLES20.GL_FLOAT, false,
               2 * 4, mTexCoordBuffer);

       glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
       glBindFramebuffer(GL_FRAMEBUFFER, 0);
   }

   public void onDestroy() {
       GLES20.glDeleteProgram(mProgram);
       mProgram = 0;
   }
}

這是一個抽象類操骡,ShaderManager.Param getProgram()
方法需要子類實現(xiàn),返回不同著色器對應的OpenGL程序參數(shù)赚窃。Camera2FilterNone是沒有濾鏡效果

/**
 * 沒有濾鏡效果
 */
public class Camera2FilterNone extends Camera2BaseFilter {

    public Camera2FilterNone(Context context, int textureId) {
        super(context, textureId);
    }

    @Override
    protected ShaderManager.Param getProgram() {
        return ShaderManager.getParam(ShaderManager.CAMERA_BASE_SHADER);
    }
}

對應的著色器是
頂點著色器
assets/shader/camera2/camera2_vertex_shader_base.glsl

attribute vec4 aPosition;
uniform mat4 uMVPMatrix;
attribute vec4 aTexCoord;
varying vec2 vTextureCoord; //傳給片元著色器
void main()
{
    vTextureCoord = (uMVPMatrix * aTexCoord).xy;
    gl_Position = aPosition;
}

片元著色器
assets/shader/camera2/camera2_fragment_shader_base.glsl

#extension GL_OES_EGL_image_external : require
precision mediump float;
uniform samplerExternalOES uTextureSampler;
varying vec2 vTextureCoord;
void main()
{
  vec4 vCameraColor = texture2D(uTextureSampler, vTextureCoord);
  float fGrayColor = (0.3*vCameraColor.r + 0.59*vCameraColor.g + 0.11*vCameraColor.b);
  gl_FragColor = vCameraColor;
}


最終效果圖:

image.png

總結(jié):

已是深夜12:22册招,

就不總結(jié)了,從頭看勒极,有什么不理解的地方可以留言提出是掰。

平時11點多睡覺的我,寫文章又熬夜了...

預覽的濾鏡功能放到下一節(jié)辱匿,敬請期待...

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末键痛,一起剝皮案震驚了整個濱河市炫彩,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌絮短,老刑警劉巖江兢,帶你破解...
    沈念sama閱讀 216,324評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異丁频,居然都是意外死亡杉允,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,356評論 3 392
  • 文/潘曉璐 我一進店門席里,熙熙樓的掌柜王于貴愁眉苦臉地迎上來叔磷,“玉大人,你說我怎么就攤上這事奖磁「幕” “怎么了?”我有些...
    開封第一講書人閱讀 162,328評論 0 353
  • 文/不壞的土叔 我叫張陵咖为,是天一觀的道長秕狰。 經(jīng)常有香客問我,道長案疲,這世上最難降的妖魔是什么封恰? 我笑而不...
    開封第一講書人閱讀 58,147評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮褐啡,結(jié)果婚禮上诺舔,老公的妹妹穿的比我還像新娘。我一直安慰自己备畦,他們只是感情好低飒,可當我...
    茶點故事閱讀 67,160評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著懂盐,像睡著了一般褥赊。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上莉恼,一...
    開封第一講書人閱讀 51,115評論 1 296
  • 那天拌喉,我揣著相機與錄音,去河邊找鬼俐银。 笑死尿背,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的捶惜。 我是一名探鬼主播田藐,決...
    沈念sama閱讀 40,025評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了汽久?” 一聲冷哼從身側(cè)響起鹤竭,我...
    開封第一講書人閱讀 38,867評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎景醇,沒想到半個月后臀稚,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,307評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡啡直,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,528評論 2 332
  • 正文 我和宋清朗相戀三年烁涌,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片酒觅。...
    茶點故事閱讀 39,688評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡撮执,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出舷丹,到底是詐尸還是另有隱情抒钱,我是刑警寧澤,帶...
    沈念sama閱讀 35,409評論 5 343
  • 正文 年R本政府宣布颜凯,位于F島的核電站谋币,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏症概。R本人自食惡果不足惜蕾额,卻給世界環(huán)境...
    茶點故事閱讀 41,001評論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望彼城。 院中可真熱鬧诅蝶,春花似錦、人聲如沸募壕。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,657評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽舱馅。三九已至缰泡,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間代嗤,已是汗流浹背棘钞。 一陣腳步聲響...
    開封第一講書人閱讀 32,811評論 1 268
  • 我被黑心中介騙來泰國打工依鸥, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留尊蚁,地道東北人。 一個月前我還...
    沈念sama閱讀 47,685評論 2 368
  • 正文 我出身青樓榜掌,卻偏偏與公主長得像溶锭,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子符隙,可洞房花燭夜當晚...
    茶點故事閱讀 44,573評論 2 353

推薦閱讀更多精彩內(nèi)容