教程:弹奏 3D 钢琴
在之前的教程中,我们已经成功创建了一个完整的 88 键钢琴键盘模型。 现在我们让钢琴可供在 XR 空间中弹奏。
在本教程中,您将学习如何执行以下操作:
- 使用指针事件添加交互式钢琴功能
- 将网格缩放到不同的大小
- 在 XR 中启用传送和多指针支持
开始之前
确保已完成本教程系列的上一个教程,准备好继续添加代码。
index.html
<html>
<head>
<title>Piano in BabylonJS</title>
<script src="https://cdn.babylonjs.com/babylon.js"></script>
<script src="scene.js"></script>
<style>
body,#renderCanvas { width: 100%; height: 100%;}
</style>
</head>
<body>
<canvas id="renderCanvas"></canvas>
<script type="text/javascript">
const canvas = document.getElementById("renderCanvas");
const engine = new BABYLON.Engine(canvas, true);
createScene(engine).then(sceneToRender => {
engine.runRenderLoop(() => sceneToRender.render());
});
// Watch for browser/canvas resize events
window.addEventListener("resize", function () {
engine.resize();
});
</script>
</body>
</html>
scene.js
const buildKey = function (scene, parent, props) {
if (props.type === "white") {
/*
Props for building a white key should contain:
note, topWidth, bottomWidth, topPositionX, wholePositionX, register, referencePositionX
As an example, the props for building the middle C white key would be
{type: "white", note: "C", topWidth: 1.4, bottomWidth: 2.3, topPositionX: -0.45, wholePositionX: -14.4, register: 4, referencePositionX: 0}
*/
// Create bottom part
const bottom = BABYLON.MeshBuilder.CreateBox("whiteKeyBottom", {width: props.bottomWidth, height: 1.5, depth: 4.5}, scene);
// Create top part
const top = BABYLON.MeshBuilder.CreateBox("whiteKeyTop", {width: props.topWidth, height: 1.5, depth: 5}, scene);
top.position.z = 4.75;
top.position.x += props.topPositionX;
// Merge bottom and top parts
// Parameters of BABYLON.Mesh.MergeMeshes: (arrayOfMeshes, disposeSource, allow32BitsIndices, meshSubclass, subdivideWithSubMeshes, multiMultiMaterials)
const key = BABYLON.Mesh.MergeMeshes([bottom, top], true, false, null, false, false);
key.position.x = props.referencePositionX + props.wholePositionX;
key.name = props.note + props.register;
key.parent = parent;
return key;
}
else if (props.type === "black") {
/*
Props for building a black key should contain:
note, wholePositionX, register, referencePositionX
As an example, the props for building the C#4 black key would be
{type: "black", note: "C#", wholePositionX: -13.45, register: 4, referencePositionX: 0}
*/
// Create black color material
const blackMat = new BABYLON.StandardMaterial("black");
blackMat.diffuseColor = new BABYLON.Color3(0, 0, 0);
// Create black key
const key = BABYLON.MeshBuilder.CreateBox(props.note + props.register, {width: 1.4, height: 2, depth: 5}, scene);
key.position.z += 4.75;
key.position.y += 0.25;
key.position.x = props.referencePositionX + props.wholePositionX;
key.material = blackMat;
key.parent = parent;
return key;
}
}
const createScene = async function(engine) {
const scene = new BABYLON.Scene(engine);
const alpha = 3*Math.PI/2;
const beta = Math.PI/50;
const radius = 220;
const target = new BABYLON.Vector3(0, 0, 0);
const camera = new BABYLON.ArcRotateCamera("Camera", alpha, beta, radius, target, scene);
camera.attachControl(canvas, true);
const light = new BABYLON.HemisphericLight("light", new BABYLON.Vector3(0, 1, 0), scene);
light.intensity = 0.6;
const keyParams = [
{type: "white", note: "C", topWidth: 1.4, bottomWidth: 2.3, topPositionX: -0.45, wholePositionX: -14.4},
{type: "black", note: "C#", wholePositionX: -13.45},
{type: "white", note: "D", topWidth: 1.4, bottomWidth: 2.4, topPositionX: 0, wholePositionX: -12},
{type: "black", note: "D#", wholePositionX: -10.6},
{type: "white", note: "E", topWidth: 1.4, bottomWidth: 2.3, topPositionX: 0.45, wholePositionX: -9.6},
{type: "white", note: "F", topWidth: 1.3, bottomWidth: 2.4, topPositionX: -0.55, wholePositionX: -7.2},
{type: "black", note: "F#", wholePositionX: -6.35},
{type: "white", note: "G", topWidth: 1.3, bottomWidth: 2.3, topPositionX: -0.2, wholePositionX: -4.8},
{type: "black", note: "G#", wholePositionX: -3.6},
{type: "white", note: "A", topWidth: 1.3, bottomWidth: 2.3, topPositionX: 0.2, wholePositionX: -2.4},
{type: "black", note: "A#", wholePositionX: -0.85},
{type: "white", note: "B", topWidth: 1.3, bottomWidth: 2.4, topPositionX: 0.55, wholePositionX: 0},
]
// Transform Node that acts as the parent of all piano keys
const keyboard = new BABYLON.TransformNode("keyboard");
// Register 1 through 7
var referencePositionX = -2.4*14;
for (let register = 1; register <= 7; register++) {
keyParams.forEach(key => {
buildKey(scene, keyboard, Object.assign({register: register, referencePositionX: referencePositionX}, key));
})
referencePositionX += 2.4*7;
}
// Register 0
buildKey(scene, keyboard, {type: "white", note: "A", topWidth: 1.9, bottomWidth: 2.3, topPositionX: -0.20, wholePositionX: -2.4, register: 0, referencePositionX: -2.4*21});
keyParams.slice(10, 12).forEach(key => {
buildKey(scene, keyboard, Object.assign({register: 0, referencePositionX: -2.4*21}, key));
})
// Register 8
buildKey(scene, keyboard, {type: "white", note: "C", topWidth: 2.3, bottomWidth: 2.3, topPositionX: 0, wholePositionX: -2.4*6, register: 8, referencePositionX: 84});
// Transform node that acts as the parent of all piano components
const piano = new BABYLON.TransformNode("piano");
keyboard.parent = piano;
// Import and scale piano frame
BABYLON.SceneLoader.ImportMesh("frame", "https://raw.githubusercontent.com/MicrosoftDocs/mixed-reality/docs/mixed-reality-docs/mr-dev-docs/develop/javascript/tutorials/babylonjs-webxr-piano/files/", "pianoFrame.babylon", scene, function(meshes) {
const frame = meshes[0];
frame.parent = piano;
});
// Lift the piano keyboard
keyboard.position.y += 80;
const xrHelper = await scene.createDefaultXRExperienceAsync();
return scene;
}
使钢琴键盘可供弹奏
现在,我们创建的钢琴键盘是一个静态模型,不响应任何用户交互。 在本部分中,我们将对琴键进行编程,使琴键在有人按下它时向下移动并播放声音。
Babylon.js 提供可与之交互的不同类型的事件或可观察对象。 在示例中,我们将处理
onPointerObservable
,因为我们想要对琴键进行编程,以便在有人通过指针(可以是鼠标单击、触摸、XR 控制器按钮单击等)按下琴键时执行操作。下面是如何向
onPointerObservable
添加任何行为的基本结构:scene.onPointerObservable.add((pointerInfo) => { // do something });
虽然 Babylon.js 提供了许多不同类型的指针事件,但我们将仅使用
POINTERDOWN
和POINTERUP
事件通过以下结构对钢琴键的行为进行编程:scene.onPointerObservable.add((pointerInfo) => { switch (pointerInfo.type) { case BABYLON.PointerEventTypes.POINTERDOWN: // When the pointer is down on a piano key, // move the piano key downward (to show that it is pressed) // and play the sound of the note break; case BABYLON.PointerEventTypes.POINTERUP: // When the pointer is released, // move the piano key upward to its original position // and stop the sound of the note of the key that is released break; } });
让我们首先研究如何在按下和松开琴键时向下和向上移动钢琴键。
在指针按下事件中,我们需要检测正在单击的网格,确保它是钢琴键,并将网格的 y 坐标值稍微减小,使其看起来像是按下了琴键。
对于指针向上事件,稍微复杂一些,因为按下了琴键的指针可能不会在琴键上松开。 例如,有人可能会按下 C4 键,将鼠标拖到 E4,然后松开。 在这种情况下,我们仍然希望松开按下的琴键 (C4),而不是发生
pointerUp
事件的位置 (E4)。我们来看看下面的代码如何实现所需目标:
const pointerToKey = new Map(); scene.onPointerObservable.add((pointerInfo) => { switch (pointerInfo.type) { case BABYLON.PointerEventTypes.POINTERDOWN: if(pointerInfo.pickInfo.hit) { const pickedMesh = pointerInfo.pickInfo.pickedMesh; const pointerId = pointerInfo.event.pointerId; if (pickedMesh.parent === keyboard) { pickedMesh.position.y -= 0.5; // play the sound of the note pointerToKey.set(pointerId, { mesh: pickedMesh }); } } break; case BABYLON.PointerEventTypes.POINTERUP: const pointerId = pointerInfo.event.pointerId; if (pointerToKey.has(pointerId)) { pointerToKey.get(pointerId).mesh.position.y += 0.5; // stop the sound of the note of the key that is released pointerToKey.delete(pointerId); } break; } });
pointerId
对每个指针都是唯一的,当我们有多个控制器或使用触摸屏时,它可以帮助我们识别指针。 在这里,我们初始化了一个名为pointerToKey
的Map
对象,以存储哪个指针按下了哪个琴键的关系,这样我们就知道当指针松开时要松开哪个琴键,而不管松开发生的位置。与上面的代码交互如下所示:
现在我们研究如何在按下和松开琴键时播放和停止声音。 为此,我们将使用名为 soundfont-player 的 JavaScript 库,这样就能够轻松播放所选乐器的 MIDI 声音了。
下载库的极简化代码,将其保存在与 index.html 相同的文件夹中,并将其包含在 index.html 的
<header>
标记中:<head> <title>Babylon Template</title> <script src="https://cdn.babylonjs.com/babylon.js"></script> <script src="scene.js"></script> <script src="soundfont-player.min.js"></script> <style> body,#renderCanvas { width: 100%; height: 100%;} </style> </head>
导入库后,按照如下所示使用库初始化乐器并播放/停止 MIDI 声音:
const pianoSound = await Soundfont.instrument(new AudioContext(), 'acoustic_grand_piano'); const C4 = piano.play("C4"); // Play note C4 C4.stop(); // Stop note C4
现在让我们将其合并到指针事件中并完成本部分的代码:
const pointerToKey = new Map() const piano = await Soundfont.instrument(new AudioContext(), 'acoustic_grand_piano'); scene.onPointerObservable.add((pointerInfo) => { switch (pointerInfo.type) { case BABYLON.PointerEventTypes.POINTERDOWN: if(pointerInfo.pickInfo.hit) { let pickedMesh = pointerInfo.pickInfo.pickedMesh; let pointerId = pointerInfo.event.pointerId; if (keys.has(pickedMesh)) { pickedMesh.position.y -= 0.5; // Move the key downward pointerToKey.set(pointerId, { mesh: pickedMesh, note: pianoSound.play(pointerInfo.pickInfo.pickedMesh.name) // Play the sound of the note }); } } break; case BABYLON.PointerEventTypes.POINTERUP: let pointerId = pointerInfo.event.pointerId; if (pointerToKey.has(pointerId)) { pointerToKey.get(pointerId).mesh.position.y += 0.5; // Move the key upward pointerToKey.get(pointerId).note.stop(); // Stop the sound of the note pointerToKey.delete(pointerId); } break; } });
由于我们根据每个琴键所代表的音符命名了每个琴键的网格,因此可以通过将网格的名称传递给
pianoSound.play()
函数来轻松指示要播放的音符。 另请注意,我们将声音存储到pointerToKey
映射中,这样就可以知道松开琴键时要停止的声音。
为沉浸式 VR 模式缩放钢琴
到现在为止,当你添加交互式功能时,你可能已经使用鼠标(甚至使用触摸屏)弹奏了钢琴。 在本部分中,我们将进入沉浸式 VR 空间。
若要在沉浸式 VR 头戴显示设备中打开页面,必须先将头戴显示设备连接到开发者计算机,并确保将其设置为在 Windows Mixed Reality 应用中使用。 如果使用的是 Windows Mixed Reality 模拟器,请确保它已启用。
现在,你将在网页的右下角看到一个沉浸式 VR 按钮。 单击该按钮,你将能够在所连接到的 XR 设备中看到钢琴。
进入虚拟空间后,你可能会注意到所构建的钢琴十分巨大。 在 VR 世界中,只能站在钢琴的底部,通过将指针指向远处的琴键来弹奏钢琴。
按比例缩小钢琴,使其尺寸更像真实世界中的普通立式钢琴。 为此,需要使用效用函数,该函数允许相对于空间中的一个点缩放网格。 将此函数添加到 scene.js(
createScene()
之外):const scaleFromPivot = function(transformNode, pivotPoint, scale) { const _sx = scale / transformNode.scaling.x; const _sy = scale / transformNode.scaling.y; const _sz = scale / transformNode.scaling.z; transformNode.scaling = new BABYLON.Vector3(_sx, _sy, _sz); transformNode.position = new BABYLON.Vector3(pivotPoint.x + _sx * (transformNode.position.x - pivotPoint.x), pivotPoint.y + _sy * (transformNode.position.y - pivotPoint.y), pivotPoint.z + _sz * (transformNode.position.z - pivotPoint.z)); }
此函数采用 3 个参数:
- transformNode:要缩放的
TransformNode
- pivotPoint:一个
Vector3
对象,它指示缩放相对于的点 - 缩放:比例因子
- transformNode:要缩放的
我们将使用此函数以 0.015 的系数缩放钢琴框架和琴键,并以原点为轴心点。 通过将函数调用放在
keyboard.position.y += 80;
之后,将其追加到createScene()
函数:// Put this line at the beginning of createScene() const scale = 0.015;
// Put this function call after keyboard.position.y += 80; // Scale the entire piano scaleFromPivot(piano, new BABYLON.Vector3(0, 0, 0), scale);
我们也不要忘记缩放相机位置:
const alpha = 3*Math.PI/2; const beta = Math.PI/50; const radius = 220*scale; // scale the radius const target = new BABYLON.Vector3(0, 0, 0); const camera = new BABYLON.ArcRotateCamera("Camera", alpha, beta, radius, target, scene); camera.attachControl(canvas, true);
现在,当我们再次进入 VR 空间时,钢琴将是普通立式钢琴的大小。
启用 WebXR 功能
现在我们已经在 VR 空间中将钢琴缩放到合适的尺寸,接下来启用一些很不错的 WebXR 功能来改善空间中的钢琴演奏体验。
如果你一直使用沉浸式 VR 控制器弹奏钢琴,可能已经注意到一次只能使用一个控制器。 让我们使用 Babylon.js 的 WebXR 功能管理器在 XR 空间中启用多指针支持。
在
xrHelper
初始化行之后,将以下代码添加到createScene()
函数中:const featuresManager = xrHelper.baseExperience.featuresManager; const pointerSelection = featuresManager.enableFeature(BABYLON.WebXRFeatureName.POINTER_SELECTION, "stable", { xrInput: xrHelper.input, enablePointerSelectionOnAllControllers: true });
此外,根据起点位置,你可能发现将自己定位在钢琴前有点困难。 如果你熟悉沉浸式 VR 环境,便已了解“传送”,它是一项功能,允许通过指向空间中的另一个位置立即移动到该位置。
若要使用 Babylon.js 的传送功能,首先需要有一个可以在 VR 空间中“站立”的地面网格。 将以下代码添加到
createScene()
函数以创建地面:const ground = BABYLON.MeshBuilder.CreateGround("ground", {width: 400, height: 400});
传送支持还附带一个非常有用的功能,称为对齐位置。 简而言之,对齐位置是我们希望用户所在的特定位置。
例如,可以在钢琴前面设置一个对齐位置,当用户将指针指向钢琴附近时,这样他们就可以轻松地传送到该位置。
追加以下代码以启用传送功能并指定对齐点:
const teleportation = featuresManager.enableFeature(BABYLON.WebXRFeatureName.TELEPORTATION, "stable", { xrInput: xrHelper.input, floorMeshes: [ground], snapPositions: [new BABYLON.Vector3(2.4*3.5*scale, 0, -10*scale)], });
现在,你应能够通过传送到钢琴前的对齐点轻松地将自己定位在钢琴前,并且应能够使用两个控制器一次弹奏两个琴键。
总结
恭喜! 你已完成 Babylon.js 钢琴构建教程系列,并了解了如何:
- 创建、定位和合并网格以构建钢琴键盘模型
- 导入立式钢琴框架的 Babylon.js 模型
- 为每个钢琴键添加指针交互
- 基于轴心点缩放网格的大小
- 启用关键的 WebXR 功能,例如传送和多指针支持
下面是 scene.js 和 index.html 的最终代码:
scene.js
const buildKey = function (scene, parent, props) {
if (props.type === "white") {
/*
Props for building a white key should contain:
note, topWidth, bottomWidth, topPositionX, wholePositionX, register, referencePositionX
As an example, the props for building the middle C white key would be
{type: "white", note: "C", topWidth: 1.4, bottomWidth: 2.3, topPositionX: -0.45, wholePositionX: -14.4, register: 4, referencePositionX: 0}
*/
// Create bottom part
const bottom = BABYLON.MeshBuilder.CreateBox("whiteKeyBottom", {width: props.bottomWidth, height: 1.5, depth: 4.5}, scene);
// Create top part
const top = BABYLON.MeshBuilder.CreateBox("whiteKeyTop", {width: props.topWidth, height: 1.5, depth: 5}, scene);
top.position.z = 4.75;
top.position.x += props.topPositionX;
// Merge bottom and top parts
// Parameters of BABYLON.Mesh.MergeMeshes: (arrayOfMeshes, disposeSource, allow32BitsIndices, meshSubclass, subdivideWithSubMeshes, multiMultiMaterials)
const key = BABYLON.Mesh.MergeMeshes([bottom, top], true, false, null, false, false);
key.position.x = props.referencePositionX + props.wholePositionX;
key.name = props.note + props.register;
key.parent = parent;
return key;
}
else if (props.type === "black") {
/*
Props for building a black key should contain:
note, wholePositionX, register, referencePositionX
As an example, the props for building the C#4 black key would be
{type: "black", note: "C#", wholePositionX: -13.45, register: 4, referencePositionX: 0}
*/
// Create black color material
const blackMat = new BABYLON.StandardMaterial("black");
blackMat.diffuseColor = new BABYLON.Color3(0, 0, 0);
// Create black key
const key = BABYLON.MeshBuilder.CreateBox(props.note + props.register, {width: 1.4, height: 2, depth: 5}, scene);
key.position.z += 4.75;
key.position.y += 0.25;
key.position.x = props.referencePositionX + props.wholePositionX;
key.material = blackMat;
key.parent = parent;
return key;
}
}
const scaleFromPivot = function(transformNode, pivotPoint, scale) {
const _sx = scale / transformNode.scaling.x;
const _sy = scale / transformNode.scaling.y;
const _sz = scale / transformNode.scaling.z;
transformNode.scaling = new BABYLON.Vector3(_sx, _sy, _sz);
transformNode.position = new BABYLON.Vector3(pivotPoint.x + _sx * (transformNode.position.x - pivotPoint.x), pivotPoint.y + _sy * (transformNode.position.y - pivotPoint.y), pivotPoint.z + _sz * (transformNode.position.z - pivotPoint.z));
}
const createScene = async function(engine) {
const scale = 0.015;
const scene = new BABYLON.Scene(engine);
const alpha = 3*Math.PI/2;
const beta = Math.PI/50;
const radius = 220*scale;
const target = new BABYLON.Vector3(0, 0, 0);
const camera = new BABYLON.ArcRotateCamera("Camera", alpha, beta, radius, target, scene);
camera.attachControl(canvas, true);
const light = new BABYLON.HemisphericLight("light", new BABYLON.Vector3(0, 1, 0), scene);
light.intensity = 0.6;
const keyParams = [
{type: "white", note: "C", topWidth: 1.4, bottomWidth: 2.3, topPositionX: -0.45, wholePositionX: -14.4},
{type: "black", note: "C#", wholePositionX: -13.45},
{type: "white", note: "D", topWidth: 1.4, bottomWidth: 2.4, topPositionX: 0, wholePositionX: -12},
{type: "black", note: "D#", wholePositionX: -10.6},
{type: "white", note: "E", topWidth: 1.4, bottomWidth: 2.3, topPositionX: 0.45, wholePositionX: -9.6},
{type: "white", note: "F", topWidth: 1.3, bottomWidth: 2.4, topPositionX: -0.55, wholePositionX: -7.2},
{type: "black", note: "F#", wholePositionX: -6.35},
{type: "white", note: "G", topWidth: 1.3, bottomWidth: 2.3, topPositionX: -0.2, wholePositionX: -4.8},
{type: "black", note: "G#", wholePositionX: -3.6},
{type: "white", note: "A", topWidth: 1.3, bottomWidth: 2.3, topPositionX: 0.2, wholePositionX: -2.4},
{type: "black", note: "A#", wholePositionX: -0.85},
{type: "white", note: "B", topWidth: 1.3, bottomWidth: 2.4, topPositionX: 0.55, wholePositionX: 0},
]
// Transform Node that acts as the parent of all piano keys
const keyboard = new BABYLON.TransformNode("keyboard");
// Register 1 through 7
var referencePositionX = -2.4*14;
for (let register = 1; register <= 7; register++) {
keyParams.forEach(key => {
buildKey(scene, keyboard, Object.assign({register: register, referencePositionX: referencePositionX}, key));
})
referencePositionX += 2.4*7;
}
// Register 0
buildKey(scene, keyboard, {type: "white", note: "A", topWidth: 1.9, bottomWidth: 2.3, topPositionX: -0.20, wholePositionX: -2.4, register: 0, referencePositionX: -2.4*21});
keyParams.slice(10, 12).forEach(key => {
buildKey(scene, keyboard, Object.assign({register: 0, referencePositionX: -2.4*21}, key));
})
// Register 8
buildKey(scene, keyboard, {type: "white", note: "C", topWidth: 2.3, bottomWidth: 2.3, topPositionX: 0, wholePositionX: -2.4*6, register: 8, referencePositionX: 84});
// Transform node that acts as the parent of all piano components
const piano = new BABYLON.TransformNode("piano");
keyboard.parent = piano;
// Import and scale piano frame
BABYLON.SceneLoader.ImportMesh("frame", "https://raw.githubusercontent.com/MicrosoftDocs/mixed-reality/docs/mixed-reality-docs/mr-dev-docs/develop/javascript/tutorials/babylonjs-webxr-piano/files/", "pianoFrame.babylon", scene, function(meshes) {
const frame = meshes[0];
frame.parent = piano;
});
// Lift the piano keyboard
keyboard.position.y += 80;
// Scale the entire piano
scaleFromPivot(piano, new BABYLON.Vector3(0, 0, 0), scale);
const pointerToKey = new Map()
const pianoSound = await Soundfont.instrument(new AudioContext(), 'acoustic_grand_piano');
scene.onPointerObservable.add((pointerInfo) => {
switch (pointerInfo.type) {
case BABYLON.PointerEventTypes.POINTERDOWN:
// Only take action if the pointer is down on a mesh
if(pointerInfo.pickInfo.hit) {
let pickedMesh = pointerInfo.pickInfo.pickedMesh;
let pointerId = pointerInfo.event.pointerId;
if (pickedMesh.parent === keyboard) {
pickedMesh.position.y -= 0.5; // Move the key downward
pointerToKey.set(pointerId, {
mesh: pickedMesh,
note: pianoSound.play(pointerInfo.pickInfo.pickedMesh.name) // Play the sound of the note
});
}
}
break;
case BABYLON.PointerEventTypes.POINTERUP:
let pointerId = pointerInfo.event.pointerId;
// Only take action if the released pointer was recorded in pointerToKey
if (pointerToKey.has(pointerId)) {
pointerToKey.get(pointerId).mesh.position.y += 0.5; // Move the key upward
pointerToKey.get(pointerId).note.stop(); // Stop the sound of the note
pointerToKey.delete(pointerId);
}
break;
}
});
const xrHelper = await scene.createDefaultXRExperienceAsync();
const featuresManager = xrHelper.baseExperience.featuresManager;
featuresManager.enableFeature(BABYLON.WebXRFeatureName.POINTER_SELECTION, "stable", {
xrInput: xrHelper.input,
enablePointerSelectionOnAllControllers: true
});
const ground = BABYLON.MeshBuilder.CreateGround("ground", {width: 400, height: 400});
featuresManager.enableFeature(BABYLON.WebXRFeatureName.TELEPORTATION, "stable", {
xrInput: xrHelper.input,
floorMeshes: [ground],
snapPositions: [new BABYLON.Vector3(2.4*3.5*scale, 0, -10*scale)],
});
return scene;
}
index.html
<html>
<head>
<title>Babylon Template</title>
<script src="https://cdn.babylonjs.com/babylon.js"></script>
<script src="scene.js"></script>
<script src="soundfont-player.min.js"></script>
<style>
body,#renderCanvas { width: 100%; height: 100%;}
</style>
</head>
<body>
<canvas id="renderCanvas"></canvas>
<script>
const canvas = document.getElementById("renderCanvas"); // Get the canvas element
const engine = new BABYLON.Engine(canvas, true); // Generate the BABYLON 3D engine
// Register a render loop to repeatedly render the scene
createScene(engine).then(sceneToRender => {
engine.runRenderLoop(() => sceneToRender.render());
});
// Watch for browser/canvas resize events
window.addEventListener("resize", function () {
engine.resize();
});
</script>
</body>
</html>
后续步骤
有关混合现实 JavaScript 开发的更多信息,请参阅 JavaScript 开发概述。