当前位置: 首页 > 工具软件 > Babylon.js > 使用案例 >

Babylon.js 深入 - 第 2 章 - 声音(2)

越琦
2023-12-01

Babylon.js 深入 - 第 2 章 - 声音(2)

声音

Babylon.js 声音引擎基于 Web Audio 规范,要使用它,您需要使用与 Web Audio 兼容的浏览器。声音引擎提供环境、空间和定向声音。

创建空间 3D 声音

要将声音转换为空间 3D 声音,您需要通过设置以下选项来实现:

var music = new BABYLON.Sound("music", "music.wav", scene, null, {
    loop: true,
    autoplay: true,
    spatialSound: true
});

空间 3D 声音的默认属性是:

  • distanceModel - 衰减模型 - 默认为 linear (线性)方程,其他选项是 inverse (翻转) 、 exponential (指数)。
  • maxDistance - 最大距离 - 默认为 100,一旦听者距离声音超过 100 个单位,音量将为 0
  • panningModel - 平移模型 - 默认为 equalpower,等功率平移算法通常被认为是简单高效的,另一个可用选项是 HRTF,它是一种更高质量的空间化算法,使用卷积与来自人类受试者的测量脉冲响应。这种平移方法呈现立体声输出。这是使用耳机时的最佳算法。

maxDistance 仅在使用 linear (线性)衰减时使用。否则,您可以使用 rolloffFactorrefDistance 选项调整其他模型的衰减。默认情况下两者都设置为 1,但您可以更改它。

例如:

var music = new BABYLON.Sound("music", "music.wav", scene, null, {
    loop: true,
    autoplay: true,
    spatialSound: true,
    distanceModel: "exponential",
    rolloffFactor: 2
});

声音在 3D 世界中的默认位置是 (0,0,0)。要更改它,请使用 setPosition() 函数:

music.setPosition(new BABYLON.Vector3(100, 0, 0));

3D 空间音效的示例:

示例链接

示例代码:

<!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 createDefaultEngine = function () {
            return new BABYLON.Engine(canvas, true, {
                preserveDrawingBuffer: true,
                stencil: true,
                disableWebGL2Support: false
            });
        };
        var createScene = function () {
            var scene = new BABYLON.Scene(engine);
            var camera = new BABYLON.FreeCamera("FreeCamera", new BABYLON.Vector3(0, 5, 0), scene);
            camera.attachControl(canvas, true);
            var light0 = new BABYLON.DirectionalLight("Omni", new BABYLON.Vector3(0, -5, 2), scene);
            //Ground
            var ground = BABYLON.Mesh.CreatePlane("ground", 600.0, scene);
            ground.material = new BABYLON.StandardMaterial("groundMat", scene);
            ground.material.diffuseColor = new BABYLON.Color3(1, 1, 1);
            ground.material.backFaceCulling = false;
            ground.position = new BABYLON.Vector3(0, 0, 0);
            ground.rotation = new BABYLON.Vector3(Math.PI / 2, 0, 0);
            var sphereMat = new BABYLON.StandardMaterial("sphereMat", scene);
            sphereMat.diffuseColor = BABYLON.Color3.Purple();
            sphereMat.backFaceCulling = false;
            sphereMat.alpha = 0.3;
            var sphereMusic1 = BABYLON.Mesh.CreateSphere("musicsphere", 20, 50, scene);
            sphereMusic1.material = sphereMat;
            sphereMusic1.position = new BABYLON.Vector3(60, 0, 0);
            var sphereMusic2 = BABYLON.Mesh.CreateSphere("musicsphere", 20, 200, scene);
            sphereMusic2.material = sphereMat;
            sphereMusic2.position = new BABYLON.Vector3(-100, 0, 0);
            var sphereMusic3 = BABYLON.Mesh.CreateSphere("musicsphere", 20, 60, scene);
            sphereMusic3.material = sphereMat;
            sphereMusic3.position = new BABYLON.Vector3(0, 0, 100);
            var music1 = new BABYLON.Sound("Violons11", "./violons11.wav", scene,
                null, {
                    loop: true,
                    autoplay: true,
                    spatialSound: true,
                    maxDistance: 25
                });
            music1.setPosition(new BABYLON.Vector3(60, 0, 0));
            var music2 = new BABYLON.Sound("Violons18", "./violons18.wav", scene,
                null, {
                    loop: true,
                    autoplay: true,
                    spatialSound: true
                });
            music2.setPosition(new BABYLON.Vector3(-100, 0, 0));
            var music3 = new BABYLON.Sound("Cellolong", "./cellolong.wav", scene,
                null, {
                    loop: true,
                    autoplay: true,
                    spatialSound: true,
                    maxDistance: 30
                });
            music3.setPosition(new BABYLON.Vector3(0, 0, 100));
            // 为场景设置重力,类似于G力,在Y轴上
            scene.gravity = new BABYLON.Vector3(0, -0.9, 0);
            // 启用碰撞
            scene.collisionsEnabled = true;
            // 然后将碰撞和重力应用于活动摄影机
            camera.checkCollisions = true;
            camera.applyGravity = true;
            // 设置摄像机周围的椭球体
            camera.ellipsoid = new BABYLON.Vector3(1, 1, 1);
            // 最后,指定哪个网格是可碰撞的
            ground.checkCollisions = true;
            return scene;
        };
        window.initFunction = async function () {
            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();
            if (!engine) throw 'engine should not be null.';
            startRenderLoop(engine, canvas);
            window.scene = createScene();
        };
        initFunction().then(() => {
            sceneToRender = scene
        });
        // Resize
        window.addEventListener("resize", function () {
            engine.resize();
        });
    </script>
</body>

</html>

使用键盘和鼠标移动到场景中。每个声音都由一个紫色球体表示。当你进入一个球体时,你会开始听到音乐。声音在球体中心更大,离开球体时下降到 0

将声音附加到网格

这可能是在场景中处理 3D 声音的最简单方法。只需创建一个 BABYLON.Sound,将它附加到现有的网格上就可以了!如果网格在移动,声音也会随之移动。

这是要使用的代码:

var music = new BABYLON.Sound("Violons", "sounds/violons11.wav", scene, null, {
    loop: true,
    autoplay: true
});
// 声音现在将跟随盒子网格位置
music.attachToMesh(box);

对声音调用 attachToMesh() 函数会自动将其转换为空间 3D 声音。使用上面的代码,您将使用默认的 Babylon.js 值:maxDistance100 的线性衰减和 HRTF 类型的平移模型。

附加到网格的声音的示例:

示例链接

示例代码:

<!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 createDefaultEngine = function () {
            return new BABYLON.Engine(canvas, true, {
                preserveDrawingBuffer: true,
                stencil: true,
                disableWebGL2Support: false
            });
        };
        var createScene = function () {
            // 这将创建一个基本的巴比伦场景对象(非网格)
            var scene = new BABYLON.Scene(engine);
            // Lights
            var light0 = new BABYLON.DirectionalLight("Omni", new BABYLON.Vector3(-2, -5, 2), scene);
            var light1 = new BABYLON.PointLight("Omni", new BABYLON.Vector3(2, -5, -2), scene);
            // 需要一个自由的相机进行碰撞
            var camera = new BABYLON.FreeCamera("FreeCamera", new BABYLON.Vector3(0, -8, -20), scene);
            camera.attachControl(canvas, true);
            // Ground
            var ground = BABYLON.Mesh.CreatePlane("ground", 400.0, scene);
            ground.material = new BABYLON.StandardMaterial("groundMat", scene);
            ground.material.diffuseColor = new BABYLON.Color3(1, 1, 1);
            ground.material.backFaceCulling = false;
            ground.position = new BABYLON.Vector3(5, -10, -15);
            ground.rotation = new BABYLON.Vector3(Math.PI / 2, 0, 0);
            // 简单的箱子
            var box = BABYLON.Mesh.CreateBox("crate", 2, scene);
            box.material = new BABYLON.StandardMaterial("Mat", scene);
            box.material.diffuseTexture = new BABYLON.Texture("./2/textures/crate.png", scene);
            box.position = new BABYLON.Vector3(10, -9, 0);
            // 创建和加载声音异步
            var music = new BABYLON.Sound("Violons", "./violons11.wav", scene, function () {
                // 声音已准备好播放(加载和解码)
                // TODO: 添加你的逻辑
                console.log("Sound ready to be played!");
            }, {
                loop: true,
                autoplay: true
            });
            // 声音现在将跟随网格位置
            music.attachToMesh(box);
            // 为场景设置重力(G 力,在 Y 轴上)
            scene.gravity = new BABYLON.Vector3(0, -0.9, 0);
            // 启用碰撞
            scene.collisionsEnabled = true;
            // 然后将碰撞和重力应用到活动相机
            camera.checkCollisions = true;
            camera.applyGravity = true;
            // 设置相机周围的椭球体
            camera.ellipsoid = new BABYLON.Vector3(1, 1, 1);
            // 最后,指定哪个网格是可碰撞的
            ground.checkCollisions = true;
            var alpha = 0;
            scene.registerBeforeRender(function () {
                // 移动音箱会自动移动附加在其上的相关声音
                box.position = new BABYLON.Vector3(Math.cos(alpha) * 30, -9, Math.sin(alpha) * 30);
                alpha += 0.01;
            });
            return scene;
        };
        window.initFunction = async function () {
            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();
            if (!engine) throw 'engine should not be null.';
            startRenderLoop(engine, canvas);
            window.scene = createScene();
        };
        initFunction().then(() => {
            sceneToRender = scene
        });
        // Resize
        window.addEventListener("resize", function () {
            engine.resize();
        });
    </script>
</body>

</html>

将位置设置为音频侦听器

默认情况下,场景的耳朵或听者始终是当前活动的摄像机。有时,例如:在制作第三人称游戏时,您可能需要设置另一个网格作为听者,例如:角色头部。这可以通过设置场景的 audioListenerPositionProvider 属性来实现。

您创建的方法必须返回一个有效的 Vector3 对象:

// 返回静态位置
scene.audioListenerPositionProvider = () => {
    return new BABYLON.Vector3(0, 0, 10);
};

// 返回网格的当前位置
// 建议使用 absolutePosition 属性
// 反映网格在世界中的位置
scene.audioListenerPositionProvider = () => {
    // 返回静态位置
    return myMesh.absolutePosition;
};

要切换回使用您的相机作为侦听器,只需将该属性设置为 null

创建空间定向 3D 声音

默认情况下,空间声音是全向的。但是,如果您愿意,您可以使用定向声音。

注意:定向声音仅适用于附加到网格的空间声音。

这是要使用的代码:

var music = new BABYLON.Sound("Violons", "violons11.wav", scene, null, {
    loop: true,
    autoplay: true
});
music.setDirectionalCone(90, 180, 0);
music.setLocalDirectionToMesh(new BABYLON.Vector3(1, 0, 0));
music.attachToMesh(box);

setDirectionalCone 有 3 个参数:

  • coneInnerAngle - 内锥的大小,以度为单位。
  • coneOuterAngle - 外锥的大小,以度为单位。
  • coneOuterGain - 当您在外锥体之外时的音量,介于 0.01.0 之间。

锥体的外角必须大于或等于内角,否则将记录错误并且定向声音将不起作用。

setLocalDirectionToMesh() 只是与您所附加的网格相关的圆锥体的方向。默认,它是 (1,0,0)

空间定向声音的示例:

示例链接

示例代码:

<!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 createDefaultEngine = function () {
            return new BABYLON.Engine(canvas, true, {
                preserveDrawingBuffer: true,
                stencil: true,
                disableWebGL2Support: false
            });
        };
        var createScene = function () {
            var scene = new BABYLON.Scene(engine);
            // Lights
            var light0 = new BABYLON.DirectionalLight("Omni", new BABYLON.Vector3(-2, -5, 2), scene);
            var light1 = new BABYLON.PointLight("Omni", new BABYLON.Vector3(2, -5, -2), scene);
            // 需要一个自由的相机进行碰撞
            var camera = new BABYLON.FreeCamera("FreeCamera", new BABYLON.Vector3(0, -8, -20), scene);
            camera.attachControl(canvas, true);
            // Ground
            var ground = BABYLON.Mesh.CreatePlane("ground", 400.0, scene);
            ground.material = new BABYLON.StandardMaterial("groundMat", scene);
            ground.material.diffuseColor = new BABYLON.Color3(1, 1, 1);
            ground.material.backFaceCulling = false;
            ground.position = new BABYLON.Vector3(5, -10, -15);
            ground.rotation = new BABYLON.Vector3(Math.PI / 2, 0, 0);
            // 简单的箱子
            var box = BABYLON.Mesh.CreateBox("crate", 2, scene);
            box.material = new BABYLON.StandardMaterial("Mat", scene);
            box.material.diffuseTexture = new BABYLON.Texture("./2/textures/crate.png", scene);
            box.position = new BABYLON.Vector3(10, -9, 0);
            var cylinder = BABYLON.Mesh.CreateCylinder("cylinder", box.scaling.y, 0, box.scaling.x, 15, 1, scene,
                false);
            cylinder.parent = box;
            cylinder.position = new BABYLON.Vector3(box.scaling.x, 0, 0);
            cylinder.rotation.y = Math.PI;
            cylinder.rotation.z = -Math.PI / 2;
            var violons11 = new BABYLON.Sound("Violons", "./violons11.wav", scene, null, {
                loop: true,
                autoplay: true
            });
            violons11.setDirectionalCone(90, 180, 0);
            violons11.setLocalDirectionToMesh(new BABYLON.Vector3(1, 0, 0));
            violons11.attachToMesh(box);
            // 为场景设置重力(G 力,在 Y 轴上)
            scene.gravity = new BABYLON.Vector3(0, -0.9, 0);
            // 启用碰撞
            scene.collisionsEnabled = true;
            // 然后将碰撞和重力应用到活动相机
            camera.checkCollisions = true;
            camera.applyGravity = true;
            // 设置相机周围的椭球体
            camera.ellipsoid = new BABYLON.Vector3(1, 1, 1);
            // 最后,指定哪个网格是可碰撞的
            ground.checkCollisions = true;
            return scene;
        };
        window.initFunction = async function () {
            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();
            if (!engine) throw 'engine should not be null.';
            startRenderLoop(engine, canvas);
            window.scene = createScene();
        };
        initFunction().then(() => {
            sceneToRender = scene
        });
        // Resize
        window.addEventListener("resize", function () {
            engine.resize();
        });
    </script>
</body>

</html>

进入 3D 场景。如果您在由灰色锥体定义的空间内,您应该听到音乐,否则您将听不到它,因为 coneOuterGain 设置为 0

创建自己的自定义衰减函数

如果您想使用特定算法管理衰减或 Web Audio 中的距离模型,您可以使用 Babylon.js 自定义衰减函数绕过原生 Web Audio 衰减。

注意:网络音频是硬件加速的。这意味着它主要由设备上的专用音频芯片通过本机浏览器处理。这在 3D 实时渲染的性能方面几乎没有任何成本。切换到自定义衰减将使用基于 JavaScriptBabylon.js 距离计算,速度会更慢。

此外,自定义衰减仅适用于空间声音,但也适用于连接到 Babylon.js 网格的声音。让我们查看执行此操作的代码。首先,您必须在选项中指定它:

// 创建和加载声音异步
var music = new BABYLON.Sound("Music", "music.wav", scene, null, {
    loop: true,
    autoplay: true,
    useCustomAttenuation: true
});

您将切换到内部 Babylon.js 数学计算。默认的自定义衰减函数是线性的。

要创建自己的逻辑,您需要这样的代码:

// 创建自定义衰减函数。 在物体附近,体积几乎为 0。
// 距离越远,声音越大
music.setAttenuationFunction(function (
    currentVolume,
    currentDistance,
    maxDistance,
    refDistance,
    rolloffFactor
) {
    return (currentVolume * currentDistance) / maxDistance;
});

您可以随心所欲地使用这 5 个参数。只需返回一个数字,该数字将是应用于声音的音量。

自定义衰减函数的示例:

示例链接

示例代码:

<!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 createDefaultEngine = function () {
            return new BABYLON.Engine(canvas, true, {
                preserveDrawingBuffer: true,
                stencil: true,
                disableWebGL2Support: false
            });
        };
        var createScene = function () {
            var scene = new BABYLON.Scene(engine);
            var camera = new BABYLON.FreeCamera("FreeCamera", new BABYLON.Vector3(0, 5, 0), scene);
            camera.attachControl(canvas, true);
            var light0 = new BABYLON.DirectionalLight("Omni", new BABYLON.Vector3(0, -5, 2), scene);
            // Ground
            var ground = BABYLON.Mesh.CreatePlane("ground", 600.0, scene);
            ground.material = new BABYLON.StandardMaterial("groundMat", scene);
            ground.material.diffuseColor = new BABYLON.Color3(1, 1, 1);
            ground.material.backFaceCulling = false;
            ground.position = new BABYLON.Vector3(0, 0, 0);
            ground.rotation = new BABYLON.Vector3(Math.PI / 2, 0, 0);
            var sphereMat = new BABYLON.StandardMaterial("sphereMat", scene);
            sphereMat.diffuseColor = BABYLON.Color3.Purple();
            sphereMat.backFaceCulling = false;
            sphereMat.alpha = 0.3;
            var sphereMusic1 = BABYLON.Mesh.CreateSphere("musicsphere", 20, 50, scene);
            sphereMusic1.material = sphereMat;
            sphereMusic1.position = new BABYLON.Vector3(60, 0, 0);
            var sphereMusic2 = BABYLON.Mesh.CreateSphere("musicsphere", 20, 200, scene);
            sphereMusic2.material = sphereMat;
            sphereMusic2.position = new BABYLON.Vector3(-100, 0, 0);
            var sphereMusic3 = BABYLON.Mesh.CreateSphere("musicsphere", 20, 60, scene);
            sphereMusic3.material = sphereMat;
            sphereMusic3.position = new BABYLON.Vector3(0, 0, 100);
            var music1 = new BABYLON.Sound("Violons11", "./violons11.wav", scene,
                null, {
                    loop: true,
                    autoplay: true,
                    maxDistance: 25,
                    useCustomAttenuation: true
                });
            music1.attachToMesh(sphereMusic1);
            var music2 = new BABYLON.Sound("Violons18", "./violons18.wav", scene,
                null, {
                    loop: true,
                    autoplay: true,
                    useCustomAttenuation: true
                });
            music2.attachToMesh(sphereMusic2);
            var music3 = new BABYLON.Sound("Cellolong", "./cellolong.wav", scene,
                null, {
                    loop: true,
                    autoplay: true,
                    maxDistance: 30,
                    useCustomAttenuation: true
                });
            music3.attachToMesh(sphereMusic3);
            // 为场景设置重力(G 力,在 Y 轴上)
            scene.gravity = new BABYLON.Vector3(0, -0.9, 0);
            // 启用碰撞
            scene.collisionsEnabled = true;
            // 然后将碰撞和重力应用到活动相机
            camera.checkCollisions = true;
            camera.applyGravity = true;
            // 设置相机周围的椭球体
            camera.ellipsoid = new BABYLON.Vector3(1, 1, 1);
            // 最后,指定哪个网格是可碰撞的
            ground.checkCollisions = true;
            return scene;
        };
        window.initFunction = async function () {
            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();
            if (!engine) throw 'engine should not be null.';
            startRenderLoop(engine, canvas);
            window.scene = createScene();
        };
        initFunction().then(() => {
            sceneToRender = scene
        });
        // Resize
        window.addEventListener("resize", function () {
            engine.resize();
        });
    </script>
</body>

</html>

处理从 .babylon 文件加载的声音

目前只有我们的 3DS Max 导出器可以将声音直接导出到 .babylon

要访问由 Babylon.js.babylon 文件加载器加载的声音,您需要在场景对象上使用 getSoundByName() 函数。

这是一个加载嵌入声音的 .babylon 场景文件的简单示例:

var canvas = document.getElementById("renderCanvas");
var engine = new BABYLON.Engine(canvas, true);
BABYLON.SceneLoader.Load(
    "TestScene/",
    "testsound.babylon",
    engine,
    function (newScene) {
        newScene.executeWhenReady(function () {
            newScene.activeCamera.attachControl(canvas);
            var gunshotSound = newScene.getSoundByName("gunshot-1.wav");
            window.addEventListener("keydown", function (evt) {
                if (evt.keyCode === 32 && gunshotSound) {
                    gunshotSound.play();
                }
            });
            engine.runRenderLoop(function () {
                newScene.render();
            });
        });
    },
    function (progress) {
        // 待办事项:向用户提供进度反馈
    }
);

按空格键将播放枪声。

使用音轨

将您的音乐和声音隔离在多个轨道上来更好地管理声音分组实例的音量是很有用的。它还将在未来的版本中在特定轨道上发挥效果。

默认情况下,Babylon.js 正在创建一个 BABYLON.SoundTrack 对象作为其主轨道。每次您创建一个新的 BABYLON.Sound 时,它都会为您添加到这个主音轨中。

var soundTrack = new BABYLON.SoundTrack(scene);
soundTrack.AddSound(cellolong);
soundTrack.AddSound(violons11);

使用此代码,cellolongviolons11 声音将从 Babylon.js 主轨道移动到此特定音轨。现在这意味着您可以独立于主音轨改变该音轨的音量,从而改变这 2 种声音的音量。

AddSound() 函数会将声音从其原始容器(主音轨或特定音轨)移动到指定的新音轨。例如,使用以下代码:

var soundTrack1 = new BABYLON.SoundTrack(scene);
soundTrack1.AddSound(cellolong);
soundTrack1.AddSound(violons11);
var soundTrack2 = new BABYLON.SoundTrack(scene);
soundTrack2.AddSound(violons11);

violons11 声音最终将只存在于 soundTrack2 中。

使用分析器

您可以轻松地实时分析音频频率。

最容易理解其工作原理的代码是:

var myAnalyser = new BABYLON.Analyser(scene);
BABYLON.Engine.audioEngine.connectToAnalyser(myAnalyser);
myAnalyser.drawDebugCanvas();

这将连接到音频引擎的全局音量,并将一起播放的所有声音的频率绘制到屏幕顶部的 2D 画布中显示。

您可以更改调试画布的位置和大小,并在音轨上使用分析器而不是全局音频引擎:

var myAnalyser = new BABYLON.Analyser(scene);
soundTrack1.connectToAnalyser(myAnalyser);
myAnalyser.DEBUGCANVASSIZE.width = 160;
myAnalyser.DEBUGCANVASSIZE.height = 100;
myAnalyser.DEBUGCANVASPOS.x = 40;
myAnalyser.DEBUGCANVASPOS.y = 30;
myAnalyser.drawDebugCanvas();

你也可以调用自己的分析器函数来创建你自己的用法。

完整的音频示例的示例:

示例链接

示例代码:

<!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 createDefaultEngine = function () {
            return new BABYLON.Engine(canvas, true, {
                preserveDrawingBuffer: true,
                stencil: true,
                disableWebGL2Support: false
            });
        };
        var createScene = function () {
            var scene = new BABYLON.Scene(engine);
            // Lights
            var light0 = new BABYLON.DirectionalLight("Omni", new BABYLON.Vector3(-2, -5, 2), scene);
            var light1 = new BABYLON.PointLight("Omni", new BABYLON.Vector3(2, -5, -2), scene);
            // Need a free camera for collisions
            var camera = new BABYLON.FreeCamera("FreeCamera", new BABYLON.Vector3(15, -8, -40), scene);
            camera.attachControl(canvas, true);
            // Ground
            var ground = BABYLON.Mesh.CreatePlane("ground", 400.0, scene);
            ground.material = new BABYLON.StandardMaterial("groundMat", scene);
            ground.material.diffuseColor = new BABYLON.Color3(1, 1, 1);
            ground.material.backFaceCulling = false;
            ground.position = new BABYLON.Vector3(5, -10, -15);
            ground.rotation = new BABYLON.Vector3(Math.PI / 2, 0, 0);
            // 创建和加载声音异步
            var music = new BABYLON.Sound("Violons", "./violons18.wav", scene, null, {
                loop: true,
                autoplay: true
            });
            var myAnalyser = new BABYLON.Analyser(scene);
            BABYLON.Engine.audioEngine.connectToAnalyser(myAnalyser);
            myAnalyser.FFT_SIZE = 32;
            myAnalyser.SMOOTHING = 0.9;
            var spatialBoxArray = [];
            var spatialBox;
            var color;
            for (var index = 0; index < myAnalyser.FFT_SIZE / 2; index++) {
                spatialBox = BABYLON.Mesh.CreateBox("sb" + index, 2, scene);
                spatialBox.position = new BABYLON.Vector3(index * 2, 0, 0);
                spatialBox.material = new BABYLON.StandardMaterial("sbm" + index, scene);
                color = hsvToRgb(index / (myAnalyser.FFT_SIZE) / 2 * 360, 100, 50),
                    spatialBox.material.diffuseColor = new BABYLON.Color3(color.r, color.g, color.b);
                spatialBoxArray.push(spatialBox);
            }
            scene.registerBeforeRender(function () {
                var workingArray = myAnalyser.getByteFrequencyData();

                for (var i = 0; i < myAnalyser.getFrequencyBinCount(); i++) {
                    spatialBoxArray[i].scaling.y = workingArray[i] / 32;
                }
            });
            // 为场景设置重力(G 力,在 Y 轴上)
            scene.gravity = new BABYLON.Vector3(0, -0.9, 0);
            // 启用碰撞
            scene.collisionsEnabled = true;
            // 然后将碰撞和重力应用到活动相机
            camera.checkCollisions = true;
            camera.applyGravity = true;
            // 设置相机周围的椭球体
            camera.ellipsoid = new BABYLON.Vector3(1, 1, 1);
            // 最后,指定哪个网格是可碰撞的
            ground.checkCollisions = true;

            function hsvToRgb(h, s, v) {
                var r, g, b;
                var i;
                var f, p, q, t;
                h = Math.max(0, Math.min(360, h));
                s = Math.max(0, Math.min(100, s));
                v = Math.max(0, Math.min(100, v));
                s /= 100;
                v /= 100;
                if (s == 0) {
                    r = g = b = v;
                    return [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255)];
                }
                // sector 0 to 5
                h /= 60;
                i = Math.floor(h);
                // factorial part of h
                f = h - i;
                p = v * (1 - s);
                q = v * (1 - s * f);
                t = v * (1 - s * (1 - f));
                switch (i) {
                    case 0:
                        r = v;
                        g = t;
                        b = p;
                        break;
                    case 1:
                        r = q;
                        g = v;
                        b = p;
                        break;
                    case 2:
                        r = p;
                        g = v;
                        b = t;
                        break;
                    case 3:
                        r = p;
                        g = q;
                        b = v;
                        break;
                    case 4:
                        r = t;
                        g = p;
                        b = v;
                        break;
                        // case 5:
                    default:
                        r = v;
                        g = p;
                        b = q;
                }
                return {
                    r: r,
                    g: g,
                    b: b
                };
            }
            return scene;
        };
        window.initFunction = async function () {
            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();
            if (!engine) throw 'engine should not be null.';
            startRenderLoop(engine, canvas);
            window.scene = createScene();
        };
        initFunction().then(() => {
            sceneToRender = scene
        });
        // Resize
        window.addEventListener("resize", function () {
            engine.resize();
        });
    </script>
</body>

</html>
 类似资料: