目标是从给定的一组图片创建许多缩略图,假设所有缩略图都具有相同的尺寸。
https://threejs.org/examples/#misc_ubiquity_test2是展示texture.wrapS
/ texture.wrapT
+ texture.offset
方法的一个很好的示例,但它意味着克隆每个缩略图的纹理,这会影响性能。一个问题是:如何重用单个纹理?
考虑到16384x16384px
限制,另一个需要考虑的方面是:如何准备多个精灵,加载相应的纹理并在瓷砖之间分配(缩略图)?
答案 0 :(得分:2)
假设:
~1.485
(2894x1949px
)128
缩略图imagemagic
已安装./assets/images/thumbnails/
是操作的工作目录./assets/images/sprite-0.jpg
,...,./assets/images/sprite-<n>.jpg
将是实际的精灵 - 水平地图集(单行瓷砖)首先,让我们定义所需的缩略图尺寸。由于three.js
要求每个纹理维度都是2
的幂,因此可以将高度设置为256px
,使宽度等于380px
。这意味着每个精灵43
个图块(43*380=16340
,其中16384
是总宽度的限制。)
清理./assets/images/thumbnails/original-selected/
并复制43
原始资产的一部分。
执行下面列出的一组步骤。
将结果sprite.jpg
重命名为sprite-<iteration>.jpg
。
生成小资产:
$ mogrify -path ./assets/images/thumbnails/small/ -resize 380x256 ./assets/images/thumbnails/original-selected/*.png
用小资产建立精灵:
$ convert +append ./assets/images/thumbnails/small/*.png ./assets/images/sprite.png
请注意,精灵现在为16340x256
,因此必须将其调整为16384x256
这两个维度都是2
的幂(否则three.js
将会动态执行此操作):
$ convert -resize 16384x256\! ./assets/images/sprite.png ./assets/images/sprite.png
最后,将精灵转换为JPEG,缩小尺寸:
$ convert -quality 85 ./assets/images/sprite.png ./assets/images/sprite.jpg
平铺自身(设置geometry.faceVertexUvs
值)的灵感来自https://solutiondesign.com/blog/-/sdg/webgl-and-three-js-texture-mappi-1/19147
import {Scene, Texture, TextureLoader, Vector2, PlaneGeometry, BufferGeometry, MeshBasicMaterial, Mesh} from 'three';
const thumbnailWidth = 380;
const thumbnailHeight = 256;
const thumbnailsCount = 128;
const spriteLength = 43;
const spriteUrlPattern = 'assets/images/sprite-<index>.jpg';
const scene = new Scene();
const loader = new TextureLoader();
loadAllTextures()
.then(initializeAllThumbnails);
function loadAllTextures(): Promise<Texture[]> {
const spritesCount = Math.ceil(thumbnailsCount / spriteLength);
const singlePromises = [];
for (let i = 0; i < spritesCount; i += 1) {
singlePromises.push(loadSingleTexture(i));
}
return Promise.all(singlePromises);
}
function loadSingleTexture(index: number): Promise<Texture> {
const url = spriteUrlPattern.replace('<index>', String(index));
return new Promise((resolve) => {
loader.load(url, resolve);
});
}
// Tiles are taken from different sprites,
// so thumbnail meshes are built using corresponding textures.
// E.g. given 128 tiles packed into 3 sprites,
// thumbnails 0..43 take the 1st texture, 44..86 - the 2nd one and so on.
function initializeAllThumbnails(allTextures: Texture[]) {
const baseGeometry = new PlaneGeometry(thumbnailWidth, thumbnailHeight);
const materials = allTextures.map((texture) => new MeshBasicMaterial({
map: texture,
}));
for (let thumbnailIndex = 0; thumbnailIndex < thumbnailsCount; thumbnailIndex += 1) {
const geometry = getThumbnailGeometry(thumbnailIndex, baseGeometry);
const materialIndex = Math.floor(thumbnailIndex / spriteLength);
const material = materials[materialIndex]; // could be cloned in here, if each material will need individual transformations, e.g. opacity
const mesh = new Mesh(geometry, material);
scene.add(mesh);
}
}
function getThumbnailGeometry(thumbnailIndex: number, baseGeometry: PlaneGeometry): BufferGeometry {
const tileWidth = 1 / spriteLength;
const tileIndex = thumbnailIndex % spriteLength;
const offset = tileIndex * tileWidth;
// +---+---+---+
// | 3 | . | 2 |
// +---+---/---+
// | . | / | . |
// +---/---+---+
// | 0 | . | 1 |
// +---+---+---+
const tile = [
new Vector2(offset, 0),
new Vector2(offset + tileWidth, 0),
new Vector2(offset + tileWidth, 1),
new Vector2(offset, 1),
];
const plainGeometry = baseGeometry.clone();
const bufferGeometry = new BufferGeometry();
// a face consists of 2 triangles, coords defined counterclockwise
plainGeometry.faceVertexUvs[0] = [
[tile[3], tile[0], tile[2]],
[tile[0], tile[1], tile[2]],
];
bufferGeometry.fromGeometry(plainGeometry);
return bufferGeometry;
}