Threejs实现闪电效果

简介: 这篇文章讲解了如何利用Three.js实现闪电效果,包括设置粒子系统、调整材质属性以及控制闪电路径的方法。

这是一次比较失败的功能实现,本来想网上很少有threejs实现闪电效果的,但是我觉得好像可以做出来,就尝试着做了,结果做出来的太丑了,但是不能时间白费,所以记录下,总得有个交代。

首先还是搭建出基础的场景

initScene(){
      this.scene = new THREE.Scene();
      const axesHelper = new THREE.AxesHelper( 10);
      axesHelper.position.set(0,0,0)
      this.scene.add( axesHelper );
    },
    initCamera(){
      this.camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 10000);
      this.camera.position.set(1000,1000,1000);
      this.camera.lookAt(500,100,500)
    },
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('#000000', 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;      // // 最大角度
      this.camera.position.set(1000,1000,1000);
      this.camera.lookAt(500,100,500)
    },
    initAnimate() {
    requestAnimationFrame(this.initAnimate);
      this.renderer.render(this.scene, this.camera);
    },

有了基础的场景之后,开始绘制闪电,首先闪电是歪歪扭扭的不是笔直的,所以想着要用到随机数,使用粒子效果做出一个个点,并且是歪歪扭扭往一个方向延伸的,

initLightNing() {
      // Geometry
      const particlesGeometry = new THREE.BufferGeometry()
      const count = 5000
      const positions = new Float32Array(count * 3)
      let beginX = 0;let beginY = 0;let beginZ = 0;
      for(let i = 0; i < count * 3; i = i+3 ){
        positions[i] = beginX + (Math.random())
        positions[i+1] = beginY + (Math.random()*0.8)
        positions[i+2] = beginZ + (Math.random())
        beginX = positions[i];
        beginY = positions[i+1];
        beginZ = positions[i+2];
      }
      particlesGeometry.setAttribute('position', new THREE.BufferAttribute(positions, 3))
      // Material
      const particlesMaterial = new THREE.PointsMaterial({
        size: 4,
        sizeAttenuation: true
      })
      particlesMaterial.color = new THREE.Color(0xffffff);
      // Points
      const particles = new THREE.Points(particlesGeometry, particlesMaterial)
      particles.name = 'lightning'
      this.scene.add(particles)
    },

然后就有了这样的效果,一切往我想的方向进行,得到了一条歪歪扭扭的曲线

这样的曲线当做闪电的主分支,然后加点叉就好了,叉肯定也是需要歪歪扭扭的,且要和主线链接,所以应该是随机找主线的某个点进行开叉,并往下走一点歪歪扭扭的曲线,且曲线的长度不一致,然后我就做了一个可以生成小分叉的曲线,小分叉的开始点由主分叉传过来,因为要连接,且个数不一致也由主分支决定长度。为了效果更好点,我还贴心的做了三个这样的小分叉,往x,y和z三个轴方向偏移一点

addForkX(beginX,beginY,beginZ,count){
      const positions = new Float32Array(count * 3)
      let startX = beginX;let startY = beginY;let startZ = beginZ;
      for(let i = 0; i < count * 3; i = i+3 ){
        positions[i] = startX + (Math.random())
        positions[i+1] = startY + (Math.random()*0.4)
        positions[i+2] = startZ + (Math.random()*0.5)
        startX = positions[i];
        startY = positions[i+1];
        startZ = positions[i+2];
      }
      return positions;
    },
    addForkY(beginX,beginY,beginZ,count){
      const positions = new Float32Array(count * 3)
      let startX = beginX;let startY = beginY;let startZ = beginZ;
      for(let i = 0; i < count * 3; i = i+3 ){
        positions[i] = startX + (Math.random()*0.5)
        positions[i+1] = startY + (Math.random()*0.4)
        positions[i+2] = startZ + (Math.random())
        startX = positions[i];
        startY = positions[i+1];
        startZ = positions[i+2];
      }
      return positions;
    },
    addForkZ(beginX,beginY,beginZ,count){
      const positions = new Float32Array(count * 3)
      let startX = beginX;let startY = beginY;let startZ = beginZ;
      for(let i = 0; i < count * 3; i = i+3 ){
        positions[i] = startX + (Math.random()*0.4)
        positions[i+1] = startY + (Math.random()*0.5)
        positions[i+2] = startZ + (Math.random())
        startX = positions[i];
        startY = positions[i+1];
        startZ = positions[i+2];
      }
      return positions;
    },

此时主分支代码修改如下:

initLightNing() {
      const particlesGeometry = new THREE.BufferGeometry()
      const count = 5000
      const positions = new Float32Array(count * 3)
      let beginX = 0;let beginY = 0;let beginZ = 0;
      for(let i = 0; i < count * 3; i = i+3 ){
        let count = 0;
        if(Math.random() > 0.993){
          count = Math.floor(Math.random()*400)
          //将分叉的数据填充到主体的数据集合中
          let forkPosition = []
          if(Math.random()<0.35){
            forkPosition = this.addForkX(beginX,beginY,beginZ,count)
          }else if(Math.random()<0.7){
            forkPosition = this.addForkY(beginX,beginY,beginZ,count)
          }else{
            forkPosition = this.addForkZ(beginX,beginY,beginZ,count)
          }
          for (let j = 0; j < forkPosition.length; j++) {
            positions[i+j] = forkPosition[j]
          }
          i = i+count*3
          continue;
        }
        positions[i] = beginX + (Math.random())
        positions[i+1] = beginY + (Math.random()*0.8)
        positions[i+2] = beginZ + (Math.random())
        beginX = positions[i];
        beginY = positions[i+1];
        beginZ = positions[i+2];
      }
      particlesGeometry.setAttribute('position', new THREE.BufferAttribute(positions, 3))
      // Material
      const particlesMaterial = new THREE.PointsMaterial({
        size: 4,
        sizeAttenuation: true
      })
      particlesMaterial.color = new THREE.Color(0xffffff);
      // Points
      const particles = new THREE.Points(particlesGeometry, particlesMaterial)
      particles.name = 'lightning'
      this.scene.add(particles)
    },

然后就得到这样的画面,一个主分叉和多个小分叉,但是明显和闪电差距有点远,后来想着加上动画会不会好点,

设置出现闪电,2/3秒的时候闪电消失,然后下一秒的时候又出现闪电,

initAnimate() {
      this.count++;
      if(this.count === 40){
        this.scene.children.forEach(item=>{
          if(item.name === 'lightning'){
            this.scene.remove(item);
          }
        })
      }else if(this.count === 60){
        this.initLightNing();
        this.count = 0;
      }
      requestAnimationFrame(this.initAnimate);
      this.renderer.render(this.scene, this.camera);
    },

然后就有了这样的动画,不过怎么看都不像闪电,倒过来放上树叶倒像小树苗。最终原因应该是闪电的分叉,应该是需要前期往两侧,尖部往下,而且偏紫色,有高亮效果,我这不过是歪歪扭扭的主线加歪歪扭扭的支线。
WechatIMG120.jpg
闪电

这里不支持上传视频,我就只能上传个图片了,如果想看动态效果可以私我,我发给你视频

完整源码如下:


<template>
  <div>
    <div id="container"></div>
  </div>
</template>

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

export default {
  name: "lightning-example",
  data() {
    return{
      scene:null,
      camera:null,
      cameraCurve:null,
      renderer:null,
      container:null,
      controls:null,
      fireParticles:null,
      count:0,
    }
  },
  methods:{
    initScene(){
      this.scene = new THREE.Scene();
      const axesHelper = new THREE.AxesHelper( 10);
      axesHelper.position.set(0,0,0)
      this.scene.add( axesHelper );
    },
    initCamera(){
      this.camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 10000);
      this.camera.position.set(1000,1000,1000);
      this.camera.lookAt(500,100,500)
    },
    initLight(){
      //添加两个平行光
      const directionalLight1 = new THREE.DirectionalLight(0xffffff, 1.5);
      directionalLight1.position.set(300,-300,300)
      this.scene.add(directionalLight1);
      const directionalLight2 = new THREE.DirectionalLight(0xffffff, 1.5);
      directionalLight2.position.set(300,300,300)
      this.scene.add(directionalLight2);
    },
    addForkX(beginX,beginY,beginZ,count){
      const positions = new Float32Array(count * 3)
      let startX = beginX;let startY = beginY;let startZ = beginZ;
      for(let i = 0; i < count * 3; i = i+3 ){
        positions[i] = startX + (Math.random())
        positions[i+1] = startY + (Math.random()*0.4)
        positions[i+2] = startZ + (Math.random()*0.5)
        startX = positions[i];
        startY = positions[i+1];
        startZ = positions[i+2];
      }
      return positions;
    },
    addForkY(beginX,beginY,beginZ,count){
      const positions = new Float32Array(count * 3)
      let startX = beginX;let startY = beginY;let startZ = beginZ;
      for(let i = 0; i < count * 3; i = i+3 ){
        positions[i] = startX + (Math.random()*0.5)
        positions[i+1] = startY + (Math.random()*0.4)
        positions[i+2] = startZ + (Math.random())
        startX = positions[i];
        startY = positions[i+1];
        startZ = positions[i+2];
      }
      return positions;
    },
    addForkZ(beginX,beginY,beginZ,count){
      const positions = new Float32Array(count * 3)
      let startX = beginX;let startY = beginY;let startZ = beginZ;
      for(let i = 0; i < count * 3; i = i+3 ){
        positions[i] = startX + (Math.random()*0.4)
        positions[i+1] = startY + (Math.random()*0.5)
        positions[i+2] = startZ + (Math.random())
        startX = positions[i];
        startY = positions[i+1];
        startZ = positions[i+2];
      }
      return positions;
    },
    //闪电主分支
    initLightNing() {
      const particlesGeometry = new THREE.BufferGeometry()
      const count = 5000
      const positions = new Float32Array(count * 3)
      let beginX = 0;let beginY = 0;let beginZ = 0;
      for(let i = 0; i < count * 3; i = i+3 ){
        let count = 0;
        if(Math.random() > 0.993){
          count = Math.floor(Math.random()*400)
          //将分叉的数据填充到主体的数据集合中
          let forkPosition = []
          if(Math.random()<0.35){
            forkPosition = this.addForkX(beginX,beginY,beginZ,count)
          }else if(Math.random()<0.7){
            forkPosition = this.addForkY(beginX,beginY,beginZ,count)
          }else{
            forkPosition = this.addForkZ(beginX,beginY,beginZ,count)
          }
          for (let j = 0; j < forkPosition.length; j++) {
            positions[i+j] = forkPosition[j]
          }
          i = i+count*3
          continue;
        }
        positions[i] = beginX + (Math.random())
        positions[i+1] = beginY + (Math.random()*0.8)
        positions[i+2] = beginZ + (Math.random())
        beginX = positions[i];
        beginY = positions[i+1];
        beginZ = positions[i+2];
      }
      particlesGeometry.setAttribute('position', new THREE.BufferAttribute(positions, 3))
      // Material
      const particlesMaterial = new THREE.PointsMaterial({
        size: 4,
        sizeAttenuation: true
      })
      particlesMaterial.color = new THREE.Color(0xffffff);
      // Points
      const particles = new THREE.Points(particlesGeometry, particlesMaterial)
      particles.name = 'lightning'
      this.scene.add(particles)
    },
    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('#000000', 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;      // // 最大角度
      this.camera.position.set(1000,1000,1000);
      this.camera.lookAt(500,100,500)
    },
    initAnimate() {
      this.count++;
      if(this.count === 40){
        this.scene.children.forEach(item=>{
          if(item.name === 'lightning'){
            this.scene.remove(item);
          }
        })
      }else if(this.count === 60){
        this.initLightNing();
        this.count = 0;
      }
      requestAnimationFrame(this.initAnimate);
      this.renderer.render(this.scene, this.camera);
    },
    initPage(){
      this.initScene();
      this.initCamera();
      this.initLight();
      this.initRenderer();
      this.initControl();
      this.initAnimate();
      this.initLightNing();
    }
  },
  mounted() {
    this.initPage()
  }
}
</script>

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

</style>
相关文章
Threejs实现模拟河流,水面水流,水管水流,海面
Threejs实现模拟河流,水面水流,水管水流,海面
3207 0
Threejs实现模拟河流,水面水流,水管水流,海面
Threejs实现下雨,下雪,阴天,晴天,火焰
Threejs实现下雨,下雪,阴天,晴天,火焰
2389 0
Threejs实现下雨,下雪,阴天,晴天,火焰
|
数据可视化 定位技术
three.js实现烟雾缭绕效果
前言 大家好!我是Fly哥,最近接广告的接的有点多, 感谢大家还是一如既往的支持我!respect, 前几天我在朋友圈分享了一个烟雾缭绕的效果。很多小伙伴都表示非常感兴趣,有的同学说用到了噪声, 有的同学说用到了着色器,还有更过分说用到了ps, 胖虎竟然无语凝噎。其实都是就是简单的贴图。配合一点想象力。我们先看下效果: 然后我就发了一条朋友圈,问这个像什么?? 有的说 云层, 有的说云墨,有的说雾霭, 其实都不是, 我想做的是烟雾。好的话不不多说!, 本篇文章阅读大概5分钟。不耽误大家太多时间,主要是介绍思路, 说太多也没啥意义。如果你对three.js 还没有一点了解都没有,
three.js实现烟雾缭绕效果
Threejs实现模拟管道液体流动
Threejs实现模拟管道液体流动
3102 0
Threejs实现模拟管道液体流动
|
开发框架
threejs做特效:实现物体的发光效果-EffectComposer详解!
【8月更文挑战第7天】实现物体的发光效果-EffectComposer详解!
1924 6
threejs做特效:实现物体的发光效果-EffectComposer详解!
|
11月前
threeJs用精灵模型Sprite实现下雨效果
这篇文章介绍了使用Three.js中的Sprite(精灵)模型来实现下雨特效的方法和技术细节。
429 2
threeJs用精灵模型Sprite实现下雨效果
|
11月前
Threejs创建天空和太阳
这篇文章讲解了如何使用Three.js中的Sky组件来创建真实的天空与太阳效果,包括调整天空的颜色、太阳的位置以及实现大气散射等技巧。
429 3
|
11月前
Threejs实现动画
这篇文章讲解了如何使用Three.js实现动画效果,并介绍了如何控制动画的时间轴。
258 3
Threejs实现动画
|
11月前
threeJs绘制曲线
这篇文章讲解了如何使用Three.js中的CatmullRomCurve3来绘制平滑的曲线,并提供了实现的代码示例。
164 3
threeJs绘制曲线
|
11月前
Threejs用切线实现模型沿着轨道行驶
这篇文章详细介绍了如何使用Three.js让一个模型沿着预定的曲线路径移动,并保持模型的方向始终沿着路径的切线方向,提供了实现这一效果的具体代码和步骤。
551 1
Threejs用切线实现模型沿着轨道行驶