文章目录
一 什么是OpenGL?
Open Graphics Library
图形领域的工业标准,是一套跨编程语言、跨平台的、专业的图形编程(软件)接口。它用于二维、三维图像,是一个功能强大,调用方便的底层图形库。
与硬件无关。可以在不同的平台如Windows、Linux、Mac、Android、IOS之间进行移植。因此,支持OpenGL的软件具有很好的移植性,可以获得非常广泛的应用。
- 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绘制流程
可编程管线
OpenGL坐标系
Shader着色器
着色器(Shader)是运行在GPU上的小程序
顶点着色器(vertex shader)
如何处理顶点、法线等数据的小程序。
片元着色器(fragment shader)
如何处理光、阴影、遮挡、环境等等对物体表面的影响,最终生成一副图像的小程序
三 GLSL
OpenGL着色语言(OpenGL Shading Language)
数据类型
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();
}
}