Sound Blocks - EP01:前言
平常上班時耳機都常都是放白噪音、雨聲等等自然聲音。
同一個聲音聽太多次,連下一個雷聲甚麼時候出現我都快背起來了。(›´ω`‹ )
雖然有很多不錯的線上混音器網站,但是總感覺差了一點甚麼。
這次想嘗試用 Babylon.js 做一個 3D 白噪音混音器,聲音使用隨機播放的形式呈現。
說明
做一個有趣的白噪音網站,同時練習更複雜的 3D 應用
概念
每個聲音都是一個積木,積木可以任意組合,積木種類與規模會產生不同自然音效。
例如:
- 草:無
- 樹木:風吹過樹葉聲音
- 雲:下雨聲音
- 房子:咖啡廳聲音
- 河:流水聲
不同規模會產生生態系,例如樹木夠多會有蟲鳴、鳥叫等等。
地圖資訊會儲存在 URL 中,分享連結即可讓其他人聽到你的設計的自然音效。
素材
3D 積木則基於 Kay Lousberg 大大設計的「KayKit - Medieval Hexagon Pack」
感謝 Kay Lousberg 大大提供如此優秀的素材!(*´∀`)~♥
開發
現在讓我們開始吧。੭ ˙ᗜ˙ )੭
第一步我打算先在此部落格進行原型測試。
歸功於 VitePress 強大的客製化能力,現在讓我們來建立一個完全客製化的頁面。
建立頁面
新增檔案。
content\aquarium\sound-blocks\index.md
---
title: 聲音積木
image: https://codlin.me/cod-toys.webp
layout: false
---
layout
設為 false
代表不使用預設的佈局,這樣就可以自由設計頁面了!♪( ◜ω◝و(و
TIP
詳細說明可見文件:VitePress:Layout
現在導航到 http://localhost:3000/aquarium/sound-blocks/
應該會是一片空白。
讓我們新增一個 Vue 元件作為首頁。
content\aquarium\sound-blocks\sound-blocks.vue
<template>
<div class=" overflow-hidden">
<canvas
class="canvas w-full h-full"
/>
</div>
</template>
<script setup lang="ts">
onMounted(() => {
document.body.classList.add('overflow-hidden')
})
onUnmounted(() => {
document.body.classList.remove('overflow-hidden')
})
</script>
<style lang="sass">
body.overflow-hidden
overflow: hidden
</style>
<style lang="sass" scoped>
.canvas
outline: none
</style>
預留一個 canvas
之後用於 Babylon.js 渲染。
在 body
上加上 overflow-hidden
是為了避免頁面出現滾動條。
接著在頁面中引入元件。
content\aquarium\sound-blocks\index.md
---
title: 聲音積木
image: https://codlin.me/cod-toys.webp
layout: false
---
<script setup>
import SoundBlocks from './sound-blocks.vue'
</script>
<sound-blocks />
現在讓我們來產生 3D 場景吧。
由於 babylon 場景最基本構成是 engine
、scene
、camera
、light
。
所以我將相關邏輯直接獨立為 use-babylon-scene
,簡化元件內容。
程式碼內容就不贅述了,有興趣的可以參考這裡。
現在來引用 use-babylon-scene
。
content\aquarium\sound-blocks\sound-blocks.vue
// ...
<script setup lang="ts">
import { useBabylonScene } from './use-babylon-scene'
const {
canvasRef,
} = useBabylonScene()
// ...
</script>
// ...
接著產生地板與霧氣效果。
content\aquarium\sound-blocks\sound-blocks.vue
// ...
<script setup lang="ts">
import type { Scene } from '@babylonjs/core'
import {
Color3,
MeshBuilder,
StandardMaterial,
} from '@babylonjs/core'
import { useBabylonScene } from './use-babylon-scene'
function createGround({ scene }: {
scene: Scene;
}) {
const ground = MeshBuilder.CreateGround('ground', {
width: 1000,
height: 1000,
}, scene)
const groundMaterial = new StandardMaterial('groundMaterial', scene)
groundMaterial.diffuseColor = new Color3(0.98, 0.98, 0.98)
ground.material = groundMaterial
ground.receiveShadows = true
}
const {
canvasRef,
} = useBabylonScene({
async init(params) {
const { scene } = params
createGround({ scene })
},
})
// ...
</script>
// ...
現在場景中應該會有一個白色地板與灰背景效果了。
接著把模型放到靜態目錄中,下載模型並解壓縮。
content\public\sound-blocks\hexagon-pack
來建立第一個積木吧!
首先建立一個統一匯出產生積木 function 的檔案,並定義產生積木的 params
型別。
content\aquarium\sound-blocks\blocks\index.ts
import type { Scene, ShadowGenerator } from '@babylonjs/core'
export interface CreateBlockParams {
scene: Scene;
shadowGenerator?: ShadowGenerator;
}
產生第一個樹木積木。
content\aquarium\sound-blocks\blocks\tree.ts
import type { CreateBlockParams } from '.'
import {
SceneLoader,
TransformNode,
Vector3,
} from '@babylonjs/core'
import { forEach, pipe } from 'remeda'
export async function createTreeBlock(
{
scene,
shadowGenerator,
}: CreateBlockParams,
) {
const [
treeResult,
hexResult,
] = await Promise.all([
SceneLoader.ImportMeshAsync(
'',
'/sound-blocks/hexagon-pack/decoration/nature/',
'trees_B_medium.gltf',
scene,
),
SceneLoader.ImportMeshAsync(
'',
'/sound-blocks/hexagon-pack/tiles/base/',
'hex_grass.gltf',
scene,
),
])
const rootNode = new TransformNode('block-root', scene)
pipe(
treeResult.meshes,
forEach((mesh) => {
mesh.receiveShadows = true
}),
([rootMesh]) => {
if (!rootMesh)
return
rootMesh.position = new Vector3(0, 0.5, 0)
shadowGenerator?.addShadowCaster(rootMesh)
rootMesh.parent = rootNode
},
)
pipe(
hexResult.meshes,
forEach((mesh) => {
mesh.receiveShadows = true
}),
([rootMesh]) => {
if (!rootMesh)
return
rootMesh.position = new Vector3(0, 0.5, 0)
shadowGenerator?.addShadowCaster(rootMesh)
rootMesh.parent = rootNode
},
)
return {
rootNode,
}
}
並在 index.ts
統一匯出。
content\aquarium\sound-blocks\blocks\index.ts
import type { Scene, ShadowGenerator } from '@babylonjs/core'
export interface CreateBlockParams {
scene: Scene;
shadowGenerator?: ShadowGenerator;
}
export * from './tree'
最後在元件引用看看。
content\aquarium\sound-blocks\sound-blocks.vue
// ...
<script setup lang="ts">
// ...
const {
canvasRef,
} = useBabylonScene({
async init(params) {
const { scene } = params
const shadowGenerator = createShadowGenerator(scene)
createGround({ scene })
await createTreeBlock({ scene, shadowGenerator })
},
})
// ...
</script>
// ...
沒意外的話,畫面裡會跑出一個精緻的樹木積木。
成功踏出第一步!✧⁑。٩(ˊᗜˋ*)و✧⁕。
總結 🐟
以上程式碼可以在此取得
- 建立 3D 場景
- 建立第一個積木