首页 > 其他分享 >Threejs实现模型对接音乐节奏

Threejs实现模型对接音乐节奏

时间:2024-03-13 17:32:28浏览次数:28  
标签:Threejs boxMeshs 节奏 对接 THREE let position new dataArray

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

        先上个视频效果

<iframe allowfullscreen="true" data-mediaembed="csdn" frameborder="0" id="P2XIzdiD-1710292422095" src="https://live.csdn.net/v/embed/370114"></iframe>

threejs做音乐节奏

这只是做个简单的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);
      }
    },

此时的场景如下:

下面就是关键的步骤,通过不断更新的音乐节奏来更新小方块的位置,这里是在渲染动画里去实现,在循环中不断通过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,boxMeshs,节奏,对接,THREE,let,position,new,dataArray
From: https://blog.csdn.net/qq_26881073/article/details/136669443

相关文章

  • 在UniApp中实现对接PayPal支付
    基本的指南:获取PayPalAPI凭证:首先,你需要在PayPal开发者中心注册并创建一个应用。这将为你提供必要的API凭证,如ClientID和Secret,用于与PayPal的API进行通信。配置UniApp项目:在你的UniApp项目中,你需要配置PayPal的相关信息。这通常包括在manifest.json文件中设置支付相关......
  • LACP短超时和长超时对接实验
    实验现象sw1和sw2互联的两条链路加入统一聚合口1,配置为动态聚合具体debug信息见文章最后SW1和SW21、默认情况下lacp为长超时,displink-aggverbri1 本端和对端的flags “B”都没有置位(表示默认为长超时),debugginglacp(debugginglink-aggregationlacpall)报文情况来......
  • Java 对接Zabbix获取主机监控+告警数据
    1.Java对接ZabbixAPI前提准备  zabbix对接文档地址: https://www.zabbix.com/documentation/6.4/zh/manual/api对接ZabbixAPI接口需要针对对接的用户授予对应的API权限,如下图所示 使用超管账户登录zabbix 用户--->用户,查看用户列表      点击对接......
  • 如何开发云打印服务?云打印api怎么对接?
    近来一段时间,云打印的火热让很多企业和第三方程序也想通过云打印服务来进行App的变现,尤其是一些学习类、工具类App,本身的变现方式较为单一,对于云打印服务的需求就更大了。那么我们该如何开发云打印服务?云打印api怎么对接?今天就带大家来了解一下。 如何开发云打印服务?云打印ap......
  • php+java加密对接算法
    本文为joshua317原创文章,转载请注明:转载自joshua317博客 https://www.joshua317.com/article/322请求示例:{"appKey":"demo","nonce":"12345","sign":"04a8ba0a19ffc491716131a542729a9c250d84ce4211889a15f920ce974cf23......
  • envoy&istio 对接ratelimit 实现限流之ratelimit启动
    直接采用官方提供的Docker镜像进行启动编写docker-compose.yaml文件version:"3"services:ratelimit:image:envoyproxy/ratelimit:19f2079fcommand:/bin/ratelimitports:-8080:8080-8081:8081-6070:6070volu......
  • envoy&istio 对接ratelimit 实现限流之ratelimit简介
    23年的时候公司因调用企业微信接口超限,导致业务问题。架构组经过协商后决定上一个限流服务。限流这块自然而然就落到我负责的网关这块,小公司我一个人负责api网关这块。之前基于istio给公司上线了一个本地的限流(我给公司开发了一个devops管理工具,可以用来管理k8s、istio、jenki......
  • 万字长文学会对接 AI 模型:Semantic Kernel 和 Kernel Memory,工良出品,超简单的教程
    万字长文学会对接AI模型:SemanticKernel和KernelMemory,工良出品,超简单的教程目录万字长文学会对接AI模型:SemanticKernel和KernelMemory,工良出品,超简单的教程配置环境部署one-api配置项目环境模型划分和应用场景聊天提示词引导AI回复指定AI回复特定格式模板化提示......
  • php 对接vivo 用户行为数据上传接口
    vivo文档:https://open-ad.vivo.com.cn/doc/index?id=217publicfunctionvivo(){$accessToken=$this->request->param('accessToken');$srcId=$this->request->param('srcId');$cvType=$this->request-......
  • Java对接微信V3支付
    微信支付(V3版本)微信支付前期准备:(官方接口文档)获取商户号:微信商户平台 ->我有PC网站 ->接入微信支付->填写资料,提交微信审核(1-2个工作日)->审核通过后,返回微信支付首页,扫码登录->账户中心 ->个人信息,登录账户(商户号) 获取AppID:申请微信公众号(账号类型:服务号)->申请......