Hello. I wanted to post a question regarding Babylon.js scene swapping that, based on my exploration of these forums, I have not seen mentioned before. Recently, I have been writing some code that allows for the swapping of scenes to occur within the Babylon coding environment. To do this, I have created 2 different scenes in the Babylon playground, downloaded their html files, combined the two scenes into one html file, and modified some of the code outside of the “Createscene” functions. These modifications were made using Visual studio. An example of the code that I ended up creating can be seen below: note that comments have been placed nearby the elements that I have modified. Essentially, this code uses a modified version of the “initFunction” function that can be found in most downloaded playground files: when the “8” key is pressed in any scene, it triggers this function to swap to another scene. In the example below, pressing “8” will let the user cycle between a scene with a sphere that’s oscillating in scale, and a scene with a box that is oscillating in scale. Note that the oscillating animations were done with the “registerBeforeRender” function.
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Babylon.js sample code</title>
<!-- Babylon.js -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/dat-gui/0.6.2/dat.gui.min.js"></script>
<script src="https://assets.babylonjs.com/generated/Assets.js"></script>
<script src="https://preview.babylonjs.com/ammo.js"></script>
<script src="https://preview.babylonjs.com/cannon.js"></script>
<script src="https://preview.babylonjs.com/Oimo.js"></script>
<script src="https://preview.babylonjs.com/earcut.min.js"></script>
<script src="https://preview.babylonjs.com/babylon.js"></script>
<script src="https://preview.babylonjs.com/materialsLibrary/babylonjs.materials.min.js"></script>
<script src="https://preview.babylonjs.com/proceduralTexturesLibrary/babylonjs.proceduralTextures.min.js"></script>
<script src="https://preview.babylonjs.com/postProcessesLibrary/babylonjs.postProcess.min.js"></script>
<script src="https://preview.babylonjs.com/loaders/babylonjs.loaders.js"></script>
<script src="https://preview.babylonjs.com/serializers/babylonjs.serializers.min.js"></script>
<script src="https://preview.babylonjs.com/gui/babylon.gui.min.js"></script>
<script src="https://preview.babylonjs.com/inspector/babylon.inspector.bundle.js"></script>
<style>
html, body {
overflow: hidden;
width: 100%;
height: 100%;
margin: 0;
padding: 0;
}
#renderCanvas {
width: 100%;
height: 100%;
touch-action: none;
}
</style>
</head>
<body>
<canvas id="renderCanvas"></canvas>
<script>
var canvas = document.getElementById("renderCanvas");
var startRenderLoop = function (engine, canvas) {
engine.runRenderLoop(function () {
if (sceneToRender && sceneToRender.activeCamera) {
sceneToRender.render();
}
});
}
var engine = null;
var scene = null;
var sceneToRender = null;
var default_engine_created=0;//marks if a engine has been assigned yet (0=no, 1=yes)
var itterable=0;//this creates a term that changes with each frame render
var scene=null;//the first scene
var scene2=null//the 2nd scene
var createDefaultEngine = function() {
if (default_engine_created==0){//ie: if a default engine hasn't yet been created
return new BABYLON.Engine(canvas, true, { preserveDrawingBuffer: true, stencil: true, disableWebGL2Support: false,deterministicLockstep: true, lockstepMaxSteps: 4});
//note that the engine is established with the "deterministicLockstep" property set to true: this is an attempt to any "registerBeforeRender" functionailty independent of framerate.
default_engine_created=1;
}
};
var createScene = function() {
scene = new BABYLON.Scene(engine);//"scene" variable defined earlier, so this just modified variable,
scene.MaxDeltaTime=18
scene.MinDeltaTime=15
//above 2 commands set the maximum and minimum framerates.
//var camera = new BABYLON.ArcRotateCamera("Camera", -Math.PI / 2, Math.PI / 2, 12, BABYLON.Vector3.Zero(), scene);
var camera = new BABYLON.FreeCamera("camera1", new BABYLON.Vector3(0, 5, -10), scene);
// This targets the camera to scene origin
camera.setTarget(BABYLON.Vector3.Zero());
// This attaches the camera to the canvas
camera.attachControl(canvas, true);
//this creates a light
var hemiLight = new BABYLON.HemisphericLight("hemiLight", new BABYLON.Vector3(0, 1, 0), scene);
//this creates a mesh
var sphere = BABYLON.MeshBuilder.CreateSphere("sphere", {diameter: 1}, scene);
//this creates a term that changes with each frame render
itterable=0;
//this creates an animation for the mesh
scene.registerBeforeRender(function () {
sphere.scaling.y=1*Math.sin(BABYLON.Tools.ToRadians(itterable))
itterable+=2;
if (itterable==360){
//this resets the itterable value when it reaches a value of 360, so we don't have the itterable value increasing indefinitely
itterable=0;
}
//engine.deltaTime=16;
//scene.deltaTime=16;
//above commands attempt to keep framerate fixed
});
//I have tried "newScene.onAfterStepObservable.add(function (theScene) {" in place of "registerBeforeRender", but this seems to have had no effect.
//this allows the scene to be changed
scene.onKeyboardObservable.add((kbInfo) => {
switch (kbInfo.type) {
case BABYLON.KeyboardEventTypes.KEYDOWN:
switch (kbInfo.event.key) {
case "8":
initFunction("scene2").then(() => {sceneToRender = scene2});
//when the 8 key is pressed, the scene swaps to scene 2
break
case "1":
console.log(" current deltaTime: "+scene.deltaTime)
break
}
break;
}
});
return scene;
};
var createScene2 = function() {
scene2 = new BABYLON.Scene(engine); //"scene2" variable defined earlier, so this just modified variable,
scene2.MaxDeltaTime=18
scene2.MinDeltaTime=15
//above 2 commands set the maximum and minimum framerates.
//var camera = new BABYLON.ArcRotateCamera("Camera", -Math.PI / 2, Math.PI / 2, 12, BABYLON.Vector3.Zero(), scene);
var camera2 = new BABYLON.FreeCamera("camera1", new BABYLON.Vector3(0, 5, -10), scene2);
// This targets the camera to scene origin
camera2.setTarget(BABYLON.Vector3.Zero());
// This attaches the camera to the canvas
camera2.attachControl(canvas, true);
//this creates a light
var hemiLight2 = new BABYLON.HemisphericLight("hemiLight", new BABYLON.Vector3(0, 1, 0), scene2);
//this creates a mesh
var box = BABYLON.MeshBuilder.CreateBox("box", {size: 1}, scene2);
//this creates a term that changes with each frame render
itterable=0;
//this creates an animation for the mesh
scene2.registerBeforeRender(function () {
box.scaling.y=1*Math.sin(BABYLON.Tools.ToRadians(itterable))+2
itterable+=2;
if (itterable==360){
//this resets the itterable value when it reaches a value of 360, so we don't have the itterable value increasing indefinitely
itterable=0;
}
//engine.deltaTime=16;
//scene2.deltaTime=16;
//above command tries to keep framerate fixed
});
//this allows the scene to be changed
scene2.onKeyboardObservable.add((kbInfo) => {
switch (kbInfo.type) {
case BABYLON.KeyboardEventTypes.KEYDOWN:
switch (kbInfo.event.key) {
case "8":
initFunction("scene").then(() => {sceneToRender = scene});
//when the 8 key is pressed, the scene swaps to scene 2
break
case "1":
console.log(" current deltaTime: "+scene2.deltaTime)
break
}
break;
}
});
return scene2;
};
window.initFunction = async function(input) {
var asyncEngineCreation = async function() {
try {
return createDefaultEngine();
} catch(e) {
console.log("the available createEngine function failed. Creating the default engine instead");
return createDefaultEngine();
}
}
window.engine = await asyncEngineCreation();
engine=window.engine;
if (!engine) throw 'engine should not be null.';
startRenderLoop(engine, canvas);
allscenedispose();//disposes of any scene currently existing (using custom function)
if (window.scene){
window.scene.dispose();//disposes previous scene that window held
window.scene=null;
}
//the below if loops consider the input to this function, and load the corresponding environment.
if (input=="scene"){
window.scene = createScene();
}
if (input=="scene2"){
window.scene = createScene2();
}
};
var allscenedispose=()=>{
//this function disposes everything loadeed currently into each scene. It's designed with try functions so that no errors come up if the scene hasn't got anything to dispose of.
try {
scene.dispose();
scene=null;
}
catch(err) {
console.log("scene dispose failed")
}
try {
scene2.dispose();
scene2=null;
}
catch(err) {
console.log("scene2 dispose failed")
}
}
initFunction("scene").then(() => {sceneToRender = scene
});
// Resize
window.addEventListener("resize", function () {
engine.resize();
});
</script>
</body>
</html>
The problem that I’ve encountered with this code is, if the scene is changed multiple times in a row, the frame rate seems to increase unexpectedly. You will be able to see this from how the rate of the sphere/box’s oscillation seems to speed up, as well as how the first-person camera’s movement speed seems to increase too. You can also press the “1” key in any scene, and the scene’s current “deltaTime” will be printed in the console.
I have tried a few things to remedy this issue. Initially, I tried to set each scene’s “deltaTime” value as a constant, but to no avail. I’ve also tried to use the “deterministicLockstep” property when creating the Babylon.js engine (combined with replacing all “registerBeforeRender” commands with “onAfterStepObservable” commands) in order to make elements like object animation and camera movement independent of the increasing framerate. However, this hasn’t had an effect either. Other than these approaches, I have neither been able to find a way to remove the issue of this increasing framerate, nor have I found the source of the issue.
Therefore, I was wondering, in my construction of this code, whether I have missed any obvious mistakes that are leading to this increasing framerate? Thank you for your help with this.