この記事は「Babylon.js Advent Calendar 2023」の 20日目の記事です。
今回の内容
この記事では、以下の記事で書いていた「スクリーン上でブラウザのウィンドウを動かしたら、そのウィンドウの移動(位置)が描画に影響する」という内容と、Babylon.js のパーティクルの描画を組み合わせてみようと思います。
●「スクリーン上でブラウザのウィンドウを動かしたら描画に影響」「2つのブラウザのウィンドウの一方を動かすと他方に影響」という方向の実装作品の技術メモ - Qiita
https://qiita.com/youtoy/items/945edb13856417c8c901
実行した結果
まず、今回の内容を実行した結果から紹介します。
以下の動画内の内容についてざっくり書くと、ウィンドウが画面の上のほうにあるか、少し真ん中よりの位置(もしくはそれより下)にあるかで、パーティクルの数・背景の色が変わります。
作成した内容
それでは、実際に作った内容についてなど、以下で説明していきます。
元にした内容
今回のパーティクルを使った描画の部分は、公式のドキュメントに掲載されているサンプルを使って試しました。サンプルの掲載元と内容は以下のとおりです。
元にしたサンプルの掲載元: 公式ドキュメント
●Particle System | Babylon.js Documentation
https://doc.babylonjs.com/features/featuresDeepDive/particles/particle_system
元にしたサンプルの内容: Babylon.js Playground上で公開されているもの
●Particle System Examples | Babylon.js Playground
https://playground.babylonjs.com/#0K3AQ2#3067
var createScene = function () {
var scene = new BABYLON.Scene(engine);
// Setup environment
var light0 = new BABYLON.PointLight("Omni", new BABYLON.Vector3(0, 2, 8), scene);
var camera = new BABYLON.ArcRotateCamera("ArcRotateCamera", -Math.PI /2, 7 * Math.PI / 16, 20, new BABYLON.Vector3(0, 0, 0), scene);
camera.attachControl(canvas, true);
// Ground
var ground = BABYLON.Mesh.CreatePlane("ground", 50.0, scene);
ground.position = new BABYLON.Vector3(0, -50, 0);
ground.rotation = new BABYLON.Vector3(Math.PI / 2, 0, 0);
ground.material = new BABYLON.StandardMaterial("groundMat", scene);
ground.material.backFaceCulling = false;
ground.material.diffuseColor = new BABYLON.Color3(0.3, 0.3, 1);
// Create a particle system
var particleSystem = new BABYLON.ParticleSystem("particles", 2000, scene);
//Texture of each particle
particleSystem.particleTexture = new BABYLON.Texture("textures/flare.png", scene);
// Where the particles come from
particleSystem.emitter = BABYLON.Vector3.Zero(); // the starting position
particleSystem.minEmitBox = new BABYLON.Vector3(-1, -1, -1); // Bottom Left Front
particleSystem.maxEmitBox = new BABYLON.Vector3(1, 1, 1); // Top Right Back
// Colors of all particles
particleSystem.color1 = new BABYLON.Color4(0.7, 0.8, 1.0, 1.0);
particleSystem.color2 = new BABYLON.Color4(0.2, 0.5, 1.0, 1.0);
particleSystem.colorDead = new BABYLON.Color4(0, 0, 0.2, 0.0);
// Size of each particle (random between...
particleSystem.minSize = 0.1;
particleSystem.maxSize = 0.5;
// Life time of each particle (random between...
particleSystem.minLifeTime = 0.3;
particleSystem.maxLifeTime = 1.5;
// Emission rate
particleSystem.emitRate = 1500;
// Blend mode : BLENDMODE_ONEONE, or BLENDMODE_STANDARD
particleSystem.blendMode = BABYLON.ParticleSystem.BLENDMODE_ONEONE;
// Set the gravity of all particles
particleSystem.gravity = new BABYLON.Vector3(0, -9.81, 0);
// Direction of each particle after it has been emitted
particleSystem.direction1 = new BABYLON.Vector3(-7, 8, 3);
particleSystem.direction2 = new BABYLON.Vector3(7, 8, -3);
// Angular speed, in radians
particleSystem.minAngularSpeed = 0;
particleSystem.maxAngularSpeed = Math.PI;
// Speed
particleSystem.minEmitPower = 1;
particleSystem.maxEmitPower = 3;
particleSystem.updateSpeed = 0.005;
// Start the particle system
particleSystem.start();
var LTMin = function(value) {
particleSystem.minLifeTime = value;
}
var LTMax = function(value) {
particleSystem.maxLifeTime = value;
}
var updateLabelMinLT = function(value) {
return value.toFixed(3);
}
var updateLabelMaxLT = function(value) {
return value.toFixed(3);
}
var emitRate = function(value) {
particleSystem.emitRate = value;
}
var updateLabelRate = function(value) {
return value.toFixed(0);
}
var emitMinPow = function(value) {
particleSystem.minEmitPower = value;
}
var emitMaxPow = function(value) {
particleSystem.maxEmitPower = value;
}
var updateLabelMinPow = function(value) {
return value.toFixed(3);
}
var updateLabelMaxPow = function(value) {
return value.toFixed(3);
}
var updateSpeed = function(value) {
particleSystem.updateSpeed = value;
}
var updateLabelSpeed = function(value) {
return value.toFixed(5);
}
var advancedTexture = BABYLON.GUI.AdvancedDynamicTexture.CreateFullscreenUI("UI");
var selectBox = new BABYLON.GUI.SelectionPanel("sp");
selectBox.width = 0.25;
selectBox.height = 0.9;
selectBox.horizontalAlignment = BABYLON.GUI.Control.HORIZONTAL_ALIGNMENT_LEFT;
selectBox.verticalAlignment = BABYLON.GUI.Control.VERTICAL_ALIGNMENT_BOTTOM;
advancedTexture.addControl(selectBox);
var sliderLifeTime = new BABYLON.GUI.SliderGroup("LifeTime", "S");
sliderLifeTime.addSlider("Min ", LTMin, "seconds", 0, 20, 0.3, updateLabelMinLT);
sliderLifeTime.addSlider("Max ", LTMax, "seconds", 0, 20, 1.5, updateLabelMaxLT);
selectBox.addGroup(sliderLifeTime);
var sliderEmitRate = new BABYLON.GUI.SliderGroup("Emit Rate", "S");
sliderEmitRate.addSlider("Rate ", emitRate, "units", 0, 2000, 1500, updateLabelRate);
selectBox.addGroup(sliderEmitRate);
var sliderPower = new BABYLON.GUI.SliderGroup("Power", "S");
sliderPower.addSlider("Min ", emitMinPow, "units", 0, 5, 1, updateLabelMinPow);
sliderPower.addSlider("Max ", emitMaxPow, "units", 0, 5, 3, updateLabelMaxPow);
selectBox.addGroup(sliderPower);
var sliderUpdateSpeed = new BABYLON.GUI.SliderGroup("Update Speed", "S");
sliderUpdateSpeed.addSlider("Speed ", updateSpeed, "units", 0, 0.1, 0.01, updateLabelSpeed);
selectBox.addGroup(sliderUpdateSpeed);
return scene;
}
これは、以下のような見た目になります。
変更を加えた後の内容
上で掲載した元のプログラムに対して、スクリーン上でのブラウザのウィンドウの位置に応じた処理を付け加えます。
まずは、他の部分を含めた全体を紹介します。
元の内容が Babylon.js Playground上で提供されていたので、それを直接書きかえて(主には処理の追記を行って)動かしました。
let scene, particleSystem;
let eRate, col1, col2;
function checkWindowMove() {
const screenTop = window.screenTop;
if (screenTop < 200) {
scene.clearColor = new BABYLON.Color4(0.2, 0.3, 0.3, 1);
eRate = 300;
col1 = new BABYLON.Color4(0.9, 0.7, 0.5, 1.0);
col2 = new BABYLON.Color4(0.9, 0.5, 0.2, 1.0);
} else {
scene.clearColor = new BABYLON.Color4(0, 0, 0, 1);
eRate = 1500;
col1 = new BABYLON.Color4(0.7, 0.8, 1.0, 1.0);
col2 = new BABYLON.Color4(0.2, 0.5, 1.0, 1.0);
}
particleSystem.emitRate = eRate;
particleSystem.color1 = col1;
particleSystem.color2 = col2;
}
setInterval(checkWindowMove, 500);
const createScene = function () {
scene = new BABYLON.Scene(engine);
scene.clearColor = new BABYLON.Color4(0, 0, 0, 1);
// Setup environment
const light0 = new BABYLON.PointLight(
"Omni",
new BABYLON.Vector3(0, 2, 8),
scene
);
const camera = new BABYLON.ArcRotateCamera(
"ArcRotateCamera",
-Math.PI / 2,
(7 * Math.PI) / 16,
20,
new BABYLON.Vector3(0, 0, 0),
scene
);
camera.attachControl(canvas, true);
// Ground
const ground = BABYLON.Mesh.CreatePlane("ground", 50.0, scene);
ground.position = new BABYLON.Vector3(0, -50, 0);
ground.rotation = new BABYLON.Vector3(Math.PI / 2, 0, 0);
ground.material = new BABYLON.StandardMaterial("groundMat", scene);
ground.material.backFaceCulling = false;
ground.material.diffuseColor = new BABYLON.Color3(0.3, 0.3, 1);
// Create a particle system
particleSystem = new BABYLON.ParticleSystem("particles", 2000, scene);
//Texture of each particle
particleSystem.particleTexture = new BABYLON.Texture(
"textures/flare.png",
scene
);
// Where the particles come from
particleSystem.emitter = BABYLON.Vector3.Zero(); // the starting position
particleSystem.minEmitBox = new BABYLON.Vector3(-1, -1, -1); // Bottom Left Front
particleSystem.maxEmitBox = new BABYLON.Vector3(1, 1, 1); // Top Right Back
// Colors of all particles
particleSystem.color1 = new BABYLON.Color4(0.7, 0.8, 1.0, 1.0);
particleSystem.color2 = new BABYLON.Color4(0.2, 0.5, 1.0, 1.0);
particleSystem.colorDead = new BABYLON.Color4(0, 0, 0.2, 0.0);
// Size of each particle (random between...
particleSystem.minSize = 0.1;
particleSystem.maxSize = 0.5;
// Life time of each particle (random between...
particleSystem.minLifeTime = 0.3;
particleSystem.maxLifeTime = 1.5;
// Emission rate
eRate = 1500;
particleSystem.emitRate = eRate;
// Blend mode : BLENDMODE_ONEONE, or BLENDMODE_STANDARD
particleSystem.blendMode = BABYLON.ParticleSystem.BLENDMODE_ONEONE;
// Set the gravity of all particles
particleSystem.gravity = new BABYLON.Vector3(0, -9.81, 0);
// Direction of each particle after it has been emitted
particleSystem.direction1 = new BABYLON.Vector3(-7, 8, 3);
particleSystem.direction2 = new BABYLON.Vector3(7, 8, -3);
// Angular speed, in radians
particleSystem.minAngularSpeed = 0;
particleSystem.maxAngularSpeed = Math.PI;
// Speed
particleSystem.minEmitPower = 1;
particleSystem.maxEmitPower = 3;
particleSystem.updateSpeed = 0.005;
// Start the particle system
particleSystem.start();
const LTMin = function (value) {
particleSystem.minLifeTime = value;
};
const LTMax = function (value) {
particleSystem.maxLifeTime = value;
};
const updateLabelMinLT = function (value) {
return value.toFixed(3);
};
const updateLabelMaxLT = function (value) {
return value.toFixed(3);
};
const emitRate = function (value) {
particleSystem.emitRate = value;
};
const updateLabelRate = function (value) {
return value.toFixed(0);
};
const emitMinPow = function (value) {
particleSystem.minEmitPower = value;
};
const emitMaxPow = function (value) {
particleSystem.maxEmitPower = value;
};
const updateLabelMinPow = function (value) {
return value.toFixed(3);
};
const updateLabelMaxPow = function (value) {
return value.toFixed(3);
};
const updateSpeed = function (value) {
particleSystem.updateSpeed = value;
};
const updateLabelSpeed = function (value) {
return value.toFixed(5);
};
const advancedTexture =
BABYLON.GUI.AdvancedDynamicTexture.CreateFullscreenUI("UI");
const selectBox = new BABYLON.GUI.SelectionPanel("sp");
selectBox.width = 0.25;
selectBox.height = 0.9;
selectBox.horizontalAlignment = BABYLON.GUI.Control.HORIZONTAL_ALIGNMENT_LEFT;
selectBox.verticalAlignment = BABYLON.GUI.Control.VERTICAL_ALIGNMENT_BOTTOM;
advancedTexture.addControl(selectBox);
const sliderLifeTime = new BABYLON.GUI.SliderGroup("LifeTime", "S");
sliderLifeTime.addSlider(
"Min ",
LTMin,
"seconds",
0,
20,
0.3,
updateLabelMinLT
);
sliderLifeTime.addSlider(
"Max ",
LTMax,
"seconds",
0,
20,
1.5,
updateLabelMaxLT
);
selectBox.addGroup(sliderLifeTime);
const sliderEmitRate = new BABYLON.GUI.SliderGroup("Emit Rate", "S");
sliderEmitRate.addSlider(
"Rate ",
emitRate,
"units",
0,
2000,
1500,
updateLabelRate
);
selectBox.addGroup(sliderEmitRate);
const sliderPower = new BABYLON.GUI.SliderGroup("Power", "S");
sliderPower.addSlider(
"Min ",
emitMinPow,
"units",
0,
5,
1,
updateLabelMinPow
);
sliderPower.addSlider(
"Max ",
emitMaxPow,
"units",
0,
5,
3,
updateLabelMaxPow
);
selectBox.addGroup(sliderPower);
const sliderUpdateSpeed = new BABYLON.GUI.SliderGroup("Update Speed", "S");
sliderUpdateSpeed.addSlider(
"Speed ",
updateSpeed,
"units",
0,
0.1,
0.01,
updateLabelSpeed
);
selectBox.addGroup(sliderUpdateSpeed);
return scene;
};
付け加えた主な部分
主に変更したというか付け加えた部分は、冒頭の部分です。
let scene, particleSystem;
let eRate, col1, col2;
function checkWindowMove() {
const screenTop = window.screenTop;
if (screenTop < 200) {
scene.clearColor = new BABYLON.Color4(0.2, 0.3, 0.3, 1);
eRate = 300;
col1 = new BABYLON.Color4(0.9, 0.7, 0.5, 1.0);
col2 = new BABYLON.Color4(0.9, 0.5, 0.2, 1.0);
} else {
scene.clearColor = new BABYLON.Color4(0, 0, 0, 1);
eRate = 1500;
col1 = new BABYLON.Color4(0.7, 0.8, 1.0, 1.0);
col2 = new BABYLON.Color4(0.2, 0.5, 1.0, 1.0);
}
particleSystem.emitRate = eRate;
particleSystem.color1 = col1;
particleSystem.color2 = col2;
}
setInterval(checkWindowMove, 500);
あまり凝ったことはしておらず、一定の時間間隔で window.screenTop
を取得し、その値を使った処理をしています。
そして、その取得した値が適当な閾値より小さいかどうかによって、背景の色・パーティクルの色・発生量に関わるパラメータを変更している形です。
おわりに
今回、ブラウザのウィンドウをスクリーン上で動かした時に、その位置の変化が Babylon.js で描画したパーティクルのパラメータに影響を及ぼすという内容を試しました。
今回の内容は、とりあえずの動作確認という感じだったので、さらに色々試せればと思います。
今回対応できてないこと
今回、これもできたらと思って、対応しきれなかったことのメモです。
ウィンドウを揺らしたら、その揺れが画面内に伝わるようなイメージのこともできればと思ったのですが、時間が足りずでした。ちなみに、そのような挙動をする描画について、p5.js を使ったものは、前に以下を作っていたりしました。