文章目录

  • 一 什么是OpenGL?
  • 二 Android OpenGL ES
  • OpenGL坐标系
  • Shader着色器
  • 三 GLSL
  • 数据类型
  • 修饰符
  • 内建函数
  • 内建变量
  • 其他
  • 四 使用opengl显示摄像头
  • 1 自定义View
  • 2 DouyinRenderer 绘制渲染
  • 3 定义顶点着色器和片元着色器
  • 4 ScreenFilter 屏幕渲染
  • 5 工具类
  • 五 Demo


一 什么是OpenGL?

Open Graphics Library
图形领域的工业标准,是一套跨编程语言、跨平台的、专业的图形编程(软件)接口。它用于二维、三维图像,是一个功能强大,调用方便的底层图形库。
与硬件无关。可以在不同的平台如Windows、Linux、Mac、Android、IOS之间进行移植。因此,支持OpenGL的软件具有很好的移植性,可以获得非常广泛的应用。

android ndk 绘制图形_顶点着色器

  • OpenGL ES 1.0 和 1.1 :Android 1.0和更高的版本支持这个API规范。
  • OpenGL ES 2.0 :Android 2.2(API 8)和更高的版本支持这个API规范。
  • OpenGL ES 3.0 :Android 4.3(API 18)和更高的版本支持这个API规范。
  • OpenGL ES 3.1 : Android 5.0(API 21)和更高的版本支持这个API规范。

还须要由设备制造商提供了实现支持,目前广泛支持的是2.0

<uses-feature android:glEsVersion="0x00020000" android:required="true"/>

二 Android OpenGL ES

针对手机、PDA和游戏主机等嵌入式设备而设计的OpenGL API 子集。

GLSurfaceView

继承至SurfaceView,它内嵌的surface专门负责OpenGL渲染。
管理Surface与EGL
允许自定义渲染器(render)。
让渲染器在独立的线程里运作,和UI线程分离。
支持按需渲染(on-demand)和连续渲染(continuous)。

OpenGL是一个跨平台的操作GPU的API,但OpenGL需要本地视窗系统进行交互,这就需要一个中间控制层, EGL就是连接OpenGL ES和本地窗口系统的接口,引入EGL就是为了屏蔽不同平台上的区别。

OpenGL绘制流程

android ndk 绘制图形_GLSL_02


可编程管线

android ndk 绘制图形_OpenGL_03

OpenGL坐标系

android ndk 绘制图形_OpenGL_04


android ndk 绘制图形_android ndk 绘制图形_05

android ndk 绘制图形_OpenGL ES_06

Shader着色器

着色器(Shader)是运行在GPU上的小程序
顶点着色器(vertex shader)

如何处理顶点、法线等数据的小程序。

片元着色器(fragment shader)

如何处理光、阴影、遮挡、环境等等对物体表面的影响,最终生成一副图像的小程序

三 GLSL

OpenGL着色语言(OpenGL Shading Language)

android ndk 绘制图形_OpenGL ES_07

数据类型

float 浮点型
vec2 含两个浮点型数据的向量
vec4 含四个浮点型数据的向量(xyzw,rgba,stpq)
sampler2D 2D纹理采样器(代表一层纹理)

修饰符

attribute 属性变量。只能用于顶点着色器中。 一般用该变量来表示一些顶点数据,如:顶点坐标、纹理坐标、颜色等。

uniforms 一致变量。在着色器执行期间一致变量的值是不变的。与const常量不同的是,这个值在编译时期是未知的是由着色器外部初始化的。

varying 易变变量。是从顶点着色器传递到片元着色器的数据变量。

内建函数

>texture2D (采样器,坐标)  采样指定位置的纹理

内建变量

顶点

gl_Position vec4 顶点位置

片元

gl_FragColor vec4 颜色

其他

precision lowp 低精度
precision mediump 中精度
precision highp 高精度

四 使用opengl显示摄像头

1 自定义View

public class DouyinView extends GLSurfaceView {

    public DouyinView(Context context) {
        this(context,null);
    }

    public DouyinView(Context context, AttributeSet attrs) {
        super(context, attrs);
        /**
         * 配置GLSurfaceView
         */
        //设置EGL版本
        setEGLContextClientVersion(2);
        setRenderer(new DouyinRenderer(this));
        //设置按需渲染 当我们调用 requestRender 请求GLThread 回调一次 onDrawFrame
        // 连续渲染 就是自动的回调onDrawFrame
        setRenderMode(RENDERMODE_WHEN_DIRTY);
    }
}

2 DouyinRenderer 绘制渲染

public class DouyinRenderer implements GLSurfaceView.Renderer, SurfaceTexture.OnFrameAvailableListener {

    private ScreenFilter mScreenFilter;
    private DouyinView mView;
    private CameraHelper mCameraHelper;
    private SurfaceTexture mSurfaceTexture;
    private float[] mtx = new float[16];
    private int[] mTextures;

    public DouyinRenderer(DouyinView douyinView) {
        mView = douyinView;
    }

    /**
     * 画布创建好啦
     *
     * @param gl
     * @param config
     */
    @Override
    public void onSurfaceCreated(GL10 gl, EGLConfig config) {
        //初始化的操作
        mCameraHelper = new CameraHelper(Camera.CameraInfo.CAMERA_FACING_BACK);
        //准备好摄像头绘制的画布
        //通过opengl创建一个纹理id
        mTextures = new int[1];
        GLES20.glGenTextures(mTextures.length, mTextures, 0);
        mSurfaceTexture = new SurfaceTexture(mTextures[0]);
        //
        mSurfaceTexture.setOnFrameAvailableListener(this);
        //注意:必须在gl线程操作opengl
        mScreenFilter = new ScreenFilter(mView.getContext());
    }


    /**
     * 画布发生了改变
     *
     * @param gl
     * @param width
     * @param height
     */
    @Override
    public void onSurfaceChanged(GL10 gl, int width, int height) {
        //开启预览
        mCameraHelper.startPreview(mSurfaceTexture);
        mScreenFilter.onReady(width, height);
    }
    /**
     * 开始画画吧
     *
     * @param gl
     */
    @Override
    public void onDrawFrame(GL10 gl) {
        // 配置屏幕
        //清理屏幕 :告诉opengl 需要把屏幕清理成什么颜色
        GLES20.glClearColor(0, 0, 0, 0);
        //执行上一个:glClearColor配置的屏幕颜色
        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);

        // 把摄像头的数据先输出来
        // 更新纹理,然后我们才能够使用opengl从SurfaceTexure当中获得数据 进行渲染
        mSurfaceTexture.updateTexImage();
        //surfaceTexture 比较特殊,在opengl当中 使用的是特殊的采样器 samplerExternalOES (不是sampler2D)
        //获得变换矩阵
        mSurfaceTexture.getTransformMatrix(mtx);

        mScreenFilter.onDrawFrame(mTextures[0],mtx);
    }

    /**
     * surfaceTexture 有一个有效的新数据的时候回调
     */
    @Override
    public void onFrameAvailable(SurfaceTexture surfaceTexture) {
        mView.requestRender();
    }


}

3 定义顶点着色器和片元着色器

可以任意文件后缀名,甚至直接字符串定义

camera_vertex.vert

#extension GL_OES_EGL_image_external : require
//SurfaceTexture比较特殊
//float数据是什么精度的
precision mediump float;

//采样点的坐标
varying vec2 aCoord;

//采样器
uniform samplerExternalOES vTexture;

void main(){
    //变量 接收像素值
    // texture2D:采样器 采集 aCoord的像素
    //赋值给 gl_FragColor 就可以了
    gl_FragColor = texture2D(vTexture,aCoord);
}

camera_frag.frag

#extension GL_OES_EGL_image_external : require
//SurfaceTexture比较特殊
//float数据是什么精度的
precision mediump float;

//采样点的坐标
varying vec2 aCoord;

//采样器
uniform samplerExternalOES vTexture;

void main(){
    //变量 接收像素值
    // texture2D:采样器 采集 aCoord的像素
    //赋值给 gl_FragColor 就可以了
    gl_FragColor = texture2D(vTexture,aCoord);
}

4 ScreenFilter 屏幕渲染

public class ScreenFilter {

    private FloatBuffer mTextureBuffer;
    private FloatBuffer mVertexBuffer;
    private int vTexture;
    private int vMatrix;
    private int vCoord;
    private int vPosition;
    private int mProgram;
    private int mWidth;
    private int mHeight;

    public ScreenFilter(Context context) {
        //camera_vertex 中的内容读出字符串
        String vertexSource = OpenUtils.readRawTextFile(context, R.raw.camera_vertex);
        String fragSource = OpenUtils.readRawTextFile(context, R.raw.camera_frag);

        //通过字符串(代码)创建着色器程序
        //使用opengl
        //1、创建顶点着色器
        // 1.1
        int vShaderId = GLES20.glCreateShader(GLES20.GL_VERTEX_SHADER);
        // 1.2 绑定代码到着色器中去
        GLES20.glShaderSource(vShaderId, vertexSource);
        // 1.3 编译着色器代码
        GLES20.glCompileShader(vShaderId);
        //主动获取成功、失败 (如果不主动查询,只输出 一条 GLERROR之类的日志,很难定位到到底是那里出错)
        int[] status = new int[1];
        GLES20.glGetShaderiv(vShaderId, GLES20.GL_COMPILE_STATUS, status, 0);
        if (status[0] != GLES20.GL_TRUE) {
            throw new IllegalStateException("ScreenFilter 顶点着色器配置失败!");
        }
        //2、创建片元着色器
        int fShaderId = GLES20.glCreateShader(GLES20.GL_FRAGMENT_SHADER);
        GLES20.glShaderSource(fShaderId, fragSource);
        GLES20.glCompileShader(fShaderId);
        GLES20.glGetShaderiv(fShaderId, GLES20.GL_COMPILE_STATUS, status, 0);
        if (status[0] != GLES20.GL_TRUE) {
            throw new IllegalStateException("ScreenFilter 片元着色器配置失败!");
        }
        //3、创建着色器程序 (GPU上的小程序)
        mProgram = GLES20.glCreateProgram();
        //把着色器塞到程序当中
        GLES20.glAttachShader(mProgram, vShaderId);
        GLES20.glAttachShader(mProgram, fShaderId);

        //链接着色器
        GLES20.glLinkProgram(mProgram);

        //获得程序是否配置成功
        GLES20.glGetProgramiv(mProgram, GLES20.GL_LINK_STATUS, status, 0);
        if (status[0] != GLES20.GL_TRUE) {
            throw new IllegalStateException("ScreenFilter 着色器程序配置失败!");
        }

        //因为已经塞到着色器程序中了,所以删了没关系
        GLES20.glDeleteShader(vShaderId);
        GLES20.glDeleteShader(fShaderId);


        //获得着色器程序中的变量的索引, 通过这个索引来给着色器中的变量赋值
        /**
         * 顶点
         */
        vPosition = GLES20.glGetAttribLocation(mProgram, "vPosition");
        vCoord = GLES20.glGetAttribLocation(mProgram, "vCoord");
        vMatrix = GLES20.glGetUniformLocation(mProgram, "vMatrix");
        //片元
        vTexture = GLES20.glGetUniformLocation(mProgram, "vTexture");


        //创建一个数据缓冲区
        //4个点 每个点两个数据(x,y) 数据类型float
        //顶点坐标
        mVertexBuffer = ByteBuffer.allocateDirect(4 * 2 * 4).order(ByteOrder.nativeOrder())
                .asFloatBuffer();
        mVertexBuffer.clear();
        float[] v = {-1.0f, -1.0f,
                1.0f, -1.0f,
                -1.0f, 1.0f,
                1.0f, 1.0f};
        mVertexBuffer.put(v);


        mTextureBuffer = ByteBuffer.allocateDirect(4 * 2 * 4).order(ByteOrder.nativeOrder())
                .asFloatBuffer();
        mTextureBuffer.clear();
//        float[] t = {0.0f, 1.0f,
//                1.0f, 1.0f,
//                0.0f, 0.0f,
//                1.0f, 0.0f};
        //旋转
//        float[] t = {1.0f, 1.0f,
//                1.0f, 0.0f,
//                0.0f, 1.0f,
//                0.0f, 0.0f};
        //镜像
        float[] t = {1.0f, 0.0f,
                1.0f, 1.0f,
                0.0f, 0.0f,
                0.0f, 1.0f
        };
        mTextureBuffer.put(t);
    }


    public void onDrawFrame(int texture, float[] mtx) {
        //1、设置窗口大小
        //画画的时候 你的画布可以看成 10x10,也可以看成5x5 等等
        //设置画布的大小,然后画画的时候, 画布越大,你画上去的图像就会显得越小
        // x与y 就是从画布的哪个位置开始画
        GLES20.glViewport(0, 0, mWidth, mHeight);

        //使用着色器程序
        GLES20.glUseProgram(mProgram);

        // 怎么画? 其实就是传值
        //2:xy两个数据 float的类型
        //1、将顶点数据传入,确定形状
        mVertexBuffer.position(0);
        GLES20.glVertexAttribPointer(vPosition, 2, GLES20.GL_FLOAT, false, 0, mVertexBuffer);
        //传了数据之后 激活
        GLES20.glEnableVertexAttribArray(vPosition);

        //2、将纹理坐标传入,采样坐标
        mTextureBuffer.position(0);
        GLES20.glVertexAttribPointer(vCoord, 2, GLES20.GL_FLOAT, false, 0, mTextureBuffer);
        GLES20.glEnableVertexAttribArray(vCoord);

        //3、变换矩阵
        GLES20.glUniformMatrix4fv(vMatrix,1,false,mtx,0);

        //片元 vTexture 绑定图像数据到采样器
        //激活图层
        GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
        // 图像数据
        // 正常:GLES20.GL_TEXTURE_2D
        // surfaceTexure的纹理需要
        GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,texture);
        //传递参数 0:需要和纹理层GL_TEXTURE0对应
        GLES20.glUniform1i(vTexture,0);

        //参数传完了 通知opengl 画画 从第0点开始 共4个点
        GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP,0,4);

    }

    public void onReady(int width, int height) {
        mWidth = width;
        mHeight = height;
    }
}

5 工具类

CameraHelper

public class CameraHelper implements Camera.PreviewCallback {

    private static final String TAG = "CameraHelper";
    public static final int WIDTH = 640;
    public static final int HEIGHT = 480;
    private int mCameraId;
    private Camera mCamera;
    private byte[] buffer;
    private Camera.PreviewCallback mPreviewCallback;
    private SurfaceTexture mSurfaceTexture;

    public CameraHelper(int cameraId) {
        mCameraId = cameraId;
    }

    public void switchCamera() {
        if (mCameraId == Camera.CameraInfo.CAMERA_FACING_BACK) {
            mCameraId = Camera.CameraInfo.CAMERA_FACING_FRONT;
        } else {
            mCameraId = Camera.CameraInfo.CAMERA_FACING_BACK;
        }
        stopPreview();
        startPreview(mSurfaceTexture);
    }

    public int getCameraId() {
        return mCameraId;
    }

    public void stopPreview() {
        if (mCamera != null) {
            //预览数据回调接口
            mCamera.setPreviewCallback(null);
            //停止预览
            mCamera.stopPreview();
            //释放摄像头
            mCamera.release();
            mCamera = null;
        }
    }

    public void startPreview(SurfaceTexture surfaceTexture) {
        mSurfaceTexture = surfaceTexture;
        try {
            //获得camera对象
            mCamera = Camera.open(mCameraId);
            //配置camera的属性
            Camera.Parameters parameters = mCamera.getParameters();
            //设置预览数据格式为nv21
            parameters.setPreviewFormat(ImageFormat.NV21);
            //这是摄像头宽、高
            parameters.setPreviewSize(WIDTH, HEIGHT);
            // 设置摄像头 图像传感器的角度、方向
            mCamera.setParameters(parameters);
            buffer = new byte[WIDTH * HEIGHT * 3 / 2];
            //数据缓存区
            mCamera.addCallbackBuffer(buffer);
            mCamera.setPreviewCallbackWithBuffer(this);
            //设置预览画面
            mCamera.setPreviewTexture(mSurfaceTexture);
            mCamera.startPreview();
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }


    public void setPreviewCallback(Camera.PreviewCallback previewCallback) {
        mPreviewCallback = previewCallback;
    }


    @Override
    public void onPreviewFrame(byte[] data, Camera camera) {
        // data数据依然是倒的
        if (null != mPreviewCallback) {
            mPreviewCallback.onPreviewFrame(data, camera);
        }
        camera.addCallbackBuffer(buffer);
    }


}

OpenUtils

public class OpenUtils {

    public static 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();
    }
}