前言
OpenGL是Android音视频开发绕不过去的东西,书接上文,【Android音视频】简简单单入门OpenGL ES加载一张小猫咪图片吧!加载出来的小猫咪图片是倒着的,并且还把猫脸拉长了。
像下图这样:
本文就分析一下为啥会这样,然后将它摆正。
有兴趣可以拷一份代码一起看看:github.com/MReP1/OpenG…。
1、分析原因
1.1、猫咪翻转
倒过来也分两种情况,一种是旋转180度,另外一种是垂直翻转,而从结果来看,我们展示的小猫咪是垂直翻转了。
因为在默认情况下,OpenGL ES 中的纹理坐标系的原点在左下角,而图像数据通常是从左上角开始存储的。因此,当加载图像数据到纹理中时,图像会在垂直方向上翻转。
1.2、图片拉伸
而图片被拉伸的原因是因为纹理坐标和顶点坐标的映射不正确。我们看一下代码中的顶点着色器的shader代码。
private val VERTEX_SHADER_STRING = """
#version 300 es
precision mediump float;
layout(location = 0) in vec4 position;
layout(location = 1) in vec4 inputTextureCoordinate;
uniform mat4 textureTransform;
out vec2 textureCoordinate;
void main()
{
gl_Position = position;
textureCoordinate = (textureTransform * inputTextureCoordinate).xy;
}
""".trimIndent()
private const val glPositionId = 0
代码中的position入参可以用于调整顶点的位置,它是一个四维变量,在前文中,我图方便,直接传入了填满整个NDC坐标系的四个顶点,由于是展示图片,没有涉及到3D,所以只传入了x轴和y轴的值。
val FULL_RECTANGLE_BUF = floatArrayOf(
-1.0f, -1.0f, // Bottom left.
1.0f, -1.0f, // Bottom right.
-1.0f, 1.0f, // Top left.
1.0f, 1.0f // Top right.
).toFloatBuffer()
GLES30.glVertexAttribPointer(
glPositionId, 2, GLES30.GL_FLOAT, false, 0, FULL_RECTANGLE_BUF
)
此时上屏展示之后的效果就是将纹理中的内容拉伸填满至四角。
OpenGL以三个顶点为单位绘制内容,但是为什么这里能够传入四个顶点能正常显示。这个和绘制API传入的参数GLES30.GL_TRIANGLE_STRIP有关。此处与拉伸的问题无关先不展开。
GLES30.glDrawArrays(GLES30.GL_TRIANGLE_STRIP, 0, 4)
C++音视频学习资料免费获取方法:关注音视频开发T哥,点击「链接」即可免费获取2023年最新C++音视频开发进阶独家免费学习大礼包!
2、问题解决
2.1、解决图片翻转
还记得我们在顶点着色器传入了一个矩阵吗?
// 将矩阵传入着色器uniform变量
val glTexTransformId = GLES30.glGetUniformLocation(programId, "textureTransform")
GLES30.glUniformMatrix4fv(glTexTransformId, 1, false, identityMtx, 0)
而在顶点着色器中将纹理坐标与矩阵相乘,获得实际渲染的位置,由于渲染图片是2D的,因此只取xy轴。
textureCoordinate = (textureTransform * inputTextureCoordinate).xy;
此时我们就可以对这个矩阵做手脚了,一开始使用的矩阵是一个单位矩阵,我们在小学二年级的线性代数中背过矩阵相乘公式,证实这个矩阵与另一个矩阵相乘的结果是不变的。
val identityMtx = floatArrayOf(
1.0f, 0.0f, 0.0f, 0.0f,
0.0f, 1.0f, 0.0f, 0.0f,
0.0f, 0.0f, 1.0f, 0.0f,
0.0f, 0.0f, 0.0f, 1.0f
)
看到这里,其实解决方法很简单了,我们可以将这个矩阵换成一个翻转的矩阵。于是我利用Matrix工具类的函数来做缩放操作,将y轴缩到-1。
Matrix.scaleM(identityMtx, 0, 1f, -1f, 1f)
于是我们就把图片翻到下面去了,我们需要将这个图片挪回原位,像这样:
我们还是使用Matrix工具类来将矩阵调整一下:
Matrix.translateM(identityMtx, 0, 0f, -1f, 0f)
翻转问题搞好了,接下来处理拉伸问题。
3、解决图片拉伸
解决拉伸的方式有很多种,这里就简单讨论两种,注意:下面解决方式只针对本案例,而在实际使用需要考虑更多情况。
3.1、修改顶点
根据肉眼观察,是在竖直方向拉长了,此时我们就将顶点坐标往里缩缩就好啦。
val ratio = (bitmap.width).toFloat() / (bitmap.height).toFloat()
val rectangleBuf = floatArrayOf(
-1F, -1F * ratio,
1F, -1F * ratio,
-1F, 1F * ratio,
1.0F, 1F * ratio
).toFloatBuffer()
GLES30.glVertexAttribPointer(
glPositionId, 2, GLES30.GL_FLOAT, false, 0, rectangleBuf
)
将顶点的y轴往里缩缩就好啦
3.2、修改绘制ViewPort
那如果我不想改顶点,也可以在绘制的时候修改绘制的ViewPort。
val viewPortWidth: Int
val viewPortHeight: Int
if (width > height) {
viewPortWidth = height * bitmap.width / bitmap.height
viewPortHeight = height
} else {
viewPortWidth = width
viewPortHeight = width * bitmap.height / bitmap.width
}
val y = (height - viewPortHeight) / 2
val x = (width - viewPortWidth) / 2
// 设置ViewPort
GLES30.glViewport(x, y, viewPortWidth, viewPortHeight)
// 绘制纹理
GLES30.glDrawArrays(GLES30.GL_TRIANGLE_STRIP, 0, 4)
第一步首先获取绘制ViewPort的大小。由于图片的比例和我们屏幕比例不一样嘛,于是我们就将绘制的比例调整成和图片的比例一样,再算出屏幕最大能够绘制的宽高像素。
第二部就需要调整绘制ViewPort的位置,也就是x,y轴,将绘制区域居中。
如下图所示蓝色框框就是ViewPort位置:
4、总结
到这里,猫咪照片就被摆正了,大家也可以拉一下文章开头的代码跑一下看看,挺好玩的。
这个OpenGL ES小案例就告一段落了,之后就是真正关于Android音视频的内容了。
水平有限,因此写的比较少,推荐大家看看下列参考文章。
参考
矩阵变换:
- juejin.cn/post/684490…
- juejin.cn/post/684490…
纹理翻转问题:
- juejin.cn/post/685457…
着色器
- xcsf.github.io/blog/2020/0…
作者:米奇律师 链接:https://juejin.cn/post/7199213031472676922
本文暂时没有评论,来添加一个吧(●'◡'●)