Use vue to learn the particles and particle system of three.js-realize raindrop special effects by loading texture maps

1. The texture import process in VUE

1.1 Texture image storage path

When importing in vue, the default is public, so the texture image to be used needs to be placed in this path. The raindrop-3.png in the images folder as shown below is the texture image we want to use
Insert picture description here

1.2 Get the BASE_URL in the environment variable

Create a variable publicPath in the data attribute of vue, the value of this variable is the environment variable process.env.BASE_URL in vue
Insert picture description here

1.3 Load texture maps through THREE.TextureLoader

const THIS = this
const textureLoader = new THREE.TextureLoader()
const loadTexture = textureLoader.load(
  `${
      
      THIS.publicPath}images/raindrop-3.png`
)

2. Create raindrop effects

2.1 Create raindrops with loaded texture maps

In the example, the loaded raindrop texture map is assigned to the map property of THREE.PointsMaterial material to obtain a particle material in the shape of raindrops, and then raindrops are randomly created and added to the vertices property of the Geometry object. Finally, THREE.Points is used to create a particle system, and Add the particle system to the scene

// 创建粒子
createParticleSystem () {
    
    
  const THIS = this
  const textureLoader = new THREE.TextureLoader()
  const loadTexture = textureLoader.load(
    `${
      
      THIS.publicPath}images/raindrop-3.png`
  )
  // 创建几何体
  const geom = new THREE.Geometry()
  const material = new THREE.PointsMaterial({
    
    
    map: loadTexture,
    size: this.properties.size.value,
    transparent: this.properties.transparent,
    opacity: this.properties.opacity.value,
    sizeAttenuation: this.properties.sizeAttenuation,
    color: this.properties.color,
    blending: THREE.AdditiveBlending,
    depthTest: false // 解决透明度问题
  })
  const range = 160
  for (let i = 0; i < 3000; i++) {
    
    
    const particle = new THREE.Vector3(
      Math.random() * range - range / 2,
      Math.random() * range * 1.5,
      Math.random() * range - range / 2
    )
    particle.velocityY = 0.1 + Math.random() / 5
    particle.velocityX = (Math.random() - 0.5) / 3
    // 给几何体添加顶点坐标
    geom.vertices.push(particle)
  }
  // 创建粒子系统对象
  this.points = new THREE.Points(geom, material)
  this.points.sortParticles = true
  this.points.verticesNeedUpdate = true
  // 将粒子系统对象添加到场景
  this.scene.add(this.points)
}

2.2 Realize the raining special effect animation

In the render() function, call the following rainAnimation() function to realize the rain special effect animation

rainAnimation () {
    
    
  const vertices = this.points.geometry.vertices
  vertices.forEach(v => {
    
    
    v.y = v.y - v.velocityY * 3
    v.x = v.x - v.velocityX * 0.5
    if (v.y <= -60) v.y = 60
    if (v.x <= -20 || v.x >= 20) v.velocityX = v.velocityX * -1
  })
  // 实时更新网格的顶点信息
  this.points.geometry.verticesNeedUpdate = true
}

3.demo effect

Insert picture description here

4.demo code

<template>
  <div>
    <div id="container"></div>
    <div class="controls-box">
      <section>
        <el-row>
          <el-checkbox v-model="properties.transparent" @change="redraw">transparent</el-checkbox>
        </el-row>
        <el-row>
          <div v-for="(item,key) in properties" :key="key">
            <div v-if="item&&item.name!=undefined">
              <el-col :span="8">
                <span class="vertice-span">{
    
    {
    
    item.name}}</span>
              </el-col>
              <el-col :span="13">
                <el-slider v-model="item.value" :min="item.min" :max="item.max" :step="item.step" :format-tooltip="formatTooltip" @change="redraw"></el-slider>
              </el-col>
              <el-col :span="3">
                <span class="vertice-span">{
    
    {
    
    item.value}}</span>
              </el-col>
            </div>
          </div>
        </el-row>
        <el-row>
          <el-col :span="8" class="label-col"><label> color</label></el-col>
          <el-col :span="16">
            <div @click="inputClick">
              <el-input :value="properties.color"></el-input>
            </div>
            <div v-show="isShowColors" class="color-select-layer">
              <sketch-picker v-model="properties.color" @input="colorChange"></sketch-picker>
            </div>
          </el-col>
        </el-row>
        <el-row>
          <el-checkbox v-model="properties.sizeAttenuation" @change="redraw">sizeAttenuation</el-checkbox>
        </el-row>
      </section>
    </div>
  </div>
</template>

<script>
import * as THREE from 'three'
import {
    
     OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'
import {
    
     Sketch } from 'vue-color'
export default {
    
    
  components: {
    
    
    'sketch-picker': Sketch
  },
  data () {
    
    
    return {
    
    
      publicPath: process.env.BASE_URL,
      properties: {
    
    
        size: {
    
    
          name: 'size',
          value: 4,
          min: 0,
          max: 20,
          step: 0.1
        },
        opacity: {
    
    
          name: 'opacity',
          value: 0.6,
          min: 0,
          max: 1,
          step: 0.1
        },
        transparent: true,
        sizeAttenuation: true,
        color: '#ccffcc'
      },
      isShowColors: false,
      points: null,
      camera: null,
      scene: null,
      renderer: null,
      controls: null
    }
  },
  mounted () {
    
    
    this.init()
  },
  methods: {
    
    
    formatTooltip (val) {
    
    
      return val
    },
    inputClick () {
    
    
      this.isShowColors = !this.isShowColors
    },
    colorChange (val) {
    
    
      this.properties.color = val.hex
      this.redraw()
    },
    // 初始化
    init () {
    
    
      this.createScene() // 创建场景
      this.createParticleSystem() // 创建粒子系统
      this.createCamera() // 创建相机
      this.createRender() // 创建渲染器
      this.createControls() // 创建控件对象
      this.render() // 渲染
    },
    // 创建场景
    createScene () {
    
    
      this.scene = new THREE.Scene()
    },
    // 创建粒子
    createParticleSystem () {
    
    
      const THIS = this
      const textureLoader = new THREE.TextureLoader()
      const loadTexture = textureLoader.load(
        `${
      
      THIS.publicPath}images/raindrop-3.png`
      )
      // 创建几何体
      const geom = new THREE.Geometry()
      const material = new THREE.PointsMaterial({
    
    
        map: loadTexture,
        size: this.properties.size.value,
        transparent: this.properties.transparent,
        opacity: this.properties.opacity.value,
        sizeAttenuation: this.properties.sizeAttenuation,
        color: this.properties.color,
        blending: THREE.AdditiveBlending,
        depthTest: false // 解决透明度问题
      })
      const range = 160
      for (let i = 0; i < 3000; i++) {
    
    
        const particle = new THREE.Vector3(
          Math.random() * range - range / 2,
          Math.random() * range * 1.5,
          Math.random() * range - range / 2
        )
        particle.velocityY = 0.1 + Math.random() / 5
        particle.velocityX = (Math.random() - 0.5) / 3
        // 给几何体添加顶点坐标
        geom.vertices.push(particle)
      }
      // 创建粒子系统对象
      this.points = new THREE.Points(geom, material)
      this.points.sortParticles = true
      this.points.verticesNeedUpdate = true
      // 将粒子系统对象添加到场景
      this.scene.add(this.points)
    },

    // 创建相机
    createCamera () {
    
    
      const element = document.getElementById('container')
      const width = element.clientWidth // 窗口宽度
      const height = element.clientHeight // 窗口高度
      const k = width / height // 窗口宽高比
      // PerspectiveCamera( fov, aspect, near, far )
      this.camera = new THREE.PerspectiveCamera(45, k, 0.1, 1000)
      this.camera.position.set(0, 0, 100) // 设置相机位置

      this.camera.lookAt(new THREE.Vector3(10, 0, 0)) // 设置相机方向
      this.scene.add(this.camera)
    },
    // 创建渲染器
    createRender () {
    
    
      const element = document.getElementById('container')
      this.renderer = new THREE.WebGLRenderer({
    
     antialias: true, alpha: true })
      this.renderer.setSize(element.clientWidth, element.clientHeight) // 设置渲染区域尺寸
      this.renderer.setClearColor(0x3f3f3f, 1) // 设置背景颜色
      element.appendChild(this.renderer.domElement)
    },
    redraw () {
    
    
      if (this.points) {
    
    
        this.scene.remove(this.points)
      }
      this.createParticleSystem()
    },
    rainAnimation () {
    
    
      const vertices = this.points.geometry.vertices
      vertices.forEach(v => {
    
    
        v.y = v.y - v.velocityY * 3
        v.x = v.x - v.velocityX * 0.5

        if (v.y <= -60) v.y = 60
        if (v.x <= -20 || v.x >= 20) v.velocityX = v.velocityX * -1
      })
      // 实时更新网格的顶点信息
      this.points.geometry.verticesNeedUpdate = true
    },
    render () {
    
    
      this.rainAnimation()
      this.renderer.render(this.scene, this.camera)
      requestAnimationFrame(this.render)
    },
    // 创建控件对象
    createControls () {
    
    
      this.controls = new OrbitControls(this.camera, this.renderer.domElement)
    }
  }
}
</script>
<style>
#container {
    
    
  position: absolute;
  width: 100%;
  height: 100%;
}
.controls-box {
    
    
  position: absolute;
  right: 5px;
  top: 5px;
  width: 300px;
  padding: 10px;
  background-color: #fff;
  border: 1px solid #c3c3c3;
}
.label-col {
    
    
  padding: 8px 5px;
}
.color-select-layer {
    
    
  position: relative;
  left: -20px;
  padding: 15px 0;
}
.vertice-span {
    
    
  line-height: 38px;
  padding: 0 2px 0 10px;
}
</style>

Guess you like

Origin blog.csdn.net/qw8704149/article/details/111349905