Skip to content

音频组件

引擎通过封装 Web Audio API,提供用户加载播放音频的基础能力:

  • AudioListener - 虚拟音频接收组件,需要与下列音源配合使用
  • StaticAudio - 与空间位置无关的音源,播放效果与 Listener 无关
  • PositionAudio - 基于空间位置的音源,播放效果会随着 Listener 的相对位置改变

静态音频

播放与空间位置无关的音频,如全局背景音乐,音效等

ts
import {AudioListener, StaticAudio} from '@orillusion/media-extention'

// 音频接收组件,静态音频可以添加给任意对象,这里用 scene 为例
let listener = scene.addComponent(AudioListener)

// 创建静态音源
let audioObj = new Object3D()
let staticAudio = audioObj.addComponent(StaticAudio)
// 设置收听对象
staticAudio.setLisenter(listener)
// 加载音频
await staticAudio.load('https://cdn.orillusion.com/audio.ogg')
// 播放音频,声音效果与空间位置无关
staticAudio.play()

WebGPU is not supported in your browser
Please upgrade to latest Chrome/Edge

<
ts
import { BoxGeometry, Camera3D, DirectLight, Engine3D, LitMaterial, KelvinUtil, MeshRenderer, Object3D, Scene3D, Vector3, Color, OrbitController, View3D, AtmosphericComponent } from '@orillusion/core';
import { StaticAudio, AudioListener } from '@orillusion/media-extention';
import * as dat from 'dat.gui';

class Static_Audio {
    lightObj: Object3D;
    scene: Scene3D;
    camera: Object3D;
    mats: any[];
    audio: StaticAudio;
    constructor() {}

    async run() {
        Engine3D.setting.shadow.autoUpdate = true;
        Engine3D.setting.shadow.updateFrameRate = 1;
        Engine3D.setting.shadow.type = 'HARD';
        Engine3D.setting.shadow.shadowSize = 2048;
        Engine3D.setting.shadow.shadowBound = 200;
        Engine3D.setting.shadow.shadowBias = 0.002;

        await Engine3D.init();
        this.scene = new Scene3D();
        this.scene.addComponent(AtmosphericComponent);

        this.camera = new Object3D();
        this.camera.localPosition = new Vector3(0, 20, 50);
        let mainCamera = this.camera.addComponent(Camera3D);
        this.scene.addChild(this.camera);

        mainCamera.perspective(60, Engine3D.aspect, 0.1, 20000.0);
        let orbit = this.camera.addComponent(OrbitController);
        orbit.target = new Vector3(0, 4, 0);
        orbit.minDistance = 10;
        orbit.maxDistance = 200;

        let view = new View3D();
        view.scene = this.scene;
        view.camera = mainCamera;

        Engine3D.startRenderView(view);
        await this.initScene();
    }

    async initScene() {
        {
            let wall = new Object3D();
            let mr = wall.addComponent(MeshRenderer);
            mr.geometry = new BoxGeometry(40, 30, 1);
            let mat = new LitMaterial();
            mat.baseColor = new Color(1, 0, 0);
            mr.material = mat;
            this.scene.addChild(wall);
            wall.z = -5;
        }
        {
            let floor = new Object3D();
            let mr = floor.addComponent(MeshRenderer);
            mr.geometry = new BoxGeometry(3000, 1, 3000);
            let mat = new LitMaterial();
            mr.material = mat;
            this.scene.addChild(floor);
        }

        /******** light *******/
        {
            this.lightObj = new Object3D();
            this.lightObj.rotationX = 35;
            this.lightObj.rotationY = 110;
            this.lightObj.rotationZ = 0;
            let directLight = this.lightObj.addComponent(DirectLight);
            directLight.lightColor = KelvinUtil.color_temperature_to_rgb(5355);
            directLight.castShadow = true;
            directLight.intensity = 3;
            this.scene.addChild(this.lightObj);
        }
        {
            let group = new Object3D();
            let speaker = await Engine3D.res.loadGltf('https://cdn.orillusion.com/gltfs/speaker/scene.gltf');
            speaker.localScale.set(4, 4, 4);
            speaker.rotationX = -120;
            //speaker.y = 1.5
            group.addChild(speaker);
            group.y = 2;
            this.scene.addChild(group);

            let listener = this.camera.addComponent(AudioListener);
            let audio = group.addComponent(StaticAudio);
            audio.setLisenter(listener);

            await audio.load('https://cdn.orillusion.com/audio.ogg');

            let buttons = {
                play: () => {
                    audio.play();
                },
                pause: () => {
                    audio.pause();
                },
                stop: () => {
                    audio.stop();
                },
                volume: 1
            };
            let gui = new dat.GUI();
            gui.addFolder('Orillusion');
            gui.add(buttons, 'play');
            gui.add(buttons, 'pause');
            gui.add(buttons, 'stop');
            gui.add(buttons, 'volume', 0, 1, 0.01).onChange((v) => {
                audio.setVolume(v);
            });
        }
    }
}

new Static_Audio().run();

3D空间音频

基于 PannerNode 的3D空间位置的音频播放,声音的方位和大小与接收者和音源的相对位置有关。

ts
import {AudioListener, PositionAudio} from '@orillusion/media-extention'

let movingObj = new Object3D()
// 添加音频接收组件,一般是动态移动的物体,比如添加给 camera 来模拟用户空间位置
let listener = movingObj.addComponent(AudioListener)

// 创建空间音源
let audioObj = new Object3D()
let positionAudio = audioObj.addComponent(PositionAudio)
// 设置音源空间参数, 详见 PannerNode API
positionAudio.refDistance = 10;
positionAudio.maxDistance = 100;
positionAudio.setDirectionalCone( 180, 230, 0.1 ); // coneInnerAngle, coneOuterAngle, coneOuterGain
...
// 显示音频的空间范围
positionAudio.showHelper()

// 设置收听对象
positionAudio.setLisenter(listener)
// 加载音频
await positionAudio.load('https://cdn.orillusion.com/audio.ogg')

// 播放音频,声音方位和大小会随着 movingObj 和 audioObj 的相对位置而变化
positionAudio.play()

WebGPU is not supported in your browser
Please upgrade to latest Chrome/Edge

<
ts
import { BoxGeometry, Camera3D, DirectLight, Engine3D, LitMaterial, KelvinUtil, MeshRenderer, Object3D, Scene3D, Vector3, Color, OrbitController, View3D, AtmosphericComponent } from '@orillusion/core';
import { PositionAudio, AudioListener } from '@orillusion/media-extention';
import * as dat from 'dat.gui';

class Position_Audio {
    lightObj: Object3D;
    scene: Scene3D;
    camera: Object3D;
    mats: any[];
    audio: PositionAudio;
    private a = 40;
    private b = 80;
    private angle = 0;
    constructor() {}

    async run() {
        Engine3D.setting.shadow.autoUpdate = true;
        Engine3D.setting.shadow.updateFrameRate = 1;
        Engine3D.setting.shadow.type = 'HARD';
        Engine3D.setting.shadow.shadowSize = 2048;
        Engine3D.setting.shadow.shadowBound = 250;
        Engine3D.setting.shadow.shadowBias = 0.002;

        await Engine3D.init({
            renderLoop: this.loop.bind(this)
        });
        this.scene = new Scene3D();
        this.scene.addComponent(AtmosphericComponent);

        this.camera = new Object3D();
        this.camera.localPosition = new Vector3(0, 20, 50);
        let mainCamera = this.camera.addComponent(Camera3D);
        this.scene.addChild(this.camera);

        mainCamera.perspective(60, Engine3D.aspect, 0.1, 20000.0);
        let orbit = this.camera.addComponent(OrbitController);
        orbit.target = new Vector3(0, 4, 0);
        orbit.minDistance = 10;
        orbit.maxDistance = 200;

        let view = new View3D();
        view.scene = this.scene;
        view.camera = mainCamera;

        Engine3D.startRenderView(view);
        await this.initScene();
    }

    async initScene() {
        {
            let wall = new Object3D();
            let mr = wall.addComponent(MeshRenderer);
            mr.geometry = new BoxGeometry(40, 30, 1);
            let mat = new LitMaterial();
            mat.baseColor = new Color(1, 0, 0);
            mr.material = mat;
            this.scene.addChild(wall);
            wall.z = -5;
        }
        {
            let floor = new Object3D();
            let mr = floor.addComponent(MeshRenderer);
            mr.geometry = new BoxGeometry(3000, 1, 3000);
            let mat = new LitMaterial();
            mr.material = mat;
            this.scene.addChild(floor);
        }

        /******** light *******/
        {
            this.lightObj = new Object3D();
            this.lightObj.rotationX = 35;
            this.lightObj.rotationY = 110;
            this.lightObj.rotationZ = 0;
            let directLight = this.lightObj.addComponent(DirectLight);
            directLight.lightColor = KelvinUtil.color_temperature_to_rgb(5355);
            directLight.castShadow = true;
            directLight.intensity = 3;
            this.scene.addChild(this.lightObj);
        }
        {
            let [speaker, man, music] = await Promise.all([Engine3D.res.loadGltf('https://cdn.orillusion.com/gltfs/speaker/scene.gltf'), Engine3D.res.loadGltf('https://cdn.orillusion.com/gltfs/glb/CesiumMan.glb'), fetch('https://cdn.orillusion.com/audio.ogg').then((res) => res.arrayBuffer())]);
            speaker.localScale.set(4, 4, 4);
            speaker.rotationX = -120;
            speaker.y = 0.5;
            let group = new Object3D();
            group.addChild(speaker);
            group.y = 2;
            this.scene.addChild(group);

            man.name = 'man';
            man.scaleX = 10;
            man.scaleY = 10;
            man.scaleZ = 10;
            man.rotationX = -90;
            man.rotationY = -90;
            man.localPosition.set(0, 0.5, 30);
            this.scene.addChild(man);

            let listener = man.addComponent(AudioListener);
            let audio = group.addComponent(PositionAudio);
            audio.setLisenter(listener);
            await audio.loadBuffer(music);
            audio.refDistance = 10;
            audio.maxDistance = 100;
            audio.setDirectionalCone(180, 230, 0.1);
            audio.showHelper();

            let buttons = {
                play: () => {
                    audio.play();
                },
                pause: () => {
                    audio.pause();
                },
                stop: () => {
                    audio.stop();
                },
                volume: 1,
                'Toggle Helper': () => {
                    audio.toggleHelper();
                }
            };
            let gui = new dat.GUI();
            gui.addFolder('Orillusion');
            gui.add(buttons, 'play');
            gui.add(buttons, 'pause');
            gui.add(buttons, 'stop');
            gui.add(buttons, 'volume', 0, 1, 0.01).onChange((v) => {
                audio.setVolume(v);
            });
            gui.add(buttons, 'Toggle Helper');
        }
    }
    loop() {
        let man = this.scene.getChildByName('man') as Object3D;
        if (man) {
            this.angle += 0.005;
            man.x = this.a * Math.cos(this.angle);
            man.z = this.b * Math.sin(this.angle) + 30;
            man.rotationY -= (0.005 * 180) / Math.PI;
        }
    }
}

new Position_Audio().run();