共用方式為


教學:製作一個 3D 鋼琴模型

在系列的前一個教學中,你會建立一個網頁,裡面有一個 Babylon.js 場景,配有攝影機和燈光。 在這個教學中,你會建立並新增一個鋼琴模型到場景中。

立式鋼琴網格

在這個教學中,你會學到如何:

  • 建立、定位與合併網格
  • 用盒子網格製作鋼琴鍵盤
  • 匯入鋼琴框架的 3D 模型

開始之前

請確保你已經完成 系列中之前的教學 ,並準備好繼續添加程式碼。

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 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 xrHelper = await scene.createDefaultXRExperienceAsync();

    return scene;
}

快速入門

讓我們先從製作一個結構簡單的鋼琴鍵盤開始:

鋼琴音區描述

在這張圖片中,有七個白鍵和五個黑鍵,每個鍵上都標有音符名稱。 一台完整的88鍵鋼琴鍵盤包含七個完整的鍵選重複 (也稱為暫存) 及四個額外鍵。 每個暫存器的頻率是前一個暫存器的兩倍。 例如,C5的音高頻率 (,代表第五音區) 的C音是C4的兩倍,D5的音高頻率是D4的兩倍,依此類推。

視覺上,每個音區看起來都完全相同,因此我們可以從如何用這些鍵組成簡單的鋼琴鍵盤開始。 之後,我們可以找到方法將範圍擴展到88鍵全鋼琴鍵盤。

打造一個簡單的鋼琴鍵盤

注意事項

雖然你可以從線上找到預製的鋼琴鍵盤 3D 模型並匯入我們的網頁,但在這個教學中,我們將從零開始打造鍵盤,讓玩家能最大化自訂性,並展示如何透過 Babylon.js 製作 3D 模型。

  1. 在開始建立任何用於製作鍵盤的網格之前,請注意每個黑鍵並非完全對齊在周圍兩個白鍵的中間,且每個鍵寬度也不相同。 這表示我們必須為每個金鑰分別建立並定位網格。

    黑鍵對齊

  2. 對於白鍵,我們可以觀察到每個白鍵由兩部分組成: (1) 黑鍵下方的底部部分 (s) , (2) 上半部,靠近黑鍵 (s) 。 這兩個部分尺寸不同,但堆疊在一起形成完整的白鍵。

    白鍵形狀

  3. 這是為音符 C 建立單一白鍵的程式碼 (暫時不用擔心要加進 scene.js) :

    const whiteKeyBottom = BABYLON.MeshBuilder.CreateBox("whiteKeyBottom", {width: 2.3, height: 1.5, depth: 4.5}, scene);
    const whiteKeyTop = BABYLON.MeshBuilder.CreateBox("whiteKeyTop", {width: 1.4, height: 1.5, depth: 5}, scene);
    whiteKeyTop.position.z += 4.75;
    whiteKeyTop.position.x -= 0.45;
    
    // Parameters of BABYLON.Mesh.MergeMeshes:
    // (arrayOfMeshes, disposeSource, allow32BitsIndices, meshSubclass, subdivideWithSubMeshes, multiMultiMaterials)
    const whiteKeyV1 = BABYLON.Mesh.MergeMeshes([whiteKeyBottom, whiteKeyTop], true, false, null, false, false);
    whiteKeyV1.material = whiteMat;
    whiteKeyV1.name = "C4";
    

    這裡我們建立了兩個 Box 網格,一個用於白鍵的底部,一個用於上方。 接著我們調整上方部分的位置,將其堆疊在下方部分上方,並往左移動,為鄰近的黑鍵 C# (留出空間 ) 。

    最後,這兩個部分透過 MergeMeshes 功能合併成一個完整的白鍵。 這就是這段程式碼所產生的網格:

    白鍵C

  4. 建立黑鍵比較簡單。 由於所有黑色鍵都是盒子形狀,我們只需用黑色 StandardMaterial 建立盒子網格即可建立黑鍵。

    注意事項

    由於預設的網格顏色是類似白色的淺灰色,這個教學沒有包含如何為白色按鍵添加白色材質的步驟。 不過,如果你想要在白鍵上呈現真正明亮的白色,也可以自己加上材質。

    這是建立黑鍵 C# 的程式碼 (不用擔心要把這個加進 scene.js) :

    const blackMat = new BABYLON.StandardMaterial("black");
    blackMat.diffuseColor = new BABYLON.Color3(0, 0, 0);
    
    const blackKey = BABYLON.MeshBuilder.CreateBox("C#4", {width: 1.4, height: 2, depth: 5}, scene);
    blackKey.position.z += 4.75;
    blackKey.position.y += 0.25;
    blackKey.position.x += 0.95;
    blackKey.material = blackMat;
    

    這組程式碼 (產生的黑鍵與前一個白鍵) 會長如下:

    黑鑰匙C#

  5. 如你所見,建立每個鍵時,會產生許多相似的代碼,因為我們必須指定它們的尺寸和位置。 讓我們在下一節試著讓創作過程更有效率。

有效率地打造一台簡單的鋼琴鍵盤

  1. 雖然每個白色鍵的形狀略有不同,但都可以透過結合上下兩部分來製作。 我們來實作一個通用函式來建立並定位任何白鍵。

    在函數之外,將下方函數加到 scene.js,函數之外 createScene()

    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;
        }
    }
    

    在這段程式碼中,我們建立了一個名為 buildKey()的函式,當 為 時"white",它會建立並回傳白鍵。props.type 透過在參數 props中識別鍵的類型,我們可以在同一函式中同時建立黑鍵和白鍵,方法是透過 if 陳述式分支。

    buildKey() 參數如下:

    • 場景:鑰匙所在的場景
    • 鍵:網格的父鍵 (這讓我們可以將所有鍵鍵群組到單一的父鍵)
    • 道具:將要建立的密鑰屬性

    白鍵的物品 props 將包含以下物品:

    • 類型:「白色」
    • 名稱:該鍵所代表的音符名稱
    • topWidth:頂部部分的寬度
    • bottomWidth:底部部分的寬度
    • topPositionX:上半部相對於下半部的 x-位置
    • wholePositionX:整個鍵相對於暫存器末點 (鍵 B) 右邊的 x-位置。
    • 登錄器:註冊該金鑰屬於 (介於 0 到 8 之間的數字)
    • referencePositionX:暫存器端點的 x 座標 (用作參考點) 。

    透過分離 wholePositionX 和 ,我們能初始化props建立特定類型的金鑰 (所需的參數,例如 C) 在任何暫存器中,然後在特定暫存器中建立該金鑰時, (C4、C5) 時,再加 registerreferencePositionXpropsreferencePositionX

  2. 同樣地,我們也可以寫一個通用函式來建立黑鍵。 讓我們將函數擴展 buildKey() 到包含這個邏輯:

    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;
        }
    }
    

    對於黑鍵來說,包含 props 以下項目:

    • 類型:「黑色」
    • 名稱:該鍵所代表的音符名稱
    • wholePositionX:整個鍵相對於暫存器末點 (鍵 B 右邊的 x-位置)
    • 登錄器:註冊該金鑰屬於 (介於 0 到 8 之間的數字)
    • referencePositionX:暫存器端點的 x 座標 (用作參考點) 。

    製作黑鍵的做法 props 簡單多了,因為製作黑鍵只需要建立一個框,且每個黑鍵的寬度和 z 位置都相同。

  3. 現在我們有了更有效率的鍵創建方法,先初始化一個陣列,儲存 props 每個鍵對應暫存器中音符的 ,然後呼叫 buildKey() 每個鍵的函式,在第四暫存器中建立一個簡單的鍵盤。

    我們也會建立一個 TransformNodekeyboard,作為所有鋼琴鍵的節點。 由於任何對父鍵的定位或縮放變更也會套用到子鍵,因此將鍵以這種方式分組,可以讓我們能夠整體調整或移動鍵。

    在函 createScene() 式中附加以下程式碼行:

    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");
    
    keyParams.forEach(key => {
        buildKey(scene, keyboard, Object.assign({register: 4, referencePositionX: 0}, key));
    })
    

    你可能已經注意到,在這個程式碼區塊中,我們是相對於空間原點放置所有鍵。

  4. 以下是目前 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");
    
        keyParams.forEach(key => {
            buildKey(scene, keyboard, Object.assign({register: 4, referencePositionX: 0}, key));
        })
    
        const xrHelper = await scene.createDefaultXRExperienceAsync();
    
        return scene;
    }
    
  5. 這就是最終鍵盤的樣子:

    單一音區鋼琴鍵盤

擴展至88鍵鋼琴

在本節中,我們將擴展按鍵創建功能的使用,來生成一台完整的 88 鍵鋼琴鍵盤。

  1. 如前所述,一台完整的88鍵鋼琴鍵盤包含七個重複音區及四個其他音符。 其中三個額外音符位於鍵盤) 左端 (0號,1號在鍵盤) 右端 (8號。

    88鍵鋼琴佈局

  2. 我們會先在之前寫的環周圍加一個額外的迴圈,來建立七個完整重複。 將先前的迴圈 buildKey() 替換為以下程式碼:

    // 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;
    }
    

    在這個迴圈中,我們建立了暫存器 1 到 7 的鍵,並在每次進入下一個暫存器時遞增參考位置。

  3. 接著,讓我們製作剩下的鑰匙。 在函 createScene() 式中加入以下片段:

    // 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});
    

    請注意,鋼琴鍵盤最左邊的琴鍵和最右邊的琴鍵不符合 (中道具的 keyParams 尺寸,因為它們不在邊緣) 的黑鍵旁邊,因此我們需要為每個道具定義一個新的 props 物件來指定它們的特殊形狀。

  4. 修改後產生的鍵盤應該是這樣的樣子:

    完整鋼琴鍵盤網格

加裝鋼琴框架

  1. 畫面看起來有點奇怪,只有一個鍵盤漂浮在空間裡。 讓我們在鍵盤周圍加一個鋼琴框架,營造出立式鋼琴的外觀。

  2. 就像我們創建鍵一樣,我們也可以透過定位並組合一組方塊網格來建立框架。

    不過,我們會把這個挑戰留給你自己嘗試,並使用 BABYLON。SceneLoader.ImportMesh 來匯入一個立式鋼琴框架的預設網格。 將此程式碼附加於 createScene()

    // 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;
    });
    

    請注意,我們再次建立 TransformNode 一個父節點 piano ,將鍵盤與框架整體分組在一起。 如果需要,移動或調整整架鋼琴會變得容易許多。

  3. 當畫面匯入後,會注意到鍵盤位於畫面 (底部,因為按鍵的 y 座標預設) 為 0。 讓我們把鍵盤抬起來,讓它能放進站立鋼琴的框架裡:

    // Lift piano keys
    keyboard.position.y += 80;
    

    由於 keyboard 是所有鋼琴鍵的父鍵,我們只要改變 的 keyboardy 位置,就能提升所有鋼琴鍵。

  4. 最終的 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;
    }
    
  5. 現在我們應該有一台長這樣樣的立式鋼琴: 立式鋼琴網狀

後續步驟