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>
相关文章
|
JavaScript 前端开发 索引
用Three.js搞个炫酷3D地球
地球人怎么可以不会画地球!从canvas画地球贴图开始,用Three.js手把手教你实现一个炫酷的3D地球!
用Three.js搞个炫酷3D地球
|
3月前
Threejs制作窗户透亮效果
这篇文章讲解了如何在Three.js中制作窗户的透亮效果,包括设置透明材质和光照以实现逼真的窗户渲染效果的技术细节。
78 1
|
3月前
Threejs创建天空和太阳
这篇文章讲解了如何使用Three.js中的Sky组件来创建真实的天空与太阳效果,包括调整天空的颜色、太阳的位置以及实现大气散射等技巧。
92 3
|
2月前
|
JavaScript
jQuery制作的3D冰块立方时钟动态特效源码
jQuery制作的3D冰块立方时钟动态特效源码是一段基于jQuery实现的3D魔方立方时钟效果代码,该设计非常特别,且支持数字颜色的变化,提供8款颜色选择,非常有意思,欢迎对此段代码感兴趣的朋友前来下载使用。
40 8
|
3月前
Threejs制作骨骼模型
这篇文章详细介绍了在Three.js中创建骨骼动画的过程,包括骨骼节点的创建、权重设置以及控制骨骼关节实现动态效果的步骤,并通过一个具体的圆柱体模型演示了如何添加和控制骨骼动画。
36 2
|
3月前
Threejs制作海面效果
这篇文章详细介绍了利用Three.js制作逼真海面效果的过程,包括设置Water材质、调整光照及实现波动动画的步骤。
53 0
Threejs制作海面效果
|
5月前
|
API
【threejs教程】让你的场景及物体拥有质感:聊聊threejs中的基础网络材质!
【8月更文挑战第5天】threejs中的基础网络材质教程
76 3
|
5月前
|
API
【threejs教程】threejs中的边边角角:几何体详解
【8月更文挑战第6天】threejs中的几何体详解
133 4
|
5月前
|
前端开发
【threejs教程】终于搞明白了!原来threejs中的透视相机这么简单!
【8月更文挑战第5天】深入学习threejs中的透视相机!
159 2
|
5月前
|
前端开发 API
【threejs教程】让你的场景动起来!深入了解threejs场景及坐标轴
【8月更文挑战第5天】让你的场景动起来!深入了解threejs场景及坐标轴
47 0