Threejs实现音乐节奏表演

简介: 这篇文章详细介绍了如何使用Three.js结合音频频谱分析来创建一个随着音乐节奏变化的3D视觉表演,提供了实现音乐可视化效果的具体步骤和代码示例。

昨天看到一个网站是2维的柱形图随着音乐节奏起伏,我突然想到二维形状可以起伏,三维应该也可以,最终原理应该都是将音乐频谱分解为数据,通过数据的切换不断地改变图形的形状来实现,因为找了分析音乐频谱的源码,解析完,再通过threejs的动画实现了3D场景下的模型随着音乐的节奏而变化,下面做详细步骤的说明:

这只是做个简单的demo,主要是有了音乐的数据,后面的效果可以任意发挥,首先需要新建一个场景,还是之前的老样子,创建场景,相机,灯光,渲染器等,代码如下:

initScene(){
      scene = new THREE.Scene();
    },
    initCamera(){
      this.camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 10000);
      this.camera.position.set(200,400,200);
    },
    initLight(){
      //添加两个平行光
      const directionalLight1 = new THREE.DirectionalLight(0xffffff, 1.5);
      directionalLight1.position.set(300,300,600)
      scene.add(directionalLight1);
      const directionalLight2 = new THREE.DirectionalLight(0xffffff, 1.5);
      directionalLight2.position.set(600,200,600)
      scene.add(directionalLight2);
    },
initFloor(){
      let floorGeometry = new THREE.BoxGeometry( 200,1,200);
      let floorMaterial = new THREE.MeshBasicMaterial({color:'#03133E'});
      let floor = new THREE.Mesh( floorGeometry, floorMaterial );
      floor.position.set(0,2,0)
      scene.add(floor)
    },
initRenderer(){
      this.renderer = new THREE.WebGLRenderer({ antialias: true });
      this.container = document.getElementById("container")
      this.renderer.setSize(this.container.clientWidth, this.container.clientHeight);
      this.renderer.setClearColor('#AAAAAA', 1.0);
      this.container.appendChild(this.renderer.domElement);
    },
    initControl(){
      this.controls = new OrbitControls(this.camera, this.renderer.domElement);
      this.controls.enableDamping = true;
      this.controls.maxPolarAngle = Math.PI / 2.2;      // // 最大角度
    },
initAnimate() {
      requestAnimationFrame(this.initAnimate);
      this.renderer.render(scene, this.camera);
}

然后在地板上放小方块,如果直接循环放置会放在同一个位置重叠,因为这里要在循环里控制每个小方块之间的距离,大概意思就是每个小方块长宽高为10,每个小方块间隔1,且每铺满11个小方块为一行后就再换一行重头平铺,每个方块的高度设置为0,水平位置为循环中计算出来的位置,最终全部平铺开来,每个都加入到场景中等会用音乐的节奏来控制小方块的跳动,但是经过测试太多的小方块会导致卡顿,(因为模型和修改模型位置和材质都会消耗性能,且因为方块独立跳动没办法进行合并),这里选择用121个小方块,做成11*11的方阵。

initBox(){
      let x = -60; let z = -60;
      for (let i = 0; i < 121; i++) {
        if(i%11 === 0){
          x = x + 11
          z = -60
        }
        z = z + 11
        const geometry = new THREE.BoxGeometry(10, 10, 10);
        const material = new THREE.MeshPhongMaterial({
          color: 0x00ff00
        });
        let boxMesh = new THREE.Mesh(geometry, material);
        this.boxMeshs.push(boxMesh)
        this.boxMeshs[i].position.set(x, 0, z);
        scene.add(this.boxMeshs[i]);
      }
    },

然后就需要去加载音乐了,音乐是设置在html中的audio标签,通过js获取标签元素来获取到连接的音乐资源,通过加载到的音乐分析,并获取他的音乐节奏,这部分我直接贴出代码,就不做详细解释了,如果有问题可以私信我。

initRound(){
      const AudioContext = window.AudioContext || window.webkitAudioContext;
      let audioCtx = new AudioContext();
      if(!this.source){
        this.source = audioCtx.createMediaElementSource(this.myAudio);//通过audio标签获取播放源
        this.analyser = audioCtx.createAnalyser(); // 创建AnalyserNode 用于分析音频频谱的节点
        this.source.connect(this.analyser);
        this.analyser.connect(audioCtx.destination);
        let bufferLength = this.analyser.frequencyBinCount;
        this.dataArray = new Uint8Array(bufferLength);
      }
    },

此时的场景如下:

Threejs实现音乐节奏表演_Threejs

下面就是关键的步骤,通过不断更新的音乐节奏来更新小方块的位置,这里是在渲染动画里去实现,在循环中不断通过this.analyser.getByteFrequencyData(this.dataArray);获取到新的音乐数据,然后根据dataArray的值设置方块的位置,这里是直接设置方块的所在高度,为了更好看的效果还加了颜色设置,通过不同高度切换不同的RGB颜色,并赋值给对应的小方块材质,

initAnimate() {
      requestAnimationFrame(this.initAnimate);
      this.renderer.render(scene, this.camera);
      if(this.analyser){
        this.analyser.getByteFrequencyData(this.dataArray);
        for (let i = 0; i < this.dataArray.length-1; i++) {
          if(this.boxMeshs[i]){
            let x = this.boxMeshs[i].position.x
            let z = this.boxMeshs[i].position.z
            this.boxMeshs[i].position.set(x,this.dataArray[i],z)
            this.boxMeshs[i].geometry.parameters.width = this.dataArray[i]
            this.boxMeshs[i].material.color = new THREE.Color(0.8,this.dataArray[i],this.dataArray[i]);
            let r =0, g = 0, b  = 0;
            if(this.dataArray[i]<85){
              r = g = b = this.dataArray[i] * 3
            }else if(this.dataArray[i]<170){
              r = g = (this.dataArray[i] - 85) * 2
              b = 255
            }else{
              r = (this.dataArray[i] - 170) * 3
              g = b = 255
            }
            this.boxMeshs[i].material.color = new THREE.Color('rgb('+r+', '+g+', '+b+')')
          }
        }
      }
    },

基本的代码就这些了,下面附上完整代码

<template>
  <div>
    <div style="position: absolute;top:10px;left:40%;z-index: 100">
      <audio id='myAudio' controls>
        <source src="/static/video/music.mp3" type="audio/mp3">
      </audio>
    </div>
    <div id="container"></div>
  </div>
</template>

<script>
import * as THREE from 'three'
import {OrbitControls} from "three/addons/controls/OrbitControls";

let scene;
export default {
  name: "agv-single",
  data() {
    return{
      camera:null,
      cameraCurve:null,
      renderer:null,
      container:null,
      controls:null,

      myAudio:null,
      analyser:null,
      dataArray:[],
      boxMeshs:[],
      source:null,
    }
  },
  methods:{
    initScene(){
      scene = new THREE.Scene();
    },
    initCamera(){
      this.camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 10000);
      this.camera.position.set(200,400,200);
    },
    initLight(){
      //添加两个平行光
      const directionalLight1 = new THREE.DirectionalLight(0xffffff, 1.5);
      directionalLight1.position.set(300,300,600)
      scene.add(directionalLight1);
      const directionalLight2 = new THREE.DirectionalLight(0xffffff, 1.5);
      directionalLight2.position.set(600,200,600)
      scene.add(directionalLight2);
    },
    initFloor(){
      let floorGeometry = new THREE.BoxGeometry( 200,1,200);
      let floorMaterial = new THREE.MeshBasicMaterial({color:'#03133E'});
      let floor = new THREE.Mesh( floorGeometry, floorMaterial );
      floor.position.set(0,2,0)
      scene.add(floor)
    },
    initRound(){
      const AudioContext = window.AudioContext || window.webkitAudioContext;
      let audioCtx = new AudioContext();
      if(!this.source){
        this.source = audioCtx.createMediaElementSource(this.myAudio);//通过audio标签获取播放源
        this.analyser = audioCtx.createAnalyser(); // 创建AnalyserNode 用于分析音频频谱的节点
        this.source.connect(this.analyser);
        this.analyser.connect(audioCtx.destination);
        let bufferLength = this.analyser.frequencyBinCount;
        this.dataArray = new Uint8Array(bufferLength);
      }
    },
    initBox(){
      let x = -60; let z = -60;
      for (let i = 0; i < 121; i++) {
        if(i%11 === 0){
          x = x + 11
          z = -60
        }
        z = z + 11
        const geometry = new THREE.BoxGeometry(10, 10, 10);
        const material = new THREE.MeshPhongMaterial({
          color: 0x00ff00
        });
        let boxMesh = new THREE.Mesh(geometry, material);
        this.boxMeshs.push(boxMesh)
        this.boxMeshs[i].position.set(x, 0, z);
        scene.add(this.boxMeshs[i]);
      }
    },
    initRenderer(){
      this.renderer = new THREE.WebGLRenderer({ antialias: true });
      this.container = document.getElementById("container")
      this.renderer.setSize(this.container.clientWidth, this.container.clientHeight);
      this.renderer.setClearColor('#AAAAAA', 1.0);
      this.container.appendChild(this.renderer.domElement);
    },
    initControl(){
      this.controls = new OrbitControls(this.camera, this.renderer.domElement);
      this.controls.enableDamping = true;
      this.controls.maxPolarAngle = Math.PI / 2.2;      // // 最大角度
    },
    initAnimate() {
      requestAnimationFrame(this.initAnimate);
      this.renderer.render(scene, this.camera);
      if(this.analyser){
        this.analyser.getByteFrequencyData(this.dataArray);
        for (let i = 0; i < this.dataArray.length-1; i++) {
          if(this.boxMeshs[i]){
            let x = this.boxMeshs[i].position.x
            let z = this.boxMeshs[i].position.z
            this.boxMeshs[i].position.set(x,this.dataArray[i],z)
            this.boxMeshs[i].geometry.parameters.width = this.dataArray[i]
            this.boxMeshs[i].material.color = new THREE.Color(0.8,this.dataArray[i],this.dataArray[i]);
            let r =0, g = 0, b  = 0;
            if(this.dataArray[i]<85){
              r = g = b = this.dataArray[i] * 3
            }else if(this.dataArray[i]<170){
              r = g = (this.dataArray[i] - 85) * 2
              b = 255
            }else{
              r = (this.dataArray[i] - 170) * 3
              g = b = 255
            }
            this.boxMeshs[i].material.color = new THREE.Color('rgb('+r+', '+g+', '+b+')')
          }
        }
      }
    },
    initPage(){
      this.initScene();
      this.initCamera();
      this.initLight();
      this.initFloor();
      this.initRenderer();
      this.initControl();
      this.initBox();
      this.initAnimate();
    }
  },
  mounted() {
    this.myAudio = document.querySelector('#myAudio');
    this.myAudio.addEventListener('play', () => {
      this.initRound();
    })
    this.initPage()
  }
}
</script>

<style scoped>
#container{
  position: absolute;
  width:100%;
  height:100%;
  overflow: hidden;
}

</style>

效果图如下

Threejs实现音乐节奏表演_数据_02

相关文章
|
4天前
|
自然语言处理 搜索推荐 Docker
CosyVoice实现声音复刻
这篇文章介绍了如何使用CosyVoice平台实现个性化的声音复刻,包括录制样本音频、上传处理以及生成定制化语音的全流程。
43 6
CosyVoice实现声音复刻
|
2月前
黑神话:悟空中的音效设计与音乐制作
【8月更文第26天】在《黑神话:悟空》这款游戏中,音效和背景音乐是构建沉浸式游戏体验的重要组成部分。本文将探讨游戏音效和背景音乐的创作过程,以及它们如何增强游戏氛围。
208 0
|
4月前
|
编译器 API C++
【感受C++的魅力】:用C++演奏歌曲《起风了》——含完整源码
【感受C++的魅力】:用C++演奏歌曲《起风了》——含完整源码
|
前端开发 程序员 API
教你实现微信8.0『炸裂』的礼花表情特效
作为一个前端程序员,这就勾起了我的好奇心,虽然我从来没有实现过这样的动画,但是我还是忍不住想要去实现,最终我花了2天时间去看一些库的源码到我自己实现一个类似的效果,在这里我总结一下,并且手把手地教大家怎么学习实现。而🎉有一个自己的名字,叫做五彩纸屑,英文名字叫 confetti。
教你实现微信8.0『炸裂』的礼花表情特效
[视频]高端微课|W01.初尝万彩动画——人人都能制作动画版微课!
或许,你也曾想抛弃PPT那万年不变的动画模式!或许,你也想让你的微课提升一个档次!或许,你也羡慕网上那些科普类动画的的炫酷!
95 0
|
传感器 开发框架 网络协议
羡慕《钢铁侠》电影里科技感满满的全息手势交互吗?现在你也可以!试试这款【本地手势识别案例】
羡慕《钢铁侠》电影里科技感满满的全息手势交互吗?现在你也可以!试试这款【本地手势识别案例】
182 0
|
算法 图形学
还在被电影中吧爆炸的画面震撼?那你一定不要错过这款Unity的爆炸插件
Exploder是一个Unity3D插件,可以爆炸任何有网格的游戏对象。在游戏对象上tag设置为“Exploder”,就可以看到爆炸了!
《天道》电视剧观感
早期写简书的时候就有人评论,建议我看一看《天道》这部电视剧,今天在群里面也看到了人推荐看这部电视剧,今天看了一半,基本停不下来。 我觉得挺好看,有几个点,想拿出来再分享一下。