使用OpenGL矩阵变换将纹理从"1D"映射到"2D"

4

(通过这个问题,我试图研究解决另一个问题的想法)

如果我有一个标准的二维数组,其尺寸为宽度高度,存储在内存中,我可以将其转换为长度为宽度*高度的一维数组,然后通过index=x+y*width进行索引。当为数组分配和释放内存时,这种映射非常有用,因为内存管理器不需要担心将结构以2D方式打包,只需要关注以1D表示的每个分配数组的总长度。

我正在尝试查看是否可以将此方法用于OpenGL纹理的图像内存管理。根据上面链接的问题所描述的思路是:通过将许多所需的纹理组合成一个更大的纹理,通过装箱(即将它们绘制在一起)到大纹理中。这有助于在渲染期间最小化昂贵的纹理绑定操作。

假设我的大纹理是8×8像素(即64个像素总数):

8x8 texture:                5x5 image:            4x5 image:

   | 0 1 2 3 4 5 6 7           | 0 1 2 3 4           | 0 1 2 3
---+-----------------       ---+-----------       ---+---------
 0 | . . . . . . . .         0 | A B C D E         0 | a b c d
 1 | . . . . . . . .         1 | F G H I J         1 | e f g h
 2 | . . . . . . . .         2 | K L M N O         2 | i j k l
 3 | . . . . . . . .         3 | P Q R S T         3 | m n o p
 4 | . . . . . . . .         4 | U V W X Y         4 | q r s t
 5 | . . . . . . . .
 6 | . . . . . . . .
 7 | . . . . . . . .

我想在一个8×8的纹理中存储一个5×5的图像和一个4×5的图像(即总共45个像素)。从技术上讲,我有足够的像素可用,但我无法将这些图像放在大纹理旁边,因为这需要至少一个方向上的最小尺寸为9,另一个方向上为5。
如果我可以将我的8×8纹理简单地视为64个连续像素的内存,并将两个图像映射到其中的一维内存块中,我可以按照以下方式在纹理内排列图像: 8x8纹理:
   | 0 1 2 3 4 5 6 7
---+-----------------
 0 | A B C D E F G H
 1 | I J K L M N O P             
 2 | Q R S T U V W X
 3 | Y a b c d e f g             
 4 | h i j k l m n o             
 5 | p q r s t . . .
 6 | . . . . . . . .
 7 | . . . . . . . .

如果我以1:1的比例绘制所有图像,即任何地方都没有分数像素坐标和不需要任何线性过滤或其他像素混合,那么是否可能想出一个转换矩阵,以便使用此纹理绘制4×5图像?
使用顶点和片元着色器,这看起来可能相当容易(除非我忘了什么;我还没有尝试):
- 顶点着色器将要绘制的图像的四个角映射到表示为64×1图像的纹理上: - a:(0, 0) → (0 + 0*4 + 25, 0) = (25, 0),其中25是4×5图像的偏移量 - d:(3, 0) → (3 + 0*4 + 25, 0) = (28, 0) - q:(0, 4) → (0 + 4*4 + 25, 0) = (41, 0) - t:(3, 4) → (3 + 4*4 + 25, 0) = (44, 0) 其他纹理内部坐标的插值应该也应该在整数坐标下沿着这条线映射到正确的偏移量。 - 片元着色器通过简单地取模和除法的商和余数将64×1坐标转换为最终的8×8坐标,例如: - a:(0, 25) → (25 % 8, 25 / 8) = (1, 3) - d:(0, 28) → (28 % 8, 28 / 8) = (4, 3) - k:(0, 35) → (35 % 8, 35 / 8) = (3, 4) - q:(0, 41) → (41 % 8, 41 / 8) = (1, 5) - t:(0, 44) → (44 % 8, 44 / 8) = (4, 5)
不幸的是,自定义着色器需要OpenGL ES v2.0或更高版本,这并非所有设备都支持。
是否可以通过OpenGL ES 1.1提供的矩阵变换仅实现此映射?

1
请注意,不支持至少ES 2.0的活动设备的百分比已经下降到谷歌停止跟踪它(http://developer.android.com/about/dashboards)。我相信当他们最后公布ES 1.1的数字时,它大约是1-2%。实际上,我更担心新设备上ES 1.1实现的工作情况,考虑到它已经过时了。 - Reto Koradi
据我所知,这是不可能的,因为这不是一个线性变换,因此不能用矩阵表示。 - chbaker0
@RetoKoradi 很好的观点。我应该确实检查一下性能影响。通常情况下,我会尽可能支持尽可能多的设备,除非它在更现代的设备上出现问题。不过,如果标准矩阵变换在现代设备上变慢了,我会感到惊讶的。这仍然是做事情的标准方式,不是吗? - Markus A.
@chbaker0 我不太确定你在哪里看到了非线性。你是指我在片段着色器部分加入的模数运算吗?也许这个可以通过打开纹理重复(GL_REPEAT)来实现? - Markus A.
1
索引应该是 index = x + y * 宽度,而不是 index = x + y * 高度。 - samgak
显示剩余5条评论
1个回答

0

我还没有尝试过这个,但我想把它作为一个想法提出:

更新:现在我已经尝试过了,并且只有一个小改动(见评论),它的效果非常好!

假设我的大纹理宽度为 size,我想要绘制的图像宽度为 width,并且从大纹理内部的偏移量offset 开始,其中offset 是偏移的一维表示形式,即x + y * size

然后,以下 4x4 矩阵将几乎实现此映射:

     _                                           _
    |      1        width        offset      0    |
    |                                             |
    |   1/size   width/size   offset/size    0    |
M = |                                             |
    |      0          0            0         0    |
    |                                             |
    |_     0          0            0         1   _|

所以,在上面的例子中,要绘制4×5的图像,矩阵将是:

 _                    _
|   1    4    25    0  |
|  1/8  1/2  25/8   0  |
|   0    0     0    0  |
|_  0    0     0    1 _|

图像坐标将需要用包含4个元素的向量进行指定。
( x, y, 1, 1 )

比如说,k 的坐标(即 (2,2))将会被映射到:

M*( 2, 2, 1, 1 ) => ( 35, 4.375, 0, 1 )

这将被解释为纹理坐标(35,4.375)。

如果我们现在将最近邻作为插值规则并启用x方向的纹理包裹,这应该对应于:

( 3, 4 )

在这里我使用的是整数坐标,而在最终实现中,最终坐标需要是0到1范围内的浮点数。通过将矩阵右下角的1替换为size,可以非常容易地实现这一点,因为它将出现在输出向量的第四个位置,从而分割其他三个位置。正如@chbaker0指出的那样,这只有在纹理坐标受到通常的透视除法影响时才有效。如果不是,则需要将整个矩阵M除以size才能实现所需的结果。

在我开始尝试实现之前,这听起来合理吗?还是有人看到了问题?(可能需要我几天时间,因为我必须先做一些其他事情才能得到可测试的应用程序...)


1
这实际上可以通过将矩阵右下角的1替换为大小来轻松实现,因为这将最终出现在输出向量的第四个位置,从而分割其他三个。我不确定纹理坐标是否要经过透视除法。 规范也不是很清楚:https://www.khronos.org/registry/gles/specs/1.1/es_full_spec_1.1.12.pdf第2.10节。我仔细阅读了它,并从中得出结论,即*不*需要进行透视除法。 - chbaker0
@chbaker0 太棒了!我一直在苦苦寻找一个好的参考手册。这似乎是一个很好的选择!感谢您提供链接!如果纹理坐标不受透视除法的影响,那么只需要将整个矩阵除以“size”,而不是替换右下角的“1”。所以这不应该成为决定因素...但我会在答案中做出注释。 - Markus A.
这是最好的一个,它是由Khronos发布的官方OpenGL ES 1.1规范 :) 我明天会试着在纸上解决一些问题,因为虽然我的直觉告诉我不能用矩阵完成,但你说得有道理。 - chbaker0
1
@chbaker0 :) 那将是太棒了,谢谢!如果这能行,我认为对于动态纹理生成应用的性能来说将会有巨大的益处。 - Markus A.
@chbaker0 刚试了一下:它运行得非常好! :) 只需要一个更改:OpenGL似乎会将0.5添加到纹理坐标的y坐标,因此要在上面的示例中获得k,需要指定(2, 1.5)。或者,可能可以调整矩阵来解决这个问题。 - Markus A.

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接