0%

OpenGL如何正确渲染图片

我们使用OpenGL渲染图片或视频时,经常遇到图片或视频被拉伸而变形的情况。这种问题在线上产品中是必须要杜绝的,这篇文章我们就来看看该如何解决图片或视频被拉伸的情况。

如上图所示,图中左侧是图片的原始尺寸,其高度和宽度都没有将屏幕覆盖全,而右侧是图片被拉伸的情况。

如何才能防止图片被拉伸呢?这里我介绍两种方法,首先我们来看第一种方法。

防止图片被拉伸-方法一

第一种防图片被拉伸的方法是改变视口大小,即根据图片和窗口的宽高比计算视口的大小,从而让图片宽度占满屏幕上下留白或高度占满屏幕左右留白。

具体算法如下:

  • 分别计算图片和窗口的宽高比
  • 如果图片的宽高比大于窗口的宽高比,说明图片更宽
  • 此时,让图片的宽度占满窗口的宽度,上下留白
  • 否则,图片的宽高比小于窗口的宽高比,说明图片再高
  • 此时,让图片的高度点满窗口高度,左右留白

当确定好是图片的宽度占满窗口或图片的高度占满窗口后,接下来我们再计算图片的高度或宽度,具体计法方法如下:

  • 如果图片的宽度占满窗口宽度,则图片的高度为:窗口的宽度 / 图片的宽高比,即 $W_W / (W_I/H_I) = (W_W/W_I)*H_I$

  • 如果图片的高度占满窗口高度,则图片的宽度为:窗口的高度 * 图片的宽高比,即 $H_W * (W_I/H_I) = (H_W/
    H_I) * W_I$

此外,我们还要计算图片的起始点位置,如下图所示:

图片最初的起始点为窗口左上角,但当我们修改视口后,其起始点也发生了变化,那么这个启始点该如何计算呢?计算公式如下:

$$
X = |W_W - W_I| / 2 \
Y = |H_W - H_I| / 2
$$

有了起始点和图片的宽高,我们就可以使用OpenGL为我们提供的函数glViewport来设置视口的位置了,即

1
glViewport(X, Y, vWidth, vHeight)

由于该视频的宽高比与图片的宽度比一致,因此我们渲染图片时,让图片将视口填满即可达到最佳的展示效果。

防止图片被拉伸-方法二

当然,除了上面介绍了修改视口来防止图片被拉伸的方法外,还有一种方法可以实现图片被拉伸,这种方法被称为投影变换

如我们在第9章中介绍的,投影变换分为正交投影和透视投影,对于图片的渲染来说,使用的是正交投影。

接下来我们就来看一下如何使用正交投影实现图片的渲染。

咱们先来回顾一下正交投影的过程,如下图所示:

对于正交投影来说,首先要进行平移,将模型的中心点移到坐标的中心点处,之后对模型进行缩放,使之缩小到一个单位空间内,最后将3D模型投影到2D屏幕上。

对于图片渲染来说,当我们按照上述方法操作时就会得到一个宽度与窗口一致上下留白或高度与窗口一致左右留白的图像。

那么具体该如何做呢?其实非常简单,我们只需要让图片乘以一个正交变换矩阵就可以了。而正交变换矩阵在前面的课程中我们也推导过,如下所示:
$$
M_{ortho} =
\begin{bmatrix}
\frac{2}{r-l} & 0 & 0 & 0\
0 & \frac{2}{t-b} & 0 & 0\
0 & 0 & \frac{2}{n-f} & 0\
0 & 0 & 0 & 1
\end{bmatrix}
\cdot
\begin{bmatrix}
1 & 0 & 0 & -\frac{r + l}{2} \
0 & 1 & 0 & -\frac{t + b}{2} \
0 & 0 & 1 & -\frac{n + f}{2} \
0 & 0 & 0 & 1
\end{bmatrix}
$$

这个矩阵还是蛮复杂的,想记住它可不容易,不过好在Android已经为我们提供了现成的方法,只要我们调用下面这个方法就可以得到这个矩阵:

1
Matrix.othroM(mvpM, offset, l, r, b, t, n, f)

其中,mvpM是输出矩阵;offset是偏移量,一般设置为0;l代表模型在单位立方体中的左边界;r代表模型在单位立方体中右边界;b代表模型在单位立方体中底部边界;t代表模型在单位立方体中顶部边界;n代表模型在单位立方体中的近边界;f代表模型在单位立方体中的远边界。

由于图片是二维的,所以nf不会改变,我们将它们设置为-1和1即可。对于宽高来说,则需要根据实际情况来设定。如果上下留白,则左右设置为-1, 1;如果左右留白,则上下设置为-1,1。

那么接下来,我们就来看看如何计算矩阵的宽高。具体算法如下:

当图片的宽高比 大于 窗口的宽高比时,模型的高度为:图片的宽高比除以窗口的宽高比,即$tb = (W_I/H_I)/(W_W/H_W)$。此时我们需要将Matrix.othroM方法中的bt分别设置为 -tbtb

否则,当图片的宽高比小于等于 窗口的宽高比时,模型的宽度为:窗口的宽高比除以图片的宽高比,即$lr = (W_W/H_W)/(W_I/H_I)$。此时我们需要将Matrix.othroM方法中的lr分别设置为 -lrlr

这样我们就得到了正确的正交变换矩阵,之后用这个正交变换矩阵就可以将图片正确的渲染到屏幕上。

小结

上面我向你介绍了两种在渲染图片时防止被拉伸的方法,其中第一种是通过修改视口的方法实现的,而第二种是通过得到正交变换矩阵的方法实现的。

通过修改视口的方法相对来说更容易理解一些,但更常见的方法是通过求正交变换矩阵来得到正确的渲染结果,这种方法适用性更强,也更灵活。

参考资料

系统玩转OpenGL+AI,实现各种酷炫视频特效

欢迎关注我的其它发布渠道