本セッションの登壇者
セッション動画
「WebGL実践シェーダTips!」ということでお話しします。
今日話すことはこんな感じです。どちらかというとWebGLを使って単発案件をこなすタイプの人向けの内容だと思います。
![](https://res.cloudinary.com/techfeed/image/upload/v1667533336/entries/vrlnz8ay2qfldurxek7f.png)
HMRで爆速イテレーション
まず、HMRで爆速イテレーションという話です。
HMRとは「Hot」「Module」「Replacement」、つまり「リロードなしにモジュールを更新できるアレ」ですね。ReactやVueなどを使ったことがあれば、挙動に覚えがあるはずです。あれをWebGLでも使えないものでしょうか。
![](https://res.cloudinary.com/techfeed/image/upload/v1667533566/entries/ys4wquuvrbbfjdrhpdkk.png)
そのためには、HMR APIというものを使います。Webpackの場合はmodule.hotというAPIが、Viteの場合はimport.meta.hotがあります。
![](https://res.cloudinary.com/techfeed/image/upload/v1667533630/entries/fdxnejlqjj2in4ltrtca.png)
Webpackの場合はこのように書きます。module.hot.acceptを指定すると、このモジュールがホットリロードされます。そして、ホットリロードされたときにどういう挙動をするかを中身に書きます。
![](https://res.cloudinary.com/techfeed/image/upload/v1667533693/entries/pyzkuyjsxntkgirhj7qt.png)
Viteの場合も同じです。
![](https://res.cloudinary.com/techfeed/image/upload/v1667533801/entries/trsazibxoffr09hpvpay.png)
このような感じです。具体的に何をするかというと、Three.jsの例でTHREE.GroupのHot Reloadを見ていきます。
![](https://res.cloudinary.com/techfeed/image/upload/v1667533853/entries/rtbs8bnjwje9e3rds8ex.png)
このように、右側でThree.jsのコードをいじって場所を動かしたりすると、これが全部ホットリロードされて動きます。グループの各部分にHot Reloadを仕込むと、このように更新していくことができます。React周りみたいな感じです。
![](https://res.cloudinary.com/techfeed/image/upload/v1667533914/entries/n1p9bxbinsywk2jzzcby.png)
次に、THREE.ShaderMaterialのHot Reloadです。
![](https://res.cloudinary.com/techfeed/image/upload/v1667533988/entries/ai31ffornnrmexxelesg.png)
ライブコーディング的にシェーダを書いていくことができます。今、GLSLが映っていますけれども、GLSLを変更するとリロードが走らずに、このようにシェーダが入れ替わっていきます。簡単ですね。このようなことができます。
![](https://res.cloudinary.com/techfeed/image/upload/v1667534060/entries/nofi0xsiszlravqoljbh.png)
必要に応じてVRAMを随時監視
次は、webgl-memoryでVRAMの様子を見るという話です。
WegGL開発をしていると、こういうこと(ChromeがOut of Memoryでクラッシュしている状態)がよくあります。気づかないうちにメモリをめちゃくちゃ食っていて、どこかがリークしているのではないかと調査を始めることが多いと思います。
![](https://res.cloudinary.com/techfeed/image/upload/v1667534184/entries/sqtvle4cms11atummjfk.png)
そのようなときのために、greggman/webgl-memoryというライブラリがあります。
![](https://res.cloudinary.com/techfeed/image/upload/v1667534276/entries/lss2xlgv9v3qsrvj5bpa.png)
使い方はとても簡単で、npm install webgl-memoryを実行して、あとはwebgl-memoryをインポートした上で、GMAN_webgl_memoryという新しい拡張を取ってきて、下にあるgetMemoryInfoという関数を叩くだけです。
![](https://res.cloudinary.com/techfeed/image/upload/v1667534333/entries/pjgcqvhzpjnlg2mwfhr7.png)
そうすると、infoの中に、WebGLがトータルで持っているVRAMのサイズやバッファの数、総サイズ、テクスチャの数、総サイズなどが入って、いろいろな情報が取れます。これを見ながら、どこがメモリリークしているかなどを判断していくことができます。
![](https://res.cloudinary.com/techfeed/image/upload/v1667534394/entries/y9pbpmwwn4cz96gbs7ha.png)
ぼくはTweakpaneに仕込んでいつでも見られるようにしています。
![](https://res.cloudinary.com/techfeed/image/upload/v1667534464/entries/wajgpfasoji3yyhyrlxs.png)
重いシェーダにおける初回描画時のStallとの闘い - 空撃ちレンダリングのススメ
次は重いシェーダにおける初回描画時のStallとの闘いについてです。
重いシェーダ、たとえばレイマーチングを描画すると、一番最初のフレームがStallしたりします。
![](https://res.cloudinary.com/techfeed/image/upload/v1667534576/entries/sg9a2r6w4yetbdmh2cvp.png)
具体的には、実際の例を見てもらったほうが速いのですが、WebGLを使っていると、新しいシェーダを描画しようとするとカクッと止まる現象が発生した経験がある人も多いのではないでしょうか。
![](https://res.cloudinary.com/techfeed/image/upload/v1667534669/entries/ujzcwsan7on5x00jlg2q.png)
このように、数フレームしか出てこなかったかわいそうなエフェクトもあるでしょう。
![](https://res.cloudinary.com/techfeed/image/upload/v1667534719/entries/bfv9vmhr1pf37vlw7jsc.png)
Nsight Graphicsを立ち上げてプロファイリングを始めます。見てみると、Drawコールの前に大きなD3DCompileがあって、CPU msが1200、ここで1.2秒使っているのがわかります。こんなところにシェーダのコンパイルを差し込んだ覚えはありません。
![](https://res.cloudinary.com/techfeed/image/upload/v1667534803/entries/icmf2zfcnguh2vv3dwpc.png)
そこで、ANGLEのソースを読んでみます。ANGLEというのは、WebGLのAPIコールをD3Dなどに翻訳してくれるものです。
![](https://res.cloudinary.com/techfeed/image/upload/v1667534867/entries/wetjk3m4w6sjk6113t6h.png)
ANGLEのソースを見ていきます。
![](https://res.cloudinary.com/techfeed/image/upload/v1667535022/entries/bqnsftiga7tnwwibx0hm.png)
読みにいくと、HLSLConpiler::compileToBinary()にD3DCompileが潜んでいます。
![](https://res.cloudinary.com/techfeed/image/upload/v1667535110/entries/lgzilevgbamseoqgnbtc.png)
スタックトレースを追ってみると、一番下にdrawArraysがあります。これですね。
![](https://res.cloudinary.com/techfeed/image/upload/v1667535161/entries/jbv3yq8shjmgpvwjzktq.png)
実は、シェーダのコンパイルに必要な情報が描画時まで確定できないらしいです。なので、あらかじめ1回描画する必要があります。でも、どうやって描画すればよいのでしょうか。
![](https://res.cloudinary.com/techfeed/image/upload/v1667535315/entries/mpazoc6wj6v6mypx39bn.png)
スタックトレースにsyncProgramという関数があって、そのコメントに書いてあります。Shaderがinvalidateされる理由になり得るということです。
![](https://res.cloudinary.com/techfeed/image/upload/v1667535416/entries/ewszyodvatshjzbdeqok.png)
この中で実践上気をつけたいのは、Vertex Bufferの構成やRenderTargetの構成、Rasterizer Discardの使用、Transform Feedbackの使用、Point Spriteの使用などを揃えた上で、小さいバッファを対象にしてでもよいので、一回空撃ちレンダリングをしましょう。
![](https://res.cloudinary.com/techfeed/image/upload/v1667535484/entries/q7ftsnicoyrqke29url7.png)
そうすると、スムーズに動くようになります。先ほどのようなカクつきがなくなりました。見えなかったダイスも見えるようになりました。
![](https://res.cloudinary.com/techfeed/image/upload/v1667535549/entries/vgsrjxhwgbyr8qbztdsd.png)
おまけ - Chunkの中身を編集してリアルタイムで反映
おまけで、three-phong-shader-inspectorのご紹介です。
時間がないので手短にはなりますが、Three.jsのShaderChunkやmaterial.onBeforeCompileといったワードにピンと来る方がいらっしゃれば、こちらの動画を見ていただきたいです。
![](https://res.cloudinary.com/techfeed/image/upload/v1667535740/entries/szcpazpn0oxdws37opqs.png)
Three.jsのマテリアルを選んだり、カラーピッカーで色を変えたり、マップでテクスチャを変えたりできるツールを作りました。右側はShaderChunkで、上にvertexシェーダ、下にfragmentシェーダが表示されていて、Chunkのそれぞれの中身を展開できます。さらに、エディタになっていて、編集してCtrl-R、Ctrl-Sでこのようにライブリロードでシェーダを入れ替えることができます。
![](https://res.cloudinary.com/techfeed/image/upload/v1667535804/entries/bzgpps8g5brmkhpxp7gn.png)
three-phong-shader-inspectorツールのご紹介でした。glitch.meで動いているのでよかったら使ってみてください。
申し遅れましたが0b5vr(おぶざーばー)という者です。こういう映像を64KBのHTMLで出すことに情熱を注いでいます。
![](https://res.cloudinary.com/techfeed/image/upload/v1667535908/entries/mpk7i2quuef8cfakxlgf.png)
ご清聴ありがとうございました。