グラフィカルなデバッグを行う際、数フレームレベルの動作を検証する必要があったりします。
通常の engine.runRenderLoop
だけでは window.requestAnimationFrame が呼ばれ、モニタのリフレッシュレートに最適化された速度で再生されてしまいます。
Unity では Time.timeScale を利用してスローモーションにすることが出来ますが、 babylon.js にそのような API は存在しません。
そこで、 v4 で実装された engine.customAnimationFrameRequester: Nullable<ICustomAnimationFrameRequester>
を上書きすることで、フレームレートを落とすことが出来ます。
interface ICustomAnimationFrameRequester {
renderFunction?: Function;
requestAnimationFrame: Function;
requestID?: number;
}
これは元々は WebXR のために作られた API です。
WebVR では、 window.requestAnimationFrame
の代わりに VRDisplay.requestAnimationFrame を用いる必要があります。これは外部 HMD とメインモニタとでリフレッシュレートが異なる可能性があるため、アクティブになっている VRDisplay がある場合はそちらのリフレッシュレートを優先して処理します。
※今後置き換わる WebXR Spec では、 XRSession.requestAnimationFrame を利用することになる予定です。
今回はこちらを使ってフレームレートを落とします。
const targetFPS = 30;
engine.customAnimationFrameRequester = {
requestAnimationFrame: (func) => {
setTimeout(func, Math.round(1000 / targetFPS));
},
};
単純に、 1000 / targetFPS
ミリ秒遅延して関数を呼ぶようにするだけです。これは今回フレームの処理時間を考慮していないため、実際には 30 FPS 未満になってしまいますが、 window.requestAnimationFrame
が実装されていなかった場合のフォールバックが setTimeout(func, 16)
なのでまあいいとしましょう。
この方法を応用すれば、「ボタンをクリックしたら次のフレームを動かす」という動作も可能です。
let progressFrame = false;
document.getElementById('progress-frame').addEventListener('click', () => {
progressFrame = true;
});
const customRequestAnimationFrame = (func) => {
setTimeout(() => {
if (progressFrame) {
func();
progressFrame = false;
return;
}
customRequestAnimationFrame(func);
}, 1);
};
engine.customAnimationFrameRequester = {
requestAnimationFrame: customRequestAnimationFrame,
};
※残念ながらフレームを巻き戻すことは今の所出来ないようです。