🥉

Three.js の WebGPU 対応と TSL について

2024/05/24に公開

Three.js の WebGPU 対応について調べていく中で、ここ数年の Three.js にまつわる動向がいろいろと興味深かったので、その周辺も含めて簡単にまとめてみました。

そもそも WebGPU とは?

https://developer.mozilla.org/ja/docs/Web/API/WebGPU_API

WebGPU は、WebGL の後継技術として登場した次世代グラフィックス API です。より低レベルで汎用性のある GPU アクセスを可能にし、ブラウザ上での高度な 3D グラフィックスやコンピューティングの実現を目指しています。

WebGPU が生まれた背景には、近年のグラフィックス技術の進化が関係しています。従来の WebGL は OpenGL ES を基盤としており、Web の 3D グラフィックス表現を中心に広く使用されてきましたが、2010 年代中頃から OpenGL のレガシーな API 設計に起因するさまざまな問題点[1]を克服するため、Vulkan や Metal、Direct3D 12 などの代替技術が台頭してきました。WebGPU もこれらの技術がベースになっています。

WGSL (WebGPU Shading Language)

https://gpuweb.github.io/gpuweb/wgsl/

WGSL は WebGPU 用のシェーディング言語で、GLSL に代わるものです。GLSL は C 言語風ですが、WGSL では Rust 風の構文が採用されています[2]

なぜ WGSL という新しいシェーディング言語を採用することになったのかについては疑問が浮かぶところですが、W3C の GPU for the Web Working Group によるミーティングの議事録にその経緯が残されています[3]

各ブラウザの対応状況

https://caniuse.com/webgpu

2024 年 5 月現在、デフォルトで対応しているのは Chrome(バージョン 113 以降)だけです。Safari はTechnology Preview 版でのみ、Firefox は dom.webgpu.enabled フラグを有効にすることで使用可能になります。

Three.js の WebGPU 対応

Three.js は 2020 年ごろから WebGPU 対応に取り組んできました。

https://github.com/mrdoob/three.js/pull/20254

WebGPU を使用するだけであれば比較的簡単で、WebGPURenderer をインポートして初期化するだけです。

import WebGPURenderer from 'three/addons/renderers/webgpu/WebGPURenderer.js';

const renderer = new WebGPURenderer({ canvas });

Three.js のビルトインマテリアル(MeshStandardMaterialMeshPhongMaterial など)も基本的にはそのまま使えるようです[4]

ただし、これらのマテリアルを独自にカスタムしようとすると問題が浮上します。先述のとおり、WebGPU は GLSL をサポートしていないので、従来行っていたように、既存のマテリアルに対して onBeforeCompile() でカスタムの GLSL シェーダを注入したり、ShaderMaterialRawShaderMaterial を用いて独自にカスタムマテリアルを実装したりすることはできません。

NodeMaterial と TSL

NodeMaterial の登場

https://github.com/mrdoob/three.js/issues/7522

現行の Three.js のマテリアルシステムは、ShaderChunk と呼ばれる GLSL の小さなパーツ群(チャンク)の組み合わせで構成されていますが、チャンクが 1 つでも欠落したり、間違った順序で使用すると容易に壊れてしまう、という問題点がありました[5]

その一方で、2015 年頃からノードベースの新しいマテリアルシステムである NodeMaterial が開発されてきました。これは Blender や Unity、UE のようなゲームエンジンが採用するノードベースの開発環境を踏襲しつつ、先述の ShaderChunk の問題点を解消するものとして、主に Three.js のコントリビューターである sunag 氏によって開発が進められています。


同じく sunag 氏による、NodeMaterial を視覚的に編集するためのビジュアルノードエディタ[6]

TSL (Three.js Shading Language)

https://github.com/mrdoob/three.js/pull/17105
https://github.com/mrdoob/three.js/pull/17321

NodeMaterial の開発が進む中で、NodeMaterial にカスタムシェーダーを追加するための TSL (Three.js Shading Language もしくは Three.js Shader Language) と呼ばれるものが実装されました。いわば AltGLSL にあたるようなものですが、Three.js の WebGPU 対応に伴って、後に WGSL の出力にも対応しています[7]

以下は、ブラウザ上で動作するエディタの example です。GLSL と WGSL のどちらも出力できます。

https://threejs.org/examples/?q=tsl#webgpu_tsl_editor

また、逆に GLSL から TSL へのトランスパイラも提供されています。

https://threejs.org/examples/?q=tsl#webgpu_tsl_transpiler

TSL のサンプルコード

以下は TSL を使用したサンプルコードと動作デモです。Shading Language と銘打ってはいますが、実際には three/nodes 以下のモジュール群を駆使して JavaScript で記述します。

基本的には、NodeMaterial の positionNode に頂点の座標情報を、colorNode に色情報を入力してあげればよさそうです。

import { MeshBasicNodeMaterial, modelWorldMatrix, positionLocal, sin, timerLocal, uv, vec2, vec3, vec4 } from 'three/nodes';

const material = new MeshBasicNodeMaterial();
const frequency = vec2(10, 5);
const time = timerLocal(1);

// vertex
const modelPosition = modelWorldMatrix.mul(vec4(positionLocal, 1));
const elevation = sin(modelPosition.x.mul(frequency.x).sub(time))
  .mul(0.1)
  .add(sin(modelPosition.z.mul(frequency.y).sub(time)).mul(0.1));
material.positionNode = positionLocal.add(vec3(0, 0, elevation));

// fragment
const color1 = vec3(uv(), 1.0);
const color2 = vec3(1.0, uv());
material.colorNode = mix(color1, color2, sin(time).mul(0.5).add(0.5));

ソースコードを一見した感じでは、GLSL のビルトイン関数に類するもの(mixsmoothstep など)はすでに概ね提供されているようです。四則演算に関しては、現状は add sub mul div で代用します(JavaScript には残念ながら演算子オーバーロードの機能がありません)。

NodeMaterial と TSL に関しては現状、ドキュメントが極端に不足しているため、使い方を理解するためには公式の example や大元のソースコードを参考にする必要があります。

なお、上記のサンプルは r164 で実装していますが、WebGPURenderer および NodeMaterial、TSL はまだ開発途上にあり、今後も API が大きく変更される可能性がある点にご留意ください。

参考リンク

脚注
  1. Vulkan (API) - Wikipedia ↩︎

  2. [wgsl] Reasoning behind inheriting rust-like syntax · Issue #593 · gpuweb/gpuweb ↩︎

  3. Agenda / Minutes for GPU Web meeting 2019-12-09 - Google Docs ↩︎

  4. 内部的には、それぞれ対応する NodeMaterial に変換されるようです(たとえば MeshStandardMaterial なら MeshStandardNodeMaterial)。 ↩︎

  5. How to utilize webgpu (is nodes the best option?) - Questions - three.js forum ↩︎

  6. three.js - playground ↩︎

  7. NodeMaterial: ShaderNode ↩︎

Discussion