void onSurfaceCreated(GL10 gl, EGLConfig config);
/**
-
Called when the surface changed size.
-
-
Called after the surface is created and whenever
-
the OpenGL ES surface size changes.
-
-
Typically you will set your viewport here. If your camera
-
is fixed then you could also set your projection matrix here:
-
void onSurfaceChanged(GL10 gl, int width, int height) {
-
gl.glViewport(0, 0, width, height);
-
// for a fixed camera, set the projection too
-
float ratio = (float) width / height;
-
gl.glMatrixMode(GL10.GL_PROJECTION);
-
gl.glLoadIdentity();
-
gl.glFrustumf(-ratio, ratio, -1, 1, 1, 10);
-
}
-
@param gl the GL interface. Use
instanceof
to -
test if the interface supports GL11 or higher interfaces.
-
@param width
-
@param height
*/
void onSurfaceChanged(GL10 gl, int width, int height);
/**
-
Called to draw the current frame.
-
-
This method is responsible for drawing the current frame.
-
-
The implementation of this method typically looks like this:
-
void onDrawFrame(GL10 gl) {
-
gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
-
//... other gl calls to render the scene ...
-
}
-
@param gl the GL interface. Use
instanceof
to -
test if the interface supports GL11 or higher interfaces.
*/
void onDrawFrame(GL10 gl);
}
void onSurfaceCreated(GL10 gl, EGLConfig config)
在Surface创建或重建的情况下回调
void onSurfaceChanged(GL10 gl, int width, int height)
在Surface的大小发生变化的情况下回调
void onDrawFrame(GL10 gl)
在这里实现绘制操作。当我们设置的renderMode
为RENDERMODE_CONTINUOUSLY
时,该函数将不断地执行;
当我们设置的renderMode
为RENDERMODE_WHEN_DIRTY
时,将只在创建完成和调用requestRender
后才执行。一般我们选择RENDERMODE_WHEN_DIRTY
渲染模式,避免过度绘制。
一般情况下,我们会自己实现一个Renderer,然后为GLSurfaceView设置Renderer,可以说,Renderer的编写是整个流程的核心步骤。以下是在void onSurfaceCreated(GL10 gl, EGLConfig config)
进行的初始化操作和在void onDrawFrame(GL10 gl)
进行的绘制操作的流程图:
2. 具体实现
- 坐标系介绍
如图所示,和Android的View坐标系不同,OpenGL的坐标系是笛卡尔坐标系。
Android View的坐标系以左上角为原点,向右x递增,向下y递增;
而OpenGL坐标系以中心为原点,向右x递增,向上y递增。
- 着色器编写
/**
- 顶点着色器
*/
private static String VERTEX_SHADER =
" attribute vec4 attr_position;\n" +
" attribute vec2 attr_tc;\n" +
" varying vec2 tc;\n" +
" void main() {\n" +
" gl_Position = attr_position;\n" +
" tc = attr_tc;\n" +
" }";
/**
- 片段着色器
*/
private static String FRAG_SHADER =
" varying vec2 tc;\n" +
" uniform sampler2D ySampler;\n" +
" uniform sampler2D uSampler;\n" +
" uniform sampler2D vSampler;\n" +
" const mat3 convertMat = mat3( 1.0, 1.0, 1.0, -0.001, -0.3441, 1.772, 1.402, -0.7141, -0.58060);\n" +
" void main()\n" +
" {\n" +
" vec3 yuv;\n" +
" yuv.x = texture2D(ySampler, tc).r;\n" +
" yuv.y = texture2D(uSampler, tc).r - 0.5;\n" +
" yuv.z = texture2D(vSampler, tc).r - 0.5;\n" +
" gl_FragColor = vec4(convertMat * yuv, 1.0);\n" +
" }";
-
内建变量解释
-
gl_Position
VERTEX_SHADER
代码里的gl_Position
代表绘制的空间坐标。由于我们是二维绘制,所以直接传入OpenGL二维坐标系的左下(-1,-1)、右下(1,-1)、左上(-1,1)、右上(1,1),也就是{-1,-1,1,-1,-1,1,1,1}
gl_FragColor
FRAG_SHADER
代码里的gl_FragColor
代表单个片元的颜色
-
其他变量解释
-
ySampler
、uSampler
、vSampler
分别代表Y、U、V纹理采样器
convertMat
根据以下公式:
R = Y + 1.402 (V - 128)
G = Y - 0.34414 (U - 128) - 0.71414 (V - 128)
B = Y + 1.772 (U - 128)
我们可得到一个YUV转RGB的矩阵
1.0, 1.0, 1.0,
0, -0.344, 1.77,
1.403, -0.714, 0
-
部分类型、函数的解释
-
vec3、vec4
分别代表三维向量、四维向量。
vec4 texture2D(sampler2D sampler, vec2 coord)
以指定的矩阵将采样器的图像纹理转换为颜色值;如:
texture2D(ySampler, tc).r
获取到的是Y数据,
texture2D(uSampler, tc).r
获取到的是U数据,
texture2D(vSampler, tc).r
获取到的是V数据。
- 在Java代码中进行初始化
根据图像宽高创建Y、U、V对应的ByteBuffer
纹理数据;
根据是否镜像显示、旋转角度选择对应的转换矩阵;
public void init(boolean isMirror, int rotateDegree, int frameWidth, int frameHeight) {
if (this.frameWidth == frameWidth
&& this.frameHeight == frameHeight
&& this.rotateDegree == rotateDegree
&& this.isMirror == isMirror) {
return;
}
dataInput = false;
this.frameWidth = frameWidth;
this.frameHeight = frameHeight;
this.rotateDegree = rotateDegree;
this.isMirror = isMirror;
yArray = new byte[this.frameWidth * this.frameHeight];
uArray = new byte[this.frameWidth * this.frameHeight / 4];
vArray = new byte[this.frameWidth * this.frameHeight / 4];
int yFrameSize = this.frameHeight * this.frameWidth;
int uvFrameSize = yFrameSize >> 2;
yBuf = ByteBuffer.allocateDirect(yFrameSize);
yBuf.order(ByteOrder.nativeOrder()).position(0);
uBuf = ByteBuffer.allocateDirect(uvFrameSize);
uBuf.order(ByteOrder.nativeOrder()).position(0);
vBuf = ByteBuffer.allocateDirect(uvFrameSize);
vBuf.order(ByteOrder.nativeOrder()).position(0);
// 顶点坐标
squareVertices = ByteBuffer
.allocateDirect(GLUtil.SQUARE_VERTICES.length * FLOAT_SIZE_BYTES)
.order(ByteOrder.nativeOrder())
.asFloatBuffer();
squareVertices.put(GLUtil.SQUARE_VERTICES).position(0);
//纹理坐标
if (isMirror) {
switch (rotateDegree) {
case 0:
coordVertice = GLUtil.MIRROR_COORD_VERTICES;
break;
case 90:
coordVertice = GLUtil.ROTATE_90_MIRROR_COORD_VERTICES;
break;
case 180:
coordVertice = GLUtil.ROTATE_180_MIRROR_COORD_VERTICES;
break;
case 270:
coordVertice = GLUtil.ROTATE_270_MIRROR_COORD_VERTICES;
break;
default:
break;
}
} else {
switch (rotateDegree) {
case 0:
coordVertice = GLUtil.COORD_VERTICES;
break;
case 90:
coordVertice = GLUtil.ROTATE_90_COORD_VERTICES;
break;
case 180:
coordVertice = GLUtil.ROTATE_180_COORD_VERTICES;
break;
case 270:
coordVertice = GLUtil.ROTATE_270_COORD_VERTICES;
break;
default:
break;
}
}
coordVertices = ByteBuffer.allocateDirect(coordVertice.length * FLOAT_SIZE_BYTES).order(ByteOrder.nativeOrder()).asFloatBuffer();
coordVertices.put(coordVertice).position(0);
}
在Surface创建完成时进行Renderer初始化
private void initRenderer() {
rendererReady = false;
createGLProgram();
//启用纹理
GLES20.glEnable(GLES20.GL_TEXTURE_2D);
//创建纹理
createTexture(frameWidth, frameHeight, GLES20.GL_LUMINANCE, yTexture);
createTexture(frameWidth / 2, frameHeight / 2, GLES20.GL_LUMINANCE, uTexture);
createTexture(frameWidth / 2, frameHeight / 2, GLES20.GL_LUMINANCE, vTexture);
rendererReady = true;
}
其中createGLProgram
用于创建OpenGL Program并关联着色器代码中的变量
private void createGLProgram() {
int programHandleMain = GLUtil.createShaderProgram();
if (programHandleMain != -1) {
// 使用着色器程序
GLES20.glUseProgram(programHandleMain);
// 获取顶点着色器变量
int glPosition = GLES20.glGetAttribLocation(programHandleMain, “attr_position”);
int textureCoord = GLES20.glGetAttribLocation(programHandleMain, “attr_tc”);
// 获取片段着色器变量
int ySampler = GLES20.glGetUniformLocation(programHandleMain, “ySampler”);
int uSampler = GLES20.glGetUniformLocation(programHandleMain, “uSampler”);
int vSampler = GLES20.glGetUniformLocation(programHandleMain, “vSampler”);
//给变量赋值
/**
-
GLES20.GL_TEXTURE0 和 ySampler 绑定
-
GLES20.GL_TEXTURE1 和 uSampler 绑定
-
GLES20.GL_TEXTURE2 和 vSampler 绑定
-
也就是说 glUniform1i的第二个参数代表图层序号
*/
GLES20.glUniform1i(ySampler, 0);
GLES20.glUniform1i(uSampler, 1);
GLES20.glUniform1i(vSampler, 2);
GLES20.glEnableVertexAttribArray(glPosition);
GLES20.glEnableVertexAttribArray(textureCoord);
/**
- 设置Vertex Shader数据
*/
squareVertices.position(0);
GLES20.glVertexAttribPointer(glPosition, GLUtil.COUNT_PER_SQUARE_VERTICE, GLES20.GL_FLOAT, false, 8, squareVertices);
coordVertices.position(0);
GLES20.glVertexAttribPointer(textureCoord, GLUtil.COUNT_PER_COORD_VERTICES, GLES20.GL_FLOAT, false, 8, coordVertices);
}
}
其中createTexture
用于根据宽高和格式创建纹理
private void createTexture(int width, int height, int format, int[] textureId) {
//创建纹理
GLES20.glGenTextures(1, textureId, 0);
//绑定纹理
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureId[0]);
/**
-
{@link GLES20#GL_TEXTURE_WRAP_S}代表左右方向的纹理环绕模式
-
{@link GLES20#GL_TEXTURE_WRAP_T}代表上下方向的纹理环绕模式
-
{@link GLES20#GL_REPEAT}:重复
-
{@link GLES20#GL_MIRRORED_REPEAT}:镜像重复
-
{@link GLES20#GL_CLAMP_TO_EDGE}:忽略边框截取
-
例如我们使用{@link GLES20#GL_REPEAT}:
-
squareVertices coordVertices
-
-1.0f, -1.0f, 1.0f, 1.0f,
-
1.0f, -1.0f, 1.0f, 0.0f, -> 和textureView预览相同
-
-1.0f, 1.0f, 0.0f, 1.0f,
-
1.0f, 1.0f 0.0f, 0.0f
-
squareVertices coordVertices
-
-1.0f, -1.0f, 2.0f, 2.0f,
-
1.0f, -1.0f, 2.0f, 0.0f, -> 和textureView预览相比,分割成了4 块相同的预览(左下,右下,左上,右上)
-
-1.0f, 1.0f, 0.0f, 2.0f,
-
1.0f, 1.0f 0.0f, 0.0f
*/
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_REPEAT);
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_REPEAT);
/**
-
{@link GLES20#GL_TEXTURE_MIN_FILTER}代表所显示的纹理比加载进来的纹理小时的情况
-
{@link GLES20#GL_TEXTURE_MAG_FILTER}代表所显示的纹理比加载进来的纹理大时的情况
-
{@link GLES20#GL_NEAREST}:使用纹理中坐标最接近的一个像素的颜色作为需要绘制的像素颜色
-
{@link GLES20#GL_LINEAR}:使用纹理中坐标最接近的若干个颜色,通过加权平均算法得到需要绘制的像素颜色
*/
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST);
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, format, width, height, 0, format, GLES20.GL_UNSIGNED_BYTE, null);
}
- 在Java代码中调用绘制
在数据源获取到时裁剪并传入帧数据
@Override
public void onPreview(final byte[] nv21, Camera camera) {
//裁剪指定的图像区域
ImageUtil.cropNV21(nv21, this.squareNV21, previewSize.width, previewSize.height, cropRect);
//刷新GLSurfaceView
roundCameraGLSurfaceView.refreshFrameNV21(this.squareNV21);
}
NV21数据裁剪代码
/**
-
裁剪NV21数据
-
@param originNV21 原始的NV21数据
-
@param cropNV21 裁剪结果NV21数据,需要预先分配内存
-
@param width 原始数据的宽度
-
@param height 原始数据的高度
-
@param left 原始数据被裁剪的区域的左边界
-
@param top 原始数据被裁剪的区域的上边界
-
@param right 原始数据被裁剪的区域的右边界
-
@param bottom 原始数据被裁剪的区域的下边界
*/
public static void cropNV21(byte[] originNV21, byte[] cropNV21, int width, int height, int left, int top, int right, int bottom) {
int halfWidth = width / 2;
int cropImageWidth = right - left;
int cropImageHeight = bottom - top;
//原数据Y左上
int originalYLineStart = top * width;
int targetYIndex = 0;
//原数据UV左上
int originalUVLineStart = width * height + top * halfWidth;
//目标数据的UV起始值
int targetUVIndex = cropImageWidth * cropImageHeight;
for (int i = top; i < bottom; i++) {
System.arraycopy(originNV21, originalYLineStart + left, cropNV21, targetYIndex, cropImageWidth);
originalYLineStart += width;
targetYIndex += cropImageWidth;
if ((i & 1) == 0) {
System.arraycopy(originNV21, originalUVLineStart + left, cropNV21, targetUVIndex, cropImageWidth);
originalUVLineStart += width;
targetUVIndex += cropImageWidth;
}
}
}
传给GLSurafceView并刷新帧数据
/**
-
传入NV21刷新帧
-
@param data NV21数据
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新
如果你觉得这些内容对你有帮助,可以添加V获取:vip204888 (备注Android)
小福利:
在当下这个碎片化信息环境的时代,很多资源都可以在网络上找到,只取决于你愿不愿意找或是找的方法对不对了
很多朋友不是没有资料,大多都是有几十上百个G,但是杂乱无章,不知道怎么看从哪看起,甚至是看后就忘
如果大家觉得自己在网上找的资料非常杂乱、不成体系的话,我也分享一套给大家,比较系统,我平常自己也会经常研读。
2021大厂最新Android面试真题解析
各个模块学习视频:如数据结构与算法
只有系统,有方向的学习,才能在段时间内迅速提高自己的技术。
这份体系学习笔记,适应人群:**第一,**学习知识比较碎片化,没有合理的学习路线与进阶方向。**第二,**开发几年,不知道如何进阶更进一步,比较迷茫。第三,到了合适的年纪,后续不知道该如何发展,转型管理,还是加强技术研究。如果你有需要,我这里恰好有为什么,不来领取!说不定能改变你现在的状态呢!点赞+评论即可获得!
阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!**
由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新
如果你觉得这些内容对你有帮助,可以添加V获取:vip204888 (备注Android)
[外链图片转存中…(img-GmvTt80x-1711735932676)]
小福利:
在当下这个碎片化信息环境的时代,很多资源都可以在网络上找到,只取决于你愿不愿意找或是找的方法对不对了
很多朋友不是没有资料,大多都是有几十上百个G,但是杂乱无章,不知道怎么看从哪看起,甚至是看后就忘
如果大家觉得自己在网上找的资料非常杂乱、不成体系的话,我也分享一套给大家,比较系统,我平常自己也会经常研读。
2021大厂最新Android面试真题解析
[外链图片转存中…(img-823Qa7hT-1711735932676)]
各个模块学习视频:如数据结构与算法
[外链图片转存中…(img-MckvnmwZ-1711735932676)]
只有系统,有方向的学习,才能在段时间内迅速提高自己的技术。
[外链图片转存中…(img-RHnR2y6o-1711735932677)]
[外链图片转存中…(img-QmD9p4NQ-1711735932677)]
这份体系学习笔记,适应人群:**第一,**学习知识比较碎片化,没有合理的学习路线与进阶方向。**第二,**开发几年,不知道如何进阶更进一步,比较迷茫。第三,到了合适的年纪,后续不知道该如何发展,转型管理,还是加强技术研究。如果你有需要,我这里恰好有为什么,不来领取!说不定能改变你现在的状态呢!点赞+评论即可获得!