Use vue to learn the particles and particle system of three.js-realize snow 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 snow.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/snow.png`
)

2. Create snow special effects

2.1 Create snowflakes with loaded texture maps

In the example, assign the loaded snowflake texture map to the map property of the THREE.PointsMaterial material to obtain a particle material in the shape of a snowflake, and then randomly create a snowflake and add it to the vertices property of the Geometry object. Finally, use THREE.Points 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/snow.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 snow special effects animation

In the render() function, call the following rainAnimation() function to realize the snow 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: 6,
          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: '#E9F0E9'
      },
      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/snow.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/111350420