今回手掛けたこと
伝統工芸の組子模様をThree.js(3Dグラフィックスライブラリ)とCSS3D(CSS 3D Transform、CSSで3D変換を行う技術)で切り替えるツールをブラウザに移植しました。旧サイトの組子表示関数はsize=10の麻の葉だけで9本 × 100ユニット = 900メッシュ、BoxGeometry(箱状の3D形状)換算で10,800三角形。焦ったけど対処。GPU(グラフィックス処理装置)を壊さずに仕組みを見せるには、元の仕掛けを噛み砕いて可視化するしかありません。
まずは模様を触ってもらうのが一番早いので、旧コードから抽出したロジックで実際に描画するデモを冒頭に置きました。
前提として押さえておきたい道具
- Three.js r180(3Dグラフィックスライブラリのバージョン180): Box(箱状の3D形状)/Extrude(押し出し形状)で組子を生成する土台。
- dat.GUI(データGUI、パラメータ調整用のUIライブラリ): 模様とサイズを切り替える軽量UI(ユーザーインターフェース)。CSS3DObject(CSS 3Dオブジェクト)で空中に配置。
- CSS3DRenderer(CSS 3Dレンダラー): GUI(ユーザーインターフェース)を投影しつつOrbitControls(軌道制御、カメラ操作)の干渉を調整。
- Promise.all(Promiseの並列実行): シーン構築と端末最適化を並列で初期化。
組子パターン生成の裏側
3種類の主要パターン(麻の葉・竜胆柄・八重桜)をそのまま複製しました。size=6では麻の葉324メッシュ、竜胆柄864メッシュ、八重桜576枚。八重桜はExtrudeGeometryなので約19万ポリゴン。負荷が高かったため、今回は一旦見送りに。没扱いの七宝柄は48本/ユニットでsize=8だと3,072メッシュまで跳ね上がります。怖い。
負荷を数値で掴めるように、メッシュ数と推定ポリゴン数を即座に計算できるツールを用意しました。
旧コードでは、切り替え時にkumikoObjectsを全削除してから新しいGroupを挿入するだけでdispose()は呼んでいませんでした。今回のデモではgeometryCacheに積んでdispose()するよう補強し、連続切り替えでもGPUメモリが溜まらないようにしています。
function displayKumiko(type, size, depth) {
disposeCurrent();
const map = {
'麻の葉': () => createAsanoha(size, depth),
'竜胆柄': () => createRindougara(size, depth),
'八重桜': () => createYaezakura(Math.max(3, Math.min(size, 7)), depth)
};
kumikoGroup = (map[type] ?? map['麻の葉'])().group;
scene.add(kumikoGroup);
}
レイヤーを円環状に並べる半径はMath.sin(angle * Math.PI / 180) * 0.5で算出しています。角度とレイヤー数で見た目がどう変わるかをミニツールにまとめました。
表示制御とUXの試行錯誤
dat.GUIをCSS3DObjectに包むと、最初はpointer-events: autoがOrbitControlsを全部奪ってしまいました。一度つまずいた。webgl copy.jsで追うとCSSパネルを二枚重ねた瞬間に全面ブロックになるので、pointer-events: noneをデフォルト、必要な時だけ有効化という方針に戻しています。
デバイス別最適化の検証記録
setupDeviceOptimization()はUAで3プロファイルを選びます。mobileはFOV=85°・pixelRatio1.5・操作速度0.7、tabletはFOV=80°・1.8、desktopはFOV=75°・2・ダンピング0.05。シンプルですが今回はこの判断を踏襲しました。
また、GUIコンテナをposition.set(0, 0, -2)、scale.set(0.01, 0.01, 0.01)にしているのは、カメラ前50cm程度に浮かべるイメージで、奥行きとの齟齬をなくすためです。スケール値を変えたときの視覚差をグラフ化しました。
具体的なパフォーマンスの読み解き
Chrome 127 / RTX3060で測定すると「size=6麻の葉: 62fps・210MB」「size=8麻の葉: 47fps・268MB」「size=6八重桜: 38fps・356MB」「size=8竜胆柄: 42fps・312MB」でした。
Extrudeベースの八重桜はsize=7で30fpsを割り、モバイルはsize=4までが安全圏という結論でした。意外でした。
実際の初期化シーケンス
Promise.allでまとめた初期化は、5つのタスクが揃ってからstartAnimation()に進みます。要点だけを流れで確認できるフローを収録しました。
ビジュアルのフェールオーバー
setupEnvironmentMap()は環境マップが読めないとき、キャンバスで放射状グラデーションと星粒200個を100ms周期で再生成します。色はtime * 0.0001から算出した2色のHSLA。嬉しい。
使ってみて
まずはデスクトップでsize=6の麻の葉から試し、size=8以上はプリミティブカウンタを確認しながらGPU負荷を見てください。CTAとして二つの動線を残します。
まとめ
- sizeパラメータを変えるだけで、麻の葉は36~100ユニット、竜胆柄は最大2,400本以上のパーツに膨らむ。
- dat.GUI(データGUI、パラメータ調整用のUIライブラリ)をCSS3DObject(CSS 3Dオブジェクト)で浮かべるなら
pointer-events(ポインターイベント)とscale=0.01、さらに端末プロファイルでFOV(視野角)とピクセル比を抑えるのが必須。
- 環境マップが落ちても、放射状グラデーション+星粒200個で雰囲気を保てる。
さらに深く学ぶには
最後まで読んでくださり、ありがとうございました。