计算机系统应用教程网站

网站首页 > 技术文章 正文

【Android音视频】OpenGL ES翻转拉伸问题

btikc 2024-09-02 17:14:13 技术文章 12 ℃ 0 评论

前言

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



Tags:

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表