你如何 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
获得。
Image
s 通过其他方式在运行时创建。
编译尺寸
现在,我们可以得到它们的尺寸了。我们将在 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)
您可能会怀疑前两个参数是 width
和 height
。第三个参数是是否要生成 mipmap。最后是像素格式,我在这里选择的格式有四个通道(红色、绿色、蓝色、Alpha),每个通道 8 位(颜色深度)。
复制输入Image
s到图集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)
那你应该可以在你需要的地方设置它。我把它留给你。
一些背景
我正在开发体素游戏(例如我的世界)。我不能使用预建的纹理图集,因为我需要允许玩家向游戏中添加自定义块。这就是为什么我必须根据提供的纹理在运行时创建纹理图集。这些纹理的大小不必相同(一个可以是 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
获得。 Image
s 通过其他方式在运行时创建。
编译尺寸
现在,我们可以得到它们的尺寸了。我们将在 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)
您可能会怀疑前两个参数是 width
和 height
。第三个参数是是否要生成 mipmap。最后是像素格式,我在这里选择的格式有四个通道(红色、绿色、蓝色、Alpha),每个通道 8 位(颜色深度)。
复制输入Image
s到图集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)
那你应该可以在你需要的地方设置它。我把它留给你。