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.shadowBound = 100;

        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 = 30;
            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.shadowBound = 100;

        await Engine3D.init({
            renderLoop: this.loop.bind(this)
        });
        console.log(1)
        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 = 30;
            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()