你如何 combine/stitch 不同大小的图像到一张大小为 2 的幂的图像?

How do you combine/stitch images of different sizes to one image with a size that is a power of 2?

一些背景

我正在开发体素游戏(例如我的世界)。我不能使用预建的纹理图集,因为我需要允许玩家向游戏中添加自定义块。这就是为什么我必须根据提供的纹理在运行时创建纹理图集。这些纹理的大小不必相同(一个可以是 16 x 16,而另一个可以是 64 x 64)所以我不能只是以固定的距离粘贴图像。

真题

如何将多个不同大小的图像合并为一个,同时尽可能多地塞满图像,即图像应尽可能密集,如果图像可以放在最终的较小位置图片,它应该放在那里,以免使用可能被其他图片使用的 space。 最终图像必须是正方形,其大小必须是 2 的最小幂。例如,如果有 4 张 16 x 16 图像,则只应创建 32 x 32 图像,因为它足以包含这些图像。但是假设有 5 张图像,它应该创建一个 64 x 64 的图像作为可以包含这些图像的最小尺寸。 我还需要可以映射到块面的纹理的 UV 值。

我用的游戏引擎

我用的是godot游戏引擎

输入

假设您有一系列 Image 想要放入您的纹理图集中。假设它是一个数组:

var images:Array

并且该数组的值可以是:

  • 图像文件导入为 Image
  • Texture 秒通过调用 get_data 获得。
  • Images 通过其他方式在运行时创建。

编译尺寸

现在,我们可以得到它们的尺寸了。我们将在 PoolVector2Array 中需要它们(我们实际上可以在常规 Array 中获取它们并转换它,但没有必要这样做):

var sizes:PoolVector2Array
for image in images:
    sizes.append(image.get_size())

计算图集

确定尺寸后,我们可以调用 Geometry.make_atlas:

var atlas_data := Geometry.make_atlas(sizes)

Geometry.make_atlas的算法会尽量使图集正方形。

我们应该查询我们得到的尺寸:

var min_atlas_size:Vector2 = atlas_data.size

遗憾的是 Godot 没有公开“2 的下一次幂”函数。鉴于上下文,我相信一个简单的循环在这种情况下会很好:

var atlas_size := 16
while min_atlas_size.x > atlas_size or min_atlas_size.y > atlas_size:
    atlas_size *= 2

构建图集

创建图集Image

现在我们已经确定了大小,我们可以为图集创建一个新的Image

var atlas := Image.new() 
atlas.create(atlas_size, atlas_size, true, Image.FORMAT_RGBA8)

您可能会怀疑前两个参数是 widthheight。第三个参数是是否要生成 mipmap。最后是像素格式,我在这里选择的格式有四个通道(红色、绿色、蓝色、Alpha),每个通道 8 位(颜色深度)。


复制输入Images到图集Image

现在将您的原始图像复制到图集。我们从 make_atlas:

得到的结果中得到每个位置
var points:PoolVector2Array = atlas_data.points
for index in images.size():
    var image:Image = images[index]
    var point:Vector2 = points[index]
    var size:Vector2 = image.get_size()
    atlas.blit_rect(image, Rect2(Vector2.ZERO, size), point)

这里的Rect2就是我们要绘制的Image的区域(全部)。

注意根据你要绘制的Image的格式,可能要先转换一下:

source.convert(Image.FORMAT_RGBA8)

另外,通常导入的图片都是压缩格式,无法直接转换,需要先解压:

source.decompress()
source.convert(Image.FORMAT_RGBA8)

如果 blit_rect 行给您带来问题,请尝试。


UV坐标

顺便说一句,你说你想要 UV 坐标,我们应该从同一个循环中获取它们:

var points:PoolVector2Array = atlas_data.points
var uv_scale := 1.0/atlas_size
var uv_rects := []
for index in images.size():
    var image:Image = images[index]
    var point:Vector2 = points[index]
    var size:Vector2 = image.get_size()
    atlas.blit_rect(image, Rect2(Vector2.ZERO, size), point)
    uv_rects.append(Rect2(point * uv_scale, size * uv_scale))

正在转换为 Texture

并且由于您需要 UV 坐标,我认为这将是某些着色器的 Texture。所以,让我们做一个 Texture:

var texture_atlas := ImageTexture.new()
texture_atlas.create_from_image(atlas)

那你应该可以在你需要的地方设置它。我把它留给你。