26
13

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

WebGLAdvent Calendar 2019

Day 15

令和元年に登場したテクスチャフォーマットBasisを試す

Last updated at Posted at 2019-12-15

はじめに

昨年のAdvent CalendarはCrunched Textureについて書きました。
しかし、令和元年、Crunchedテクスチャを過去のものにする新テクスチャフォーマットが登場しました。

Crunchedとは何なのか

WebGLのテクスチャフォーマットとしてはPNGやJPEGなどブラウザではおなじみの画像フォーマットが使用されますが、これはGPU上では非圧縮で32bppまたは24bppに展開されるため、効率がよくありません。
一方でDXTCやETCなどGPU用の圧縮テクスチャを使用すれば8bppから4bppまで圧縮できます。
これらDXTCやETCをさらにCPUで圧縮したのがCrunchedです。
これによりダウンロードサイズやストレージサイズを節約できます。
Crunched TextureはUnityにも組み込まれており、PCのみならず、スマートフォンデバイスでも動作します。

Crunchedではだめなのか

Crunchedではなく、そもそも圧縮テクスチャに問題があります。
WebGLはPC/Android/iOSで動作しますが、圧縮テクスチャはそれぞれに対応したフォーマットを使用する必要があります。
PCではDXTC(BC1,BC3)、iOSではPVRTC、AndroidではETCが使われます。
そのためPC用にはPC用のDXTCに展開できるCrunchedテクスチャ、Android用にはETCに展開できるCrunchedテクスチャを作成し、それぞれダウンロードする必要があります。また、CrunchedはPVRTCには対応していません。
そもそも圧縮テクスチャはWebGLにおいては拡張機能としてのサポートであり、たとえAndroidであってもETCで対応している保証はありません。1

ETC1S to BC1

この問題を解決するカギの一つがETC1Sというフォーマットです。
このフォーマットはETC1SはETC1のサブセットで、BC1に変換可能なフォーマットとなっています。
ETC1Sフォーマットを使用すればPCではBC1を使用し、AndroidではETC1を使用するといったことができるようになります。
しかし、ETC1SをBC1に変換できたとしてもiOSではPVRTCが使用されていますし、アルファチャンネルにも対応できていません。

ある日突然登場したBasis Universal

ETC1Sを試そうかなーとずっと考えてたんですけどなかなかまとまった時間が取れない日々が続いていた私の耳に飛び込んできたのがこのBasis Universal Textureフォーマットです。
Basis Universal Textureは中間フォーマットbasisからそれぞれのプラットフォームに対応した圧縮テクスチャに変換するフォーマットで、内部的には先ほど説明したETC1Sが使用されています。
Basis Universal TextureはETC1/ETC2/PVRTC1/BC1-5/BC7/ASTCと多くのテクスチャフォーマットへと変換できます。
さらにbasisはCrunched同様、CPUで圧縮がかかっており、これらは0.3~1.25bpp程度の圧縮ができます。
ほかにはミップマップ、複数画像の格納、動画用フレームレートなどREADMEを読み込んでみるとかなり多機能です。

GoogleとKhronosが共同でglTFのテクスチャフォーマット仕様としてコントリビュートしていくと発表されており、ソースコードも公開されました。
dracoと組み合わせて使用すれば、glTFサイズを大きく削減できそうです。

早速試してみる

Basis Universal TextureのソースコードはGitHubに公開されています。
このレポジトリには圧縮/展開ツールのbasisu及び、ランタイムで圧縮テクスチャに含まれているbasisTranscoderが含まれています。
多くの環境ではbasisuはCMakeを使用すればビルドできますが、WindowsではCMakeではうまくビルドできないようですので同梱されているbasisu.slnを使用してbasisu.exeをビルドします。

basisu.exeは変換元データとしてpngファイルを使用します。
basisu image.png コマンドでimage.basisを作成できます。
basisuには様々なオプションを与えることができます。
たとえば-comp_levelで圧縮レベルが設定可能で、basisu image.png -comp_level 5とすれば時間はかかりますが最高品質で圧縮ができます。
また、圧縮テクスチャでは UNPACK_FLIP_Y_WEBGL が機能しないため、上下反転オプションの-y_flipも合わせて指定しておくとよいでしょう。

basisTranscoderはEmscriptenを使用してWebAssemblyにビルドして使用します。CMakeを使用したビルド方法がwebgl/transcoder以下のREADMEで説明されているので、説明に従ってビルドします。
WebAssemblyにビルドされたbasisTranscoderはASTC/BC1/BC3/ETC1/ETC2/PVRTCに対応しています。
また、これら圧縮テクスチャが使用できなかった場合のフォールバック先としてRGBA32とRGB565にも変換できますのでWebAssemblyが動作すればテクスチャが使用できないといったことはありません。
PNGやJPEGといったテクスチャを使っているとRGBA32に変換することが多いので、RGB565のテクスチャを使うのはなかなか珍しいかなと思います。

basisTranscoder.jsを読み込むとBASISがグローバル空間に定義されるので then メソッドでWASMがコンパイルされるのを待ち、 initializeBasis で初期化します。
その後basisFileのメソッドを呼び出すことでbasisから圧縮テクスチャへの変換ができます。

たとえばBC1に変換するコードはこんな感じになります。

BASIS().then(basisTransCoder => {
  const gl = document.querySelector("#canvas").getContext("webgl2");

  // basisTranscoder初期化
  const {initializeBasis} = basisTransCoder;
  initializeBasis();

  // basisファイルを変換
  const BasisFile = basisTransCoder.BasisFile;
  const basisFile = new BasisFile(image);
  const width = basisFile.getImageWidth(0, 0);
  const height = basisFile.getImageHeight(0, 0);

  if (!basisFile.startTranscoding()) {
    console.error("failed to startTranscoding.");
    basisFile.close();
    basisFile.delete();
    return;
  }

  const BC1 = 1;

  const extractSize = basisFile.getImageTranscodedSizeInBytes(0, 0, BC1);
  const textureSource = new Uint8Array(extractSize);

  if (!basisFile.transcodeImage(textureSource, 0, 0, basisFormat, 0, 0)) {
    console.error("failed to transcodeImage.");
  }

  basisFile.close();
  basisFile.delete();

  // 圧縮テクスチャを作成
  const tex = gl.createTexture();
  gl.bindTexture(gl.TEXTURE_2D, tex);
  const COMPRESSED_RGB_S3TC_DXT1_EXT = 0x83F0;
  gl.compressedTexImage2D(
    gl.TEXTURE_2D,
    0,
    COMPRESSED_RGB_S3TC_DXT1_EXT,
    width,
    height,
    0,
    textureSource
  );

  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
});

そして、実際組み込んでみたものが以下となります。

動かしてみた結果

PCとAndroidで圧縮テクスチャに変換できることが確認できました。確認はできていませんがおそらくiOSでもPVRTCに変換して使用できます。

多くの圧縮テクスチャに変換できますが、プラットフォームや品質面、アルファチャンネルの有無などにより、どのテクスチャに変換するかは状況に応じて考える必要がありそうです。

  1. OpenGL ES 3.0は圧縮テクスチャを標準仕様としてサポートしていますが、OpenGL ES 3.0に相当するWebGL 2.0では拡張機能としてのサポートとなっています。

26
13
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
26
13

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?