王学岗————opengl黑白效果与分屏(第32,33节课)

32节课

1,直播推流之前要做美颜和特效。
2,opengl是运行在GPU
3,左侧是cpu架构,右侧是GPU架构
在这里插入图片描述
渲染约等于计算,GPU有很多ALU单元,擅长渲染。
4,openGL 的几个阶段
1)确定形状
2)矢量图形像素化
3)上颜色
4)根据颜色,进行显示
第一步(顶点程序)和第三步(片元程序)需要程序员实现。

代码

#extension GL_OES_EGL_image_external : require
precision lowp float;
//片元程序,变量名要和camera_vert中一样
varying vec2 aCoord;
//定义采样器 内置变量
uniform samplerExternalOES  vTexture;
//运行在GPU,
void main() {
//    分屏的写法,上下分屏
    float x= aCoord.x;
//    分几屏就除以几
    float a = 1.0/3.0;
    if(x<a)
    {
        x+=a;
    }else if(x>2.0*a){
        x -= 1.0/3.0;
    }
    //    分屏的写法
    //    texture2D是自带的采样器,根据对应的图层采样对应的像素值。vTexture这里就是第0个图层,
    vec4 rgba =  texture2D(vTexture,vec2(x,aCoord.y));
    //  gl_FragColor内置的变量.在这里可以对颜色进行处理
//     黑白效果的写法
//    float color  = (rgba.r+rgba.g+rgba.b)/3.0;
//    gl_FragColor = vec4(color,color,color,rgba.a);
    gl_FragColor=rgba;
}
    左右分屏的写法
//float y= aCoord.y;
//if(y<0.5)
//{
//    y+=0.25;
//
//}else{
//    y -= 0.25;
//}
    texture2D是自带的采样器,根据对应的图层采样对应的像素值。
//vec4 rgba =  texture2D(vTexture,vec2(aCoord.x,y));

//顶点程序,确定形状(形状在cpu已经定义好了)
//声明变量, vec4代表四个顶点坐标 (vec是坐标的意思)
attribute  vec4 vPosition;

attribute vec4 vCoord;

uniform mat4 vMatrix;
//纹理坐标系
varying  vec2 aCoord;
void main() {
   //gl_position是内置变量,顶点位置坐标,一旦赋值,形状就确定了。
    gl_Position=vPosition;
    //矩阵变换,摄像头变成横着。aCoord是片元程序(raw文件)中的变量
    aCoord= (vMatrix*vCoord).xy;
}

package com.maniu.openglbbc;

import android.os.HandlerThread;
import android.util.Size;

import androidx.camera.core.CameraX;
import androidx.camera.core.Preview;
import androidx.camera.core.PreviewConfig;
import androidx.lifecycle.LifecycleOwner;

public class CameraHelper {
    private HandlerThread handlerThread;
    private CameraX.LensFacing currentFacing = CameraX.LensFacing.BACK;
    private Preview.OnPreviewOutputUpdateListener listener;


    public CameraHelper(LifecycleOwner lifecycleOwner, Preview.OnPreviewOutputUpdateListener listener) {
        this.listener = listener;
        handlerThread = new HandlerThread("Analyze-thread");
        handlerThread.start();
        //getPreView(),数据丢到GPU而不是CPU,ImageAnalysi会把数据丢到cpu,而且是yuv数据
        CameraX.bindToLifecycle(lifecycleOwner, getPreView());
    }
    private Preview getPreView() {
        // 分辨率并不是最终的分辨率,CameraX会自动根据设备的支持情况,结合你的参数,设置一个最为接近的分辨率
        PreviewConfig previewConfig = new PreviewConfig.Builder()
                .setTargetResolution(new Size(640, 480))
                .setLensFacing(currentFacing) //前置或者后置摄像头
                .build();
        Preview preview = new Preview(previewConfig);
        //需要预览输出的时候,就回调这个方法。
        preview.setOnPreviewOutputUpdateListener(listener);
        return preview;
    }

}

package com.maniu.openglbbc;

import android.content.Context;
import android.graphics.SurfaceTexture;
import android.opengl.GLES20;
import android.opengl.GLSurfaceView;
import android.util.AttributeSet;

import androidx.camera.core.Preview;
import androidx.lifecycle.LifecycleOwner;

import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;

//把摄像头的数据渲染到GLSurfaceView
public class CameraView extends GLSurfaceView implements Preview.OnPreviewOutputUpdateListener, GLSurfaceView.Renderer, SurfaceTexture.OnFrameAvailableListener {
    private SurfaceTexture mCameraTexure;
    ScreenFilter screenFilter;
//  摄像机直接把数据放到GPU内存,这个变量就是数据存放的地方,对cpu没有意义,但对GPU意义重大
    private  int textures=0;
    public CameraView(Context context) {
        super(context);
    }
    public CameraView(Context context, AttributeSet attrs) {
        super(context, attrs);
//       使用的版本
        setEGLContextClientVersion(2);
        setRenderer(this);
        //手动渲染
        setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
        initCamera();
    }

    private void initCamera() {
        CameraHelper cameraHelper = new CameraHelper
                ((LifecycleOwner)  getContext(),
                        this);
    }
    //摄像头打开会回调onUpdated
    @Override
    public void onUpdated(Preview.PreviewOutput output) {
    //camera数据要显示到我们自定义的CameraView控件。但现在两者并无关系
    //我们要让这两者发生关联。
        mCameraTexure=  output.getSurfaceTexture();
    }
//监听Surface的创建过程,创建成功的时候就渲染
    //GLSurfaceView准备好了,就绑定
    //设置了 setRenderer(this);就会回调onSurfaceCreated()。
    @Override
    public void onSurfaceCreated(GL10 gl10, EGLConfig eglConfig) {
        //绑定数据,
        mCameraTexure.attachToGLContext(textures);
        //onFrameAvailable
        mCameraTexure.setOnFrameAvailableListener(this);
        screenFilter = new ScreenFilter( getContext());

    }
    // 因为设置了监听(mCameraTexure.setOnFrameAvailableListener(this)),
    // 一旦摄像头有数据的时候就会回调onFrameAvailable()
    @Override
    public void onFrameAvailable(SurfaceTexture surfaceTexture) {
//        手动渲染,需要调用requestRender(),
//requestRender()一旦调用,就会调用onDrawFrame()。
        //如果是自动渲染16毫秒渲染一次,我们不用自动渲染
        //摄像头每捕获一次,就渲染一次
        requestRender();
    }

    @Override
    public void onSurfaceChanged(GL10 gl10, int i, int i1) {

    }
//    当摄像头捕捉一帧画面的时候调用,把camerax捕获的数据渲染到我们自定义的CameraView。
    @Override
    public void onDrawFrame(GL10 gl10) {
        //确定形状,上色
        //获取到最新数据。摄像头捕获的数据更新到GPU内存
        mCameraTexure.updateTexImage();
//        利用openGL渲染
        float[] mtx = new float[16];
//        得到纠正的矩阵,防止拉伸
        mCameraTexure.getTransformMatrix(mtx);
//        摄像头捕获一帧数据的时候
        screenFilter.onDraw(getWidth(), getHeight(),mtx, );
    }

}

package com.maniu.openglbbc;

import android.content.Context;
import android.opengl.GLES20;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;

public class ScreenFilter {
    int program;
    //根据世界坐标系画
    //两个三角形,OpenGL会把它当作六个点。两个点是共用的。
    //注意点的顺序,不能换
    //数据在GPU,
    //这就是我们说的形状,需要把形状从cpu传给GPU
    float[] VERTEX = {
            -1.0f, -1.0f,
            1.0f, -1.0f,
            -1.0f, 1.0f,
            1.0f, 1.0f
    };
    //输出,android 坐标系,要和世界坐标系一一对应。
    float[] TEXTURE = {
            0.0f, 0.0f,
            1.0f, 0.0f,
            0.0f, 1.0f,
            1.0f, 1.0f
    };
    private int vPosition;
    private int vCoord;
    private int vTexture;
    private int vMatrix;
    FloatBuffer vertexBuffer;
    FloatBuffer textureBuffer; // 纹理坐标

    public ScreenFilter(Context context) {
        //读取资源文件
        String vertexSharder = readRawTextFile(context, R.raw.camera_vert);
//      类似把Java源码打包成jar
//      创建顶点程序,而不是片源程序。vShader是程序地址
        int vShader = GLES20.glCreateShader(GLES20.GL_VERTEX_SHADER);
//        加载代码
        GLES20.glShaderSource(vShader, vertexSharder);
//        编译,会把代码编译成可执行的
        GLES20.glCompileShader(vShader);
        int[] status = new int[1];
//        查看程序是否编译成功
        GLES20.glGetShaderiv(vShader, GLES20.GL_COMPILE_STATUS, status, 0);
        if (status[0] != GLES20.GL_TRUE) {
//            失败抛异常
            throw new IllegalStateException("load vertex shader:" + GLES20.glGetShaderInfoLog
                    (vShader));
        }
        String fragSharder = readRawTextFile(context, R.raw.camera_frag);
        int fShader = GLES20.glCreateShader(GLES20.GL_FRAGMENT_SHADER);
        GLES20.glShaderSource(fShader, fragSharder);
        GLES20.glCompileShader(fShader);
        status = new int[1];
        GLES20.glGetShaderiv(fShader, GLES20.GL_COMPILE_STATUS, status, 0);
        if (status[0] != GLES20.GL_TRUE) {
            throw new IllegalStateException("load fragment shader:" + GLES20.glGetShaderInfoLog
                    (fShader));
        }
//        创建一个总程序
        program = GLES20.glCreateProgram();
        //         加载顶点程序
        GLES20.glAttachShader(program, vShader);
        //         加载片元程序
        GLES20.glAttachShader(program, fShader);
//        是程序处于激活状态
        GLES20.glLinkProgram(program);
//      在CPU中创建4 * 2 * 4内存大小的通道,4个点,每个点两个坐标,每个坐标的值是4个字节,所以是4 * 2 * 4
//        order对创建好的内存排序
//        asFloatBuffer()转化为float类型
        vertexBuffer = ByteBuffer.allocateDirect(4 * 2 * 4).order(ByteOrder.nativeOrder()).asFloatBuffer();
        
        vertexBuffer.clear();
        //现在数据还在cpu,我们这里已经建立一个通道,需要把VERTEX这个点传到GPU的vPosition
        //但不需要再初始化的时候传,再OpenGL构建的时候传,即onDraw()方法
        //openGL四个步骤,确定形状,栅格化,渲染,显示
        vertexBuffer.put(VERTEX);//把VERTEX这个点传到GPU的vPosition


        textureBuffer = ByteBuffer.allocateDirect(4 * 4 * 2).order(ByteOrder.nativeOrder())
                .asFloatBuffer();
        textureBuffer.clear();
        textureBuffer.put(TEXTURE);
 //用完了一定要释放掉,后来补充的,可能有问题
        GLES20.glDeleteShader(vShader);
        GLES20.glDeleteShader(fShader);
    }

    //摄像头捕获的形状是2G的,形状不需要自己画,我们只需要把摄像头捕获的数据采样。
//    有数据就渲染一次,
    public void onDraw(int mWidth, int mHeight, float[] mtx, int textures) {
//        把vertexBuffer(里面有VERTEX点的信息)和vPosition关联起来
//   GPU做好准备
//        告诉OpenGL要画的宽高范围是多少
        GLES20.glViewport(0, 0, mWidth, mHeight);
//   使用program程序
        GLES20.glUseProgram(program);
//      OnDraw()方法会调用很多次,第一次读完后,position会跑到最后的位置,这里重新定位到第一位
        vertexBuffer.position(0);
        textureBuffer.position(0);
//        定位到GPU变量地址,返回GPU的地址,vPosition,vCoord,vTexture()再raw文件中定义
        vPosition = GLES20.glGetAttribLocation(program, "vPosition");
        vCoord = GLES20.glGetAttribLocation(program, "vCoord");
        //tUniform指的是片元
        vTexture = GLES20.glGetUniformLocation(program, "vTexture");
//        矩阵变换
        vMatrix = GLES20.glGetUniformLocation(program, "vMatrix");
//       传顶点属性到GPU,将CPU中的vertexBuffer数据传给GPU的vPosition
//       vPosition:GPU的变量地址
//       2:每个坐标有几个值组成,我们这里是2D,所以是两个点,如果是3d,这里就传3
//       GLES20.GL_FLOAT:坐标类型,我们这里是浮点型
        GLES20.glVertexAttribPointer(vPosition, 2, GLES20.GL_FLOAT, false, 0, vertexBuffer);
//        告诉GPU启动变量
        GLES20.glEnableVertexAttribArray(vPosition);
//        纹理坐标系
        GLES20.glVertexAttribPointer(vCoord, 2, GLES20.GL_FLOAT, false, 0, textureBuffer);
        GLES20.glEnableVertexAttribArray(vCoord);
        //camerax(摄像头)捕获的数据放在GPU中,通过textures获取到GPU的数据,
        //数据找到了,我们需要使用采样器,好比ps中吸取颜色的工具,不断的把数据吸取出来
//        激活第0个图层,采样根据图层采样
        GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
//       绑定采样器,GLES20.GL_TEXTURE0 表示图层,textures表示摄像头数据存放的地方
//     把摄像头捕获的数据放到图层,这样就不需要操作textures了,只需操作图层就行了。
        GLES20.glBindTexture(GLES20.GL_TEXTURE0, textures);
//        使用图层。传值给片元的vTexture,这里不需要ByteBuffer,
// 片元传值比顶点传值不一样,没那么复杂。
//        定位到GPU片元程序的变量vTexture。0表示使用第0个图层
//  vTexture去第0个图层采样就可以了。
        GLES20.glUniform1i(vTexture, 0);
//       防止拉伸
        GLES20.glUniformMatrix4fv(vMatrix, 1, false, mtx, 0);
//        通知GPU进行渲染。
//        GLES20.GL_TRIANGLE_STRIP三角形,因为渲染都是三个顶点,
//        从第0个坐标开始渲染,渲染四个坐标
        GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
    }

    public String readRawTextFile(Context context, int rawId) {
        InputStream is = context.getResources().openRawResource(rawId);
        BufferedReader br = new BufferedReader(new InputStreamReader(is));
        String line;
        StringBuilder sb = new StringBuilder();
        try {
            while ((line = br.readLine()) != null) {
                sb.append(line);
                sb.append("\n");
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        try {
            br.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return sb.toString();
    }


}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值