threejs 貼圖動畫總結

  • 2021 年 12 月 17 日
  • 筆記

引言

在三維可視化中,會涉及到很多動畫,其中貼圖動畫是其中很重要的一種,本文介紹幾種貼圖動畫的思路,供大家一起探討。

流動動畫

流動動畫通過設置貼圖的repeat屬性,並不斷改變貼圖對象的offset讓貼圖產生流動效果。 這種動畫不難實現,首先載入貼圖,如下所示:

let img = new Image();
img.src = './images/path.png';
let texture = new eg.Texture(img);
img.onload = function () {
    texture.needsUpdate = true;
}
texture.repeat.set(100,1);
tube.material.map= texture;

function render(){
 tube.material.map.wrapS = eg.RepeatWrapping;
 tube.material.map.offset.set(offset,0);
 tube.material.map.needsUpdate = true;
 offset += 0.01;
}

上面程式碼,實現了一個tube(管道),然後給管道加了一個貼圖texture。 在渲染的時候,不斷更新texture對象的offset的值,此時就可以生產流動的動畫。如下圖所示:

flow.gif

雪碧圖動畫(Sprite Sheet)

圖集也就是常說的雪碧圖,就是把一系列小圖按照一定的布局放到一張大圖上面。 在使用的時候,截取大圖的一部分來獲取某個小圖。 這在web端是一種常用的手段,通常用於減少圖片數量,從而降低網路請求數量。

通過雪碧圖的方式,可以把動畫的系列動作的每一幀都布局在雪碧圖上。 然後通過雪碧圖創建texture對象,設置貼圖的repeat和offset,讓每次繪製獲取雪碧圖上的某一幀影像,不斷改變offset,就可以形成繪製不同幀的動畫效果。比如下面的圖片:

image.png

image.png

下面這個threejs的demo,就是這樣的效果,所以此處不再贅述程式碼,有興趣的讀者可以查看demo的源程式碼。

//stemkoski.github.io/Three.js/Texture-Animation.html 效果如下圖所示:

flow2.gif

GIF動畫

gif圖片本身自帶動畫,如果gif放到Image對象上,動畫會自動播放,只是當把gif作為貼圖對象的圖片的時候。 不會自動播放動畫。
要自動播放gif動畫,需要使用解析gif的庫,把gif圖片的每一幀解析出來, 並把每一幀影像繪製到一個canvas上,把canvas作為貼圖對象的圖片。大致程式碼如下:

載入gif圖片,並解析圖片。其中解析圖片用到了一個庫omggif,利用裡面的GifReader可以解析gif圖片的幀數據:

import { GifReader } from 'omggif';
  const loader = new FileLoader(this.manager);
    loader.setPath(this.path);
    loader.setResponseType('arraybuffer');

    loader.load(url, (response) => {
      const gifData = new Uint8Array(response);
      const reader = new GifReader(gifData);
      if (onLoad) onLoad(reader);
    }, onProgress, onError);

然後不斷的更新貼圖的影像:

 draw() {
      if (!this.reader) {
        return;
      }
      
      const { reader, image, context } = this;
      const { width, height } = image;
  
      const frameNum = ++this.frameNumber % reader.numFrames();
      const frameInfo = reader.frameInfo(frameNum);
  
      if (frameNum === 0) {
        // always clear canvas to start
        context.clearRect(0, 0, width, height);
      } else if (this.previousFrameInfo && this.previousFrameInfo.disposal === 2) {
        // disposal was "restore to background" which is essentially "restore to transparent"
        context.clearRect(this.previousFrameInfo.x,
                          this.previousFrameInfo.y,
                          this.previousFrameInfo.width,
                          this.previousFrameInfo.height);
      }
  
      const imageData = context.getImageData(0, 0, width, height);
      reader.decodeAndBlitFrameRGBA(frameNum, imageData.data);
      context.putImageData(imageData, 0, 0);
  
      this.needsUpdate = true;
  
      this.previousFrameInfo = frameInfo;
      this.timeoutId = setTimeout(this.draw.bind(this), (frameInfo.delay || 2) * 10);
    }

最終的gif貼圖效果如下圖所示

flow3.gif

APNG動畫

APNG圖片和gif圖片是類似的,也是動畫圖片。 不過相對於gif來說。APNG可以設置半透明,邊緣鋸齒不嚴重,所以使用APNG的圖片的效果要優於gif圖片。

原理上類似,也是解析APNG圖片,然後把沒一幀一次繪製到canvas上,並不斷更新texture對象。 解析APNG圖片,使用了一個開源庫,APNG-canvas。 有興趣讀者可以自行研究,此處不重點講述。

解析完成後,可以把解析的幀集合進行繪製,程式碼如下:

draw() {
      if (!this.reader) {
        return;
      }
      
      const { reader, image, context } = this;
      const { width, height } = image;
  
      const frameNum = ++this.frameNumber % reader.numFrames;
      const frameInfo = reader.frames[frameNum];
  
      if (frameNum === 0) {
        // always clear canvas to start
        context.clearRect(0, 0, width, height);
      // } else if (this.previousFrameInfo && this.previousFrameInfo.disposal === 2) {
      } else if (this.previousFrameInfo) {
        // disposal was "restore to background" which is essentially "restore to transparent"
        context.clearRect(this.previousFrameInfo.left,
                          this.previousFrameInfo.top,
                          this.previousFrameInfo.width,
                          this.previousFrameInfo.height);
      }
  
      const imageData = context.getImageData(0, 0, width, height);
      // reader.decodeAndBlitFrameRGBA(frameNum, imageData.data);
      // context.putImageData(imageData, 0, 0);
      context.drawImage(frameInfo.img,frameInfo.left,frameInfo.top,frameInfo.width,frameInfo.height);
  
      this.needsUpdate = true;
  
      this.previousFrameInfo = frameInfo;
      this.timeoutId = setTimeout(this.draw.bind(this), frameInfo.delay);

最終的apng貼圖效果如下圖所示

1.gif

總結

本文介紹了 theejs 貼圖動畫的多種實現思路。 包括 紋理流動,雪碧圖,gif和apng動畫。 通過這些動畫能力,可以創建出豐富多彩的可視化效果。

如果對可視化感興趣,可以和我交流,微信541002349(可入微信群)。另尋求有三維建模能力,UI設計能力的人員。

關注公號「ITMan彪叔」 可以及時收到更多有價值的文章。