3D / WebGL
src/lib/webgl/ に WebGL のヘルパーと Three.js の薄いラッパが入っている。
書き出し時の context lost からの復旧と、GPU 完了待ちが組み込まれているのが本質的な価値。
レンダラとの相性
書き出しは headless Chromium でフレームをスクラブするが、WebGL の描画は GPU 処理が 非同期なので、ナイーブに撮ると未完了のフレームが写る。FrameScript の WebGL ヘルパーは ここを面倒見てくれる。
useWebGLContext— WebGL 初期化とwebglcontextlost復旧。useWebGLFrameWaiter— レンダー時に GPU 完了を待つ。
素の WebGL を使う
import { useRef } from "react"
import { useWebGLContext, useWebGLFrameWaiter } from "../src/lib/webgl"
const Canvas = () => {
const canvasRef = useRef<HTMLCanvasElement | null>(null)
const { glRef } = useWebGLContext(canvasRef, ({ gl }) => {
// シェーダ・バッファ初期化
return () => {
// リソース解放
}
})
useWebGLFrameWaiter(glRef)
return <canvas ref={canvasRef} />
}
Three.js を使う — <ThreeCanvas />
Three.js のシーン構築・更新を setup コールバックで宣言するラッパ。
setup が返す { scene, camera, update, dispose } がそのままパイプラインに乗る。
import { useAnimation, useVariable } from "../src/lib/animation"
import { BEZIER_SMOOTH } from "../src/lib/animation/functions"
import { seconds } from "../src/lib/frame"
import { ThreeCanvas, THREE, disposeThreeObject } from "../src/lib/webgl/three"
const Scene = () => {
const progress = useVariable(0)
useAnimation(async (ctx) => {
await ctx.move(progress).to(1, seconds(2), BEZIER_SMOOTH)
await ctx.move(progress).to(0, seconds(2), BEZIER_SMOOTH)
}, [])
return (
<ThreeCanvas
setup={({ renderer, size }) => {
renderer.outputColorSpace = THREE.SRGBColorSpace
const scene = new THREE.Scene()
const camera = new THREE.PerspectiveCamera(
45,
size.cssWidth / size.cssHeight,
0.1,
100,
)
camera.position.z = 6
const mesh = new THREE.Mesh(
new THREE.BoxGeometry(),
new THREE.MeshStandardMaterial({ color: 0x44aa88 }),
)
scene.add(mesh)
const light = new THREE.DirectionalLight(0xffffff, 1)
light.position.set(2, 3, 4)
scene.add(light)
return {
scene,
camera,
update: ({ frame }) => {
const t = progress.get(frame)
mesh.position.x = (t - 0.5) * 3
mesh.rotation.y = t * Math.PI * 2
},
dispose: () => disposeThreeObject(mesh),
}
}}
/>
)
}
ポイント
update({ frame })はフレーム駆動。requestAnimationFrameで 回すのではなく、現在フレームに合わせて毎回呼ばれる。- useAnimation で動かした変数は
progress.get(frame)で参照する (setup内ではフックが使えないため)。 disposeはマテリアル/ジオメトリのリーク対策。disposeThreeObjectを素直に呼ぶのが楽。
CSS と Three.js の使い分け
動画全体の演出は CSS + DOM の方が圧倒的に書きやすい。Three.js は3D が要る部分だけ
局所的に <ThreeCanvas> を貼るのが正解。
全画面 3D の動画を作るより、UI 動画の中に 3D の要素を 1 つ差すユースケースが多いはず。