前言
1,做直播,要先美颜在推流
2,做直播三个数据,美颜数据,本地录制,推到网络。
3,图片美颜是通过openGL在GPU中做
架构
1,把图片渲染到OpenGL
2,
3,
4,openGL 世界坐标系
5,vPosition传递的是世界坐标系
6,Bitmap在代码里加载,代码运行再CPU,我们首先要做的事情就是要把bitmap从cpu传到GPU。要首先再GPU里准备个容器接收Bitmap,这个容器就叫纹理。
7,什么是纹理?
代码
package com.maniu.openglimage;
import static android.opengl.GLES20.GL_TEXTURE_2D;
import android.content.Context;
import android.graphics.Bitmap;
import android.opengl.GLES20;
import android.opengl.GLUtils;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
//加载opengl程序
public class ImageFilter {
protected int mProgId;
//世界坐标系,我们绘制的是一个矩形,传入这几个点就够啦
static final float COORD1[] = {
-1.0f, -1.0f,
1.0f, -1.0f,
-1.0f, 1.0f,
1.0f, 1.0f
};
//Android 布局坐标系
static final float TEXTURE_COORD1[] = {
0.0f, 1.0f,
1.0f, 1.0f,
0.0f, 0.0f,
1.0f, 0.0f,
};
protected int mInputTexture;
private String mVertexShader;
private String mFragmentShader;
private FloatBuffer mPositionBuffer;
private FloatBuffer mCoordBuffer;
protected int mPosition;
protected int vCoord;
public ImageFilter(Context context) {
this(OpenGLUtils.readRawTextFile(context,R.raw.base_vert), OpenGLUtils.readRawTextFile(context,R.raw.base_frag));
}
public ImageFilter(String vertexShader, String fragmentShader) {
// 顶点程序读到vertexShader
mVertexShader = vertexShader;
// 片元程序读到fragmentShader
mFragmentShader = fragmentShader;
}
public void initShader() {
//加载程序
mProgId = OpenGLUtils.loadProgram(mVertexShader, mFragmentShader);
// 找到GPU的变量 第一步:定位变量在GPU的位置;第二部:实例化一个通道FloteBuffer
// 把cpu的值(如COORD1)传进来 第三步,把FloateBuffer的值再给GPU的变量
mPosition = GLES20.glGetAttribLocation(mProgId, "vPosition");
vCoord = GLES20.glGetAttribLocation(mProgId,
"vCoord");
mInputTexture = GLES20.glGetUniformLocation(mProgId, "inputImageTexture");
}
//顶点程序
public void loadVertex() {
float[] coord = COORD1;
float[] texture_coord = TEXTURE_COORD1;
// 四个坐标,所以乘以4
mPositionBuffer= ByteBuffer.allocateDirect(coord.length * 4)
.order(ByteOrder.nativeOrder())
.asFloatBuffer();
mPositionBuffer.put(coord).position(0);
mCoordBuffer = ByteBuffer.allocateDirect(texture_coord.length * 4)
.order(ByteOrder.nativeOrder())
.asFloatBuffer();
mCoordBuffer.put(texture_coord).position(0);
}
public int init(Bitmap bitmap) {
initShader();
loadVertex();
return initTexture(bitmap);
}
// 生成纹理层,接收Bitmap
private int initTexture( Bitmap bitmap) {
// 入参出参对象,因为生成一个,所以填1
int[] textures = new int[1];
// 生成纹理,1:生成几个;textures:纹理ID;第三个参数一般是0
GLES20.glGenTextures(1, textures, 0);
// 使用纹理,绑定后对GLES20的操作都是针对这个纹理(textures)。最后一个参数如果传0就表示解绑
GLES20.glBindTexture(GL_TEXTURE_2D, textures[0]);
//给纹理设置属性
// GLES20.GL_TEXTURE_2D表示2D纹理,
// GLES20.GL_TEXTURE_MAG_FILTER 过滤方式
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER,
GLES20.GL_NEAREST);
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER,
GLES20.GL_LINEAR);
// GLES20.GL_TEXTURE_WRAP_S代表水平方向
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,
GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE);
// GLES20.GL_TEXTURE_WRAP_T代表垂直方向
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T,
GLES20.GL_CLAMP_TO_EDGE);
// 有请 bitmap,第一个参数表示ID,第四个参数border:边缘边界
// 现在我们把bitmap传输到了GPU的纹理层。接下来针对纹理层采样就可以了,
//把bitmap中的像素一个一个的采样到矩形中就可以了,这样矩形就显示一张图片了。。
GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bitmap, 0);
return textures[0];
}
public int drawFrame(int glTextureId){
GLES20.glUseProgram(mProgId);
mPositionBuffer.position(0);
// 把数据从CPUmPositionBuffer传到GPUmPosition。立体的就是3,二维的就是2
GLES20.glVertexAttribPointer(mPosition, 2, GLES20.GL_FLOAT, false, 0, mPositionBuffer);
GLES20.glEnableVertexAttribArray(mPosition);
mCoordBuffer.position(0);
GLES20.glVertexAttribPointer(vCoord, 2, GLES20.GL_FLOAT, false, 0,
mCoordBuffer);
GLES20.glEnableVertexAttribArray(vCoord);
// 激活一个图层,给纹理使用(上一篇文章我们是个CameraX数据使用)
GLES20.glActiveTexture(0);
// 图层和纹理绑定
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D,glTextureId );
// 第0个图层
GLES20.glUniform1i(mInputTexture, 0);
// 绘制
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
//图层使用完要解绑,0就代表解绑
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0);
return glTextureId;
}
}
package com.maniu.openglimage;
import androidx.appcompat.app.AppCompatActivity;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.opengl.GLES20;
import android.opengl.GLSurfaceView;
import android.os.Bundle;
import android.view.ViewGroup;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;
public class MainActivity extends AppCompatActivity {
private GLSurfaceView mSurfaceView;
private int mTextureId =-1;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ViewGroup container = (ViewGroup) findViewById(R.id.main);
mSurfaceView = new GLSurfaceView(this);
mSurfaceView.setEGLContextClientVersion(2);
container.addView(mSurfaceView);
final ImageFilter filter = new ImageFilter(this);
// 要把Bitmap从CPU传到GPU
final Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.demo);
mSurfaceView.setRenderer(new GLSurfaceView.Renderer() {
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
// 把图片以openGL方式渲染到屏幕上
mTextureId= filter.init(bitmap);
}
@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
// 告诉opengl窗口是多少,surfaceChanged可能会改变
GLES20.glViewport(0, 0, width, height);
}
//渲染就会调用该方法,
@Override
public void onDrawFrame(GL10 gl) {
filter.drawFrame(mTextureId);
}
});
}
}
varying highp vec2 textureCoordinate;
uniform sampler2D inputImageTexture;//0图层
//片元
void main(){
// openGL渲染到Bitmap,texture2D就是采样器
gl_FragColor = texture2D(inputImageTexture, textureCoordinate);
// gl_FragColor = vec4(0,255,0,0);如果想变成绿色,改这里。
}
attribute vec4 vPosition;
attribute vec4 vCoord;
//varying修饰的就是传值的变量
varying vec2 textureCoordinate;
//顶点
void main(){
//vPosition:传递的是世界坐标系
// 告诉opengl,我这里绘制的是矩形
gl_Position = vPosition;
//传给纹理坐标系的坐标
textureCoordinate = vCoord.xy;
}
package com.maniu.openglimage;
import android.content.Context;
import android.opengl.GLES20;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
public class OpenGLUtils {
//100% 相同
public static int loadProgram(String vSource, String fSource) {
//
/**
* 顶点着色器
*/
int vShader = GLES20.glCreateShader(GLES20.GL_VERTEX_SHADER);
//加载着色器代码
GLES20.glShaderSource(vShader, vSource);
//编译(配置)
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));
}
/**
* 片元着色器
* 流程和上面一样
*/
int fShader = GLES20.glCreateShader(GLES20.GL_FRAGMENT_SHADER);
//加载着色器代码
GLES20.glShaderSource(fShader, fSource);
//编译(配置)
GLES20.glCompileShader(fShader);
//查看配置 是否成功
GLES20.glGetShaderiv(fShader, GLES20.GL_COMPILE_STATUS, status, 0);
if (status[0] != GLES20.GL_TRUE) {
//失败
throw new IllegalStateException("load fragment shader:" + GLES20.glGetShaderInfoLog
(vShader));
}
/**
* 创建着色器程序
*/
int program = GLES20.glCreateProgram();
//绑定顶点和片元
GLES20.glAttachShader(program, vShader);
GLES20.glAttachShader(program, fShader);
//链接着色器程序
GLES20.glLinkProgram(program);
//获得状态
GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, status, 0);
if (status[0] != GLES20.GL_TRUE) {
throw new IllegalStateException("link program:" + GLES20.glGetProgramInfoLog(program));
}
// 用完了一定要释放掉,
GLES20.glDeleteShader(vShader);
GLES20.glDeleteShader(fShader);
// program就是总程序
return program;
}
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();
}
}