自己紹介
- @amagitakayosi
- Kyoto.js主催
WebGLでVJやってます
こんなん作りました
経緯
この画像エフェクトかっこいいな〜
かっけ〜!
僕のVEDAJSで作れるかな?
- WebGL(GLSL)でアニメーションを描画するライブラリ
- 要素の数だけcanvasを生成する必要があり、非効率
- WebGL contextの数に上限アリ(ソース)
Spector.jsで見てみる
- BabylonJSチームによるWebGLデバッグツール
- どういう順番で描画命令とかテクスチャを見れる
Devtools見た結果
- ページ全体にcanvas一枚
- 画像ごとに描画されてる
- Three.jsの場合、画像毎にシーン作って
viewport設定して描画すれば良さそう
参考: Three.jsのexample
できました
react-vfxの仕組み
登場人物
- VFXPlayer: シーン管理、描画
- VFXProvider: canvas生成、Player初期化
- VFXElement: img等のラッパー
処理の流れ
- VFXProviderがcanvasを生成
- Elementsのマウント時に登録
- メインループ
- 要素の位置を更新
- 画面内の要素を描画
1. VFXProvider
- 画面全体を覆うcanvasを生成
- VFXPlayerを初期化
- Three.js周りを管理するクラス
- ContextにVFXPlayerを渡す
hooks便利〜
const VFXContext = createContext(null);
const VFXProvider = props => {
const [player, setPlayer] = useState(null);
useEffect(() => {
// 中略
const p = new VFXPlayer(canvas)
setPlayer(p);
// 中略
}, []);
return (
<VFXContext.Provider value={player}>
{props.children}
</VFXContext.Provider>
);
};
VFXElements
- マウント時にVFXPlayerに登録
- 要素に応じてTextureを作成
- img, video: そのままThree.jsに渡す
- WebGLはvideo要素から直接テクスチャ作れて最高!
- span, div: 気合で画像に変換(後述)
- img, video: そのままThree.jsに渡す
hooks便利〜
const VFXImg = props => {
const { shader } = props;
const player = useContext(VFXContext);
const ref = useRef(null);
// 画像ロード後に呼ぶ
const init = useCallback(() => {
// VFXPlayerに登録
player?.addElement(ref.current, { shader });
return () => {
// VFXPlayerから削除
player?.removeElement(ref.current);
};
}, [shader, player]);
return <img ref={ref} {...props} onLoad={init} />;
};
3.メインループ
- 各要素の要素の位置を毎フレーム取得
- 画面内にあれば、その位置にテクスチャを描画
- まあまあ重いけど動くからヨシ!
IntersectionObserverは?
- 試したけど微妙だった
- コールバックの実行がたまに遅れる(?)
- iOSで触ってるときに遅れてきたりする(?)
- 遅延ロード等に使われるAPIだから仕方ない……
テキストを画像に変換
- 当初はhtml2canvasを使用
- 遅い上に無駄なリクエストが走りまくるので断念
- キャプチャするたびにページ全体をクローンする
- 正確にスタイルを再現するには
ページ全体をクローンする必要があるため
- 今回は、最低限プレーンテキストが画像化できれば良い
- SVGのforeignObjectを使う
- SVGにDOMを埋め込める奴
dom2canvasの流れ
- 画像化したい要素をクローン
- 親要素もクローン (vertical-align等の再現に必要)
- outerHTMLからSVG文字列を作成
- canvasに描画
- THREE.Textureにcanvasを渡す
dom2canvasの困りごと
- 微妙にズレたりズレなかったりする
- html2canvasでもズレてたのでムズそう
- 子要素があるとおかしくなる
- 改行するとおかしくなる
- クロスオリジンなリソースを読み込めない
なんかいい方法あったら教えてください
おまけ: 背景
react-three-fiberを使用
- 宣言的にシーンを記述できる
- React + Three.js系ライブラリの中で
最も筋が良さそう(個人の感想)
<Canvas>
<mesh>
<boxGeometry args={[1, 1, 1]}/>
<meshStandardMaterial/>
</mesh>
</Canvas>
アニメーション用の値
- 普通にnumberをpropsで渡すと重いので🆖
- react-springのuseSpringを使う
- アニメーションで使いたい値のラッパー
// scrollのラッパーを作る
const [{ scroll }, set] = useSpring(() => ({ scroll: 0 }));
const onScroll = useCallback(e => {
set({ scroll: window.scrollY });
}, [set]);
// スクロール位置を 0 ~ 1 に変換
const top = scroll.interpolate(x => {
return x / (document.body.scrollHeight - window.innerHeight);
});
return <>
<Triangle top={top} />
<Particles top={top} />
</>;
REACT-VFX本体にも使う?
- canvasを直接触りたかったのでやめた
- react-three-fiberはcanvasやrenderer周りを
抽象化してくれている - ふつーのWebGLやるのにはメチャ便利
- react-three-fiberはcanvasやrenderer周りを
まとめ
- まだまだ荒削りですがPR歓迎です!!!!
- シェーダー楽しいよ
- みんなもおもしろWebサイト作ろうぜ