JSAR 3D黑白钢琴开发实战:用代码演奏音乐的艺术

86 阅读5分钟

项目概览

这是一个基于 JSAR 的交互式 3D 黑白钢琴应用,用户可以通过点击琴键播放音符。项目展示了 JSAR 在音频处理、3D 建模和交互设计方面的综合能力。

核心特性:

  • 完整的黑白琴键布局(C4-C5,13个音符)
  • 基于 Web Audio API 的真实音频合成
  • 点击交互与视觉反馈
  • 优雅的材质与光照效果
  • 科学的琴键比例设计

JSAR 项目结构

JSAR 项目采用简洁的文件结构,类似于 npm package:

JSAR3/
├── package.json          # 项目配置文件
├── main.xsml            # 场景描述文件
├── lib/
│   └── piano.ts         # 主逻辑代码
└── icon.png             # 项目图标

package.json 配置

{"name": "jsar-piano","displayName": "3D黑白钢琴","version": "1.0.0","description": "JSAR 3D黑白钢琴 - 可点击演奏的虚拟钢琴","main": "main.xsml","files": ["main.xsml", "lib/*.ts", "icon.png"]}

这种标准化的配置方式让项目管理变得非常简单。

main.xsml 场景文件

XSML 是 JSAR 的场景描述语言,语法类似 HTML:

<xsml version="1.0"><head><title>🎹 3D黑白钢琴 - JSAR Widget</title><script src="./lib/piano.ts"></script></head><space></space></xsml>

简洁明了的声明式语法,<space> 元素代表 AR 空间场景。


核心开发:利用 JSAR 的 3D 能力

① 初始化场景

JSAR 通过 spatialDocument 全局对象提供场景访问能力:

const scene = spatialDocument.scene as BABYLON.Scene;

// 设置天空背景
scene.clearColor = new BABYLON.Color4(0.4, 0.5, 0.9, 1);

// 双光源系统const ambientLight = new BABYLON.HemisphericLight('ambientLight',
  new BABYLON.Vector3(0, 1, 0), scene);
ambientLight.intensity = 0.7;

const directionalLight = new BABYLON.DirectionalLight('directionalLight',
  new BABYLON.Vector3(-1, -2, -1), scene);
directionalLight.intensity = 0.5;

JSAR 内置了 Babylon.js 引擎,可以直接使用其强大的 3D API。

image.png

② 相机控制

let camera = scene.activeCamera;
if (!camera) {
  camera = new BABYLON.ArcRotateCamera(
    'mainCamera',
    Math.PI / 2,      // 水平角度Math.PI / 3,      // 垂直角度100,              // 距离(适配放大后的钢琴)BABYLON.Vector3.Zero(),
    scene
  );
  camera.attachControl(true);
  scene.activeCamera = camera;
} else {
  // 如果已有相机,调整其位置if (camera instanceof BABYLON.ArcRotateCamera) {
    camera.radius = 100;
    camera.target = BABYLON.Vector3.Zero();
  }
}

JSAR 自动管理相机,我们只需要设置位置和目标点即可。

③ 构建舞台

// 深紫色舞台地面const ground = BABYLON.MeshBuilder.CreateGround('ground',
  { width: 100, height: 100 }, scene);
const groundMat = new BABYLON.StandardMaterial('groundMat', scene);
groundMat.diffuseColor = new BABYLON.Color3(0.2, 0.15, 0.3);
groundMat.specularColor = new BABYLON.Color3(0.1, 0.1, 0.1);
ground.material = groundMat;

通过 Babylon.js 的 MeshBuilder,创建几何体非常简单直观。

image.png


④ 钢琴建模策略

钢琴是一个精密的 3D 模型,我们将其拆解为两大组件:

音符配置系统

首先定义钢琴的音符配置,包含频率、类型等信息:

interface KeyConfig {
  note: string;      // 音符名称frequency: number; // 音高频率isBlack: boolean;  // 是否为黑键keyIndex: number;  // 索引name: string;      // 中文名称
}

const keyConfigs: KeyConfig[] = [
  { note: 'C4', frequency: 261.63, isBlack: false, keyIndex: 0, name: 'Do' },
  { note: 'C#4', frequency: 277.18, isBlack: true, keyIndex: 1, name: 'C#' },
  { note: 'D4', frequency: 293.66, isBlack: false, keyIndex: 2, name: 'Re' },
  // ... 共13个音符
];

这种数据驱动的设计让音符管理变得非常清晰。

白键建模

白键是钢琴的主体结构:

// 放大琴键尺寸 (3.9倍)const whiteKeyWidth = 7.8;
const whiteKeyHeight = 1.95;
const whiteKeyDepth = 39;

// 创建白键const whiteKey = BABYLON.MeshBuilder.CreateBox(
  `key_${config.note}`,
  {
    width: whiteKeyWidth,
    height: whiteKeyHeight,
    depth: whiteKeyDepth
  },
  scene
);

// 白色材质 - 模拟真实钢琴琴键const whiteMaterial = new BABYLON.StandardMaterial(`mat_${config.note}`, scene);
whiteMaterial.diffuseColor = new BABYLON.Color3(0.95, 0.95, 0.95);  // 漫反射
whiteMaterial.specularColor = new BABYLON.Color3(0.5, 0.5, 0.5);     // 镜面反射
whiteKey.material = whiteMaterial;

// 位置设置
whiteKey.position.x = whiteKeyPosition;
whiteKey.position.y = whiteKeyHeight / 2;
whiteKey.position.z = 0;

image.png

黑键建模

黑键比白键更窄、更高,位于白键之间:

const blackKeyWidth = 4.68;
const blackKeyHeight = 3.12;
const blackKeyDepth = 23.4;

// 创建黑键const blackKey = BABYLON.MeshBuilder.CreateBox(
  `key_${config.note}`,
  {
    width: blackKeyWidth,
    height: blackKeyHeight,
    depth: blackKeyDepth
  },
  scene
);

// 黑色材质const blackMaterial = new BABYLON.StandardMaterial(`mat_${config.note}`, scene);
blackMaterial.diffuseColor = new BABYLON.Color3(0.1, 0.1, 0.1);
blackMaterial.specularColor = new BABYLON.Color3(0.2, 0.2, 0.2);
blackKey.material = blackMaterial;

// 黑键位置(在两个白键之间偏后)const previousWhiteKeyPos = whiteKeyPosition - whiteKeyWidth;
blackKey.position.x = previousWhiteKeyPos + whiteKeyWidth / 2;
blackKey.position.y = blackKeyHeight / 2;
blackKey.position.z = -whiteKeyDepth / 2 + blackKeyDepth / 2 + 4;

元数据系统

为每个琴键附加元数据,用于后续交互和动画:

blackKey.metadata = {
  config,                                        // 音符配置originalY: blackKeyHeight / 2,                 // 原始Y坐标originalColor: blackMaterial.diffuseColor.clone() // 原始颜色
};
keys.set(config.note, blackKey);

image.png

居中对齐

将整个钢琴居中显示:

// 计算总宽度const totalWidth = whiteKeyPosition; // 8个白键 × 7.8 = 62.4// 居中钢琴
keys.forEach((key) => {
  key.position.x -= totalWidth / 2;
});

⑤ 音频系统:赋予钢琴声音

JSAR 支持 Web Audio API,可以实现实时音频合成:

初始化音频上下文

let audioContext: AudioContext | null = null;

try {
  audioContext = new (window.AudioContext || (window as any).webkitAudioContext)();
  console.log('✅ 音频上下文创建成功');
} catch (error) {
  console.warn('⚠️ 音频上下文创建失败,音频功能可能不可用:', error);
}

播放音符

使用振荡器生成正弦波音频:

function playNote(note: string) {
  if (!audioContext) return;

  const config = keyConfigs.find(k => k.note === note);
  if (!config) return;

  // 创建振荡器和增益节点const oscillator = audioContext.createOscillator();
  const gainNode = audioContext.createGain();

  // 设置音色为正弦波
  oscillator.type = 'sine';
  oscillator.frequency.setValueAtTime(config.frequency, audioContext.currentTime);

  // 淡入效果
  gainNode.gain.setValueAtTime(0, audioContext.currentTime);
  gainNode.gain.linearRampToValueAtTime(0.3, audioContext.currentTime + 0.01);

  // 连接音频节点
  oscillator.connect(gainNode);
  gainNode.connect(audioContext.destination);

  oscillator.start();
  activeNotes.set(note, { oscillator, gainNode });

  console.log(`🎵 播放音符: ${config.name} (${config.note}) - ${config.frequency}Hz`);
}

image.png


⑥ 交互系统:点击与反馈

点击检测

JSAR 提供了场景级别的点击事件:

function setupInteraction() {
  scene.onPointerDown = (evt, pickResult) => {
    if (pickResult.hit && pickResult.pickedMesh) {
      const mesh = pickResult.pickedMesh;
      if (mesh.metadata && mesh.metadata.config) {
        playNote(mesh.metadata.config.note);

        // 300ms后自动停止setTimeout(() => {
          stopNote(mesh.metadata.config.note);
        }, 300);
      }
    }
  };
}

视觉反馈

琴键被按下时,实现下沉和变色效果:

// 播放音符时的视觉反馈const key = keys.get(note);
if (key) {
  // 琴键下沉
  key.position.y = key.metadata.originalY - 0.4;

  // 改变颜色const material = key.material as BABYLON.StandardMaterial;
  material.diffuseColor = config.isBlack
    ? new BABYLON.Color3(0.3, 0.3, 0.3)  // 黑键变浅灰
    : new BABYLON.Color3(0.8, 0.8, 0.8); // 白键变灰
}

// 停止音符时恢复
key.position.y = key.metadata.originalY;
material.diffuseColor = key.metadata.originalColor;

⑦ 材质系统:打造真实感

JSAR 支持 Babylon.js 的完整材质系统:

// 白键材质 - 亮白色带光泽const whiteMat = new BABYLON.StandardMaterial('white-mat', scene);
whiteMat.diffuseColor = new BABYLON.Color3(0.95, 0.95, 0.95);  // 漫反射
whiteMat.specularColor = new BABYLON.Color3(0.5, 0.5, 0.5);    // 镜面反射// 黑键材质 - 深黑色哑光const blackMat = new BABYLON.StandardMaterial('black-mat', scene);
blackMat.diffuseColor = new BABYLON.Color3(0.1, 0.1, 0.1);
blackMat.specularColor = new BABYLON.Color3(0.2, 0.2, 0.2);

// 地面材质 - 深紫色舞台const groundMat = new BABYLON.StandardMaterial('groundMat', scene);
groundMat.diffuseColor = new BABYLON.Color3(0.2, 0.15, 0.3);
groundMat.specularColor = new BABYLON.Color3(0.1, 0.1, 0.1);

通过调整这些参数,可以实现真实的钢琴质感。

image.png


性能优化技巧

JSAR 场景中对象数量较多时,需要注意性能:

① 共享材质

// 好的做法:多个白键共享同一材质const whiteMat = new BABYLON.StandardMaterial('white-mat', scene);
whiteKey1.material = whiteMat;
whiteKey2.material = whiteMat;
whiteKey3.material = whiteMat;

// 避免:为每个琴键创建新材质const whiteMat1 = new BABYLON.StandardMaterial('white-mat-1', scene);

② 控制音频实例

// 限制同时播放的音符数量const MAX_CONCURRENT_NOTES = 5;
if (activeNotes.size >= MAX_CONCURRENT_NOTES) {
  const oldestNote = activeNotes.keys().next().value;
  stopNote(oldestNote);
}

最终效果

通过 Console 可以看到统计信息:

🎹 钢琴尺寸: 62.4 x 39 x 3.12
📐 占地比例: 放大3.9倍
🎵 音符数量: 13 (C4-C5)
🎼 白键: 8 | 黑键: 5
📊 总对象数: 15 (13个琴键 + 地面 + 相机)
🎨 材质系统: 漫反射 + 镜面反射
🔊 音频系统: Web Audio API + 正弦波合成

JSAR 开发心得

经过这个项目的实战,我总结了 JSAR 的几个核心优势:

特性说明
开发效率TypeScript + Babylon.js,开发体验极佳
调试体验Chrome DevTools 远程调试,即时反馈
音频能力完整支持 Web Audio API,实时音频合成
3D引擎Babylon.js 引擎,功能强大
项目结构标准化的 npm package 结构

总结

JSAR 为 AR 开发者提供了一个强大而易用的开发框架。通过这个钢琴项目,我们看到了:

  • 使用 TypeScript 构建复杂 3D 场景的能力
  • Web Audio API 与 3D 场景的完美融合
  • Chrome DevTools 带来的出色调试体验
  • Babylon.js 引擎提供的丰富图形能力
  • JSAR 标准化的项目结构和开发流程

如果你也对 AR 开发感兴趣,不妨试试 JSAR。从简单的立方体开始,逐步构建你的 3D 世界,相信你会和我一样,被它的优雅和强大所打动。


相关资源