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