🤔

[Pixi.JS×pixi3d]Canvasで3dモデルをくり抜いて動画マスクにしたい

2023/11/19に公開

ウェブサイトで動画を旗っぽいゆらゆらした動きでくり抜いて表示するとしたら?

思い付きでやった備忘録的な話。
ウェブサイトで動画を旗っぽいゆらゆらした動きでくり抜いて表示するとしたらどうやって表示できるのだろう?がこの記事を書くきっかけになった。

イメージとしてはこのゆらゆらした旗の部分だけに動画が流れてほしい。

パターン1:svgでマスクをかけてそこだけ表示させる

svgはクリップしたところだけ表示させることができるからその機能を使おうというもの。
例えばこんな感じ。

ただ、旗っぽくゆらゆらさせてsvgをマスクさせるにはsvgを用意するのにも理解するのにも時間がかかりそう。例えば以下のように不規則な動きでもsvgさえ作れれば動画をマスクさせること自体はできそう。

複雑な操作・指示が来た場合には都度svgを修正しないといけないが、修正して対応できるだけのsvgの知識とソフトウェアを知らないので今回はパスした。

パターン2:canvasで3dモデルをくり抜いて動画のマスクにする

本記事のタイトル。
3dソフトウェアの要求操作知識は跳ね上がるけど、複雑なアニメーション等は全て3dソフトウェア上で行えばいいのでコード修正のカロリーは低そう。
ぱっと思いついたのがPixi.JSだったのでPixi.JSで3dモデルを表示させてそこだけcanvasをくり抜けられればいけるのではと思い調べたらpixi3dなるものがあった。
この二つを使えば3dでくり抜けるのではで試したのがこちら。

主要コードは以下である。

    // canvasのアルファ値を0にすることで透明なcanvasを作成している
    app = new PIXI.Application({
      backgroundAlpha: 0,
      resizeTo: window,
      antialias: true,
    });

    const canvas = document.querySelector('.keyvisual__canvas');
    canvas.appendChild(app.view);

    // canvasのマスク画像として使用、meshのブレンドモード変更によってmesh部分だけ透過する
    const graphics = new PIXI.Graphics();
    graphics.beginFill(0xffee88);
    graphics.drawRect(0, 0,canvas.clientWidth,canvas.clientHeight);
    graphics.endFill();
    app.stage.addChild(graphics);

    // pixiに渡すためのメッシュを作っている
    mesh = PIXI3D.Mesh3D.createCube();

    // https://github.com/jnsmalm/pixi3d-sandbox/blob/3a63b47a372f07999e9875a8e5311650365696d8/post-processing-sprite/src/index.js#L44
    // CompositeSpriteクラスはこう使うらしい
    // 3dを使った場合のくりぬきできた!
    const meshCom = new PIXI3D.CompositeSprite(app.renderer, { objectToRender: mesh });
    
    // ブレンドモードを変更することでcanvasをくり抜いてる
    // 参考リンク:https://www.shukantpal.com/blog/pixijs/pixijs-picture-kit/
    meshCom.blendMode = PIXI.BLEND_MODES.DST_OUT;
    app.stage.addChild(meshCom);

canvasそのものを透明な板として配置して、その上に単一色のオブジェクトを配置している。
pixi3dで3dのメッシュを作って、spriteとして変換してからブレンドモードをDST_OUTにすることでその範囲に配置されているオブジェクトが消される。それによってcanvasまで透けて見えてcanvasそのものも透明なので下の要素が見えるというからくり。

今回は配置したのが画像だったけれど、ただ要素として配置しただけなので動画でもできる。
3dモデルのくり抜きは案外サクッとできたので、後はBlenderあたりで旗を作れれば再現できそう。
ウェブで3d表現するのならThree.jsあたりを使うのが王道だと思うのだけどcanvasのくり抜きだけにThree.jsでゴリゴリシェーダーを書くならPixi.JS×Pixi3dのほうがサクッとできそうだし、Pixi.JSの強みである2d表現での画像処理を生かせそうだなと思った。

番外:3dソフトウェア上でくり抜き用画像を連番で用意すればいいのでは?

記事を書きながら思いついたこと。
画像で用意できるならそっちのほうがpixi3dとかサードパーティー製のライブラリに依存しなくて済む・もしかしたら単に3dモデルを読み込むより容量・速度を速くできるかも?

余談:実際に旗を作ってみて直面した罠

この記事を書きながら実際にBlenderで旗を作って読み込ませてみていたのだけどうまくいかなかった。原因としてはpixi3d自体があまりに多いシェイプキーには対応していなかったため。
https://github.com/jnsmalm/pixi3d/issues/176

シェイプキーを使わずに旗作るとなると物理演算をボーンアニメーションに変換する必要があるがあまりに面倒だったので心折れた。3dソフトウェア上での無駄な手間・要求知識等を考えるとやっぱり素直にThree.jsを使ったほうがいいのだろうか...

参考にした記事など

Discussion