如何在小程序下Threejs播放序列帧?

1,906 阅读2分钟

起因

简单的功能,但是遇到微信小程序纹理大小限制2048的问题(小米8实测),如果是小图一张纹理能容纳,但是如果是上百帧的就只能在质量和帧率上妥协?为了避免限制,所以有了一下实践three-sprite-player

如何实现

一张纹理如何分块?使用Texture的自带功能

// 因为小程序THREE需要scope,所以该npm包无three依赖
texture.wrapS = 1001; // THREE.ClampToEdgeWrapping;
texture.wrapT = 1001; // three.ClampToEdgeWrapping;
texture.minFilter = 1006; // THREE.LinearFilter
texture.repeat.set(1 / this.col, 1 / this.row);

if (sRGB) texture.encoding = 3001; // THREE.sRGBEncoding 
// 如果颜色不对请检查encoding的设置和图片是否一致

// 一张纹理的tile
const tileHeight = 1 / this.row;
const currentColumn = this.currTileOffset % this.col;
const currentRow = ~~(this.currTileOffset / this.col);

if (texture) {
  // 利用texture的offset做偏移实现
  texture.offset.x = currentColumn / this.col;
  texture.offset.y = 1 - currentRow / this.row - tileHeight;
}

简单理解就是一张纹理内的tile偏移量解析到对应行和列

微信小程序一张纹理不够放,那就使用多张,相同的道理就是在加一层映射帧序号映射到tile,然后再映射到行和列

public animate() {
  if (!this.playing || this.totalFrame === 1) return

  const now = Date.now();
  this.startTime = this.startTime ?? now;
  this.startFrame = this.startFrame ?? this.currFrame;
  const nextFrame = this.startFrame + ~~((now - this.startTime) / this.frameGap)
  this.currFrame = nextFrame % this.totalFrame

  if (nextFrame > this.currFrame) {
    this.startTime = now
    this.startFrame = this.currFrame
  }

  this.updateOffset()
}

偏移都比较简单,那么这样的帧序列图片如何生成呢?

帧序列图片合成

这里使用Jimp来做图片编辑工具

const main = async (args: Args) => {
  const t = Date.now()
  // 解析命令行参数
  const { dir, tileW, imgs, cropW, cropH, cropX, cropY, imgW, imgH } = await parseArgs(args);
  // 计算出每个tile可以容纳的行列
  const col = Math.floor(tileW / cropW);
  const row = Math.floor(tileW / cropH);
  const tileNum = Math.ceil(imgs.length / (col * row));

  // 并发
  await Promise.all(Array(tileNum).fill(0).map(async (v, t) => {
    const tileImg = await Jimp.create(
      col * cropW,
      row * cropH,
      Jimp.rgbaToInt(0, 0, 0, 0),
    );
    let sum = 0;
    const drawSprites = []
    for (let r = 0; r < row; r++) {
      for (let c = 0; c < col; c++) {
        const index = t * row * col + r * col + c;
        if (index < imgs.length) {
          drawSprites.push((async () => {
            const img = await Jimp.create(dir + path.sep + imgs[index]);
            img.resize(imgW, imgH);
            img.crop(cropX, cropY, cropW, cropH);
            tileImg.composite(img, c * cropW, r * cropH);
            sum++;
          })())
        }
      }
    }
    await Promise.all(drawSprites)

    console.log(`tile-${t} contains ${sum} sprites`);
    await tileImg.writeAsync(`output-${t}.png`);
  }))
};

这样就能形成小程序帧序列相对完善的解决方案了