音频组件
引擎通过封装 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()
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()
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();