three.js - Particles

  • Particles can be used to create stars, smoke, rain, dust, fire, etc.
  • You can have thousands of them with a reasonable frame rate(帧率)
  • Each particles is composed of plane(two triangles) always facing the camera
  • Create particles is like to create Mesh:
  • Set up
  // 导入three.js
  import * as THREE from 'three'
  // 导入轨道控制器
  import { OrbitControls } from 'three/addons/controls/OrbitControls.js';

  // scene
  const scene = new THREE.Scene()
  
  // camera
  const camera = new THREE.PerspectiveCamera(
    75,
    window.innerWidth / window.innerHeight,
    0.1,
    100
  )
  camera.position.z = 3

  // render
  const renderer = new THREE.WebGLRenderer()
  renderer.setSize(window.innerWidth, window.innerHeight)
  document.body.appendChild(renderer.domElement)

  window.addEventListener('resize', () => {
    camera.aspect = window.innerWidth / window.innerHeight
    camera.updateProjectionMatrix()

    renderer.setSize(window.innerWidth, window.innerHeight)
    renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))
  })

  // axesHelper (根据自己的需要选择是否添加坐标轴)
  const axesHelper = new THREE.AxesHelper(5)
  scene.add(axesHelper)

  // controls
  const controls = new OrbitControls(camera, renderer.domElement)
  controls.enableDamping = true

  // animate
  const clock = new THREE.Clock()
  function animate () {
    let elapsedTime = clock.getElapsedTime()

    controls.update()
    requestAnimationFrame(animate)
    renderer.render(scene, camera)
  }
  animate()
  • Simple particles(完成后如下图)
  /**
   * particles
   */
  // Geometry
  const particlesGeometry = new THREE.SphereGeometry(1, 32, 32)  // 球体 - 半径、水平和垂直分段数

  // Material
  const particlesMaterial = new THREE.PointsMaterial({
    size: 0.02, // 粒子大小
    sizeAttenuation: true, // 尺寸衰减,产生透视效果,粒子距离camera越远看起来越小
  })

  // Points
  const particles = new THREE.Points(particlesGeometry, particlesMaterial)
  scene.add(particles)
Simple particles.png
  • Custom geometry 自定义几何图形
  /**
   * particles
   */
  // Geometry
  const particlesGeometry = new THREE.BufferGeometry()
  const count = 500 // bufferGeometry个数
  const positions = new Float32Array(count * 3) // 参数表示数组长度,每个bufferGeometry有 x,y,z 3个坐标点
  // 填充positions数组
  for(let i = 0; i < count * 3; i++) {
    positions[i] = (Math.random() - 0.5) * 10  // 事粒子居中并扩大显示范围
  }
  // 3个值为一组(向缓冲区几何体添加属性)
  particlesGeometry.setAttribute(
    'position', 
    new THREE.BufferAttribute(positions, 3)
  )

  // Material
  const particlesMaterial = new THREE.PointsMaterial({
    size: 0.02, // 粒子大小
    sizeAttenuation: true, // 尺寸衰减,产生透视效果,粒子距离camera越远看起来越小
  })

  // Points
  const particles = new THREE.Points(particlesGeometry, particlesMaterial)
  scene.add(particles)
Custom geometry.png
  • Color, Map and Alpha Map
    • 添加简单的颜色、texture设置,仔细观察下图会发现前面的parcles遮挡了后面的,并且有明显的边缘
    /**
     * Textures
    */
    const textureLoader = new THREE.TextureLoader()
    const particleTexture = textureLoader.load('../public/imgs/particles/2.png')
    ...
    ...
    // Material
    const particlesMaterial = new THREE.PointsMaterial({
      color: '#ff88cc',
      size: 0.1, // 粒子大小
      sizeAttenuation: true, // 尺寸衰减,产生透视效果,粒子距离camera越远看起来越小
      map: particleTexture,
    })
    
    添加简单的颜色、texture设置.png
    • 理想中的效果应当是除圆环以外的部分都是透明不可见的,不会出现前后遮挡的问题,解决这个问题首先:activate the transparency and use the texture on the alphaMap instead of the map,但是修改后还是无法完美解决,遮挡的问题看似解决了,但是particle的边缘有时还依然存在
    // Material
    const particlesMaterial = new THREE.PointsMaterial({
      color: '#ff88cc',
      size: 0.1, // 粒子大小
      sizeAttenuation: true, // 尺寸衰减,产生透视效果,粒子距离camera越远看起来越小
      transparent: true,
      alphaMap: particleTexture,  // webgl对一个粒子的渲染和创建是同时的,同样黑色部分透明度都为0,就无法分辨前后
    })
    
    • we can still see the edges of the particles, this is because the particles are drawn in the same order as they are created, and WebGl doesn't really know which on is on front of the other

      edges of the particles.png

    • there are multiple ways to fix it, first we can use the alpha test, the alphaTest is a value between 0 and 1 that enables the WebGl to know when not to render the pixel according to the pixel's transparency, 有一定的效果,但是particle的边缘依然不能很好的重叠,前后遮挡效果一般

    // Material
    const particlesMaterial = new THREE.PointsMaterial({
      color: '#ff88cc',
      size: 0.1, // 粒子大小
      sizeAttenuation: true, // 尺寸衰减,产生透视效果,粒子距离camera越远看起来越小
      transparent: true,
      alphaMap: particleTexture,  // webgl对一个粒子的渲染和创建是同时的,同样黑色部分透明度都为0,就无法分辨前后
      alphaTest: 0.01, // 依然会有边缘出现
    })
    
    alphaTest的边缘问题.png
    • the second we can use depth test, the WebGl tests if what's being drawn is closer than what's already drawn, if it's behind , the particles won't be drawn; but it can create bugs if you have other objects in your scene or particles with different colors
    // Material
    const particlesMaterial = new THREE.PointsMaterial({
      color: '#ff88cc',
      size: 0.1, // 粒子大小
      sizeAttenuation: true, // 尺寸衰减,产生透视效果,粒子距离camera越远看起来越小
      transparent: true,
      alphaMap: particleTexture,  // webgl对一个粒子的渲染和创建是同时的,同样黑色部分透明度都为0,就无法分辨前后
      // alphaTest: 0.01, // 依然会有边缘出现
      depthTest: false, // 不适用页面有多个不同颜色物体,会出现穿透效果,测试当前正在渲染的粒子是否在其他粒子前面,behind就不画
    })
    ...
    ...
    // Cube
      const cube = new THREE.Mesh(
      new THREE.BoxGeometry(),
      new THREE.MeshBasicMaterial()
    )
    scene.add(cube)
    
    depthTest出现的穿透效果.png
    • the third we can use depth write, the depth of what's being drawn is stored in what we call depth buffer(深度缓冲区), instead of not testing if the particle is closer than what's in this depth buffer, we can tell the WebGl not to write particles in that depth buffer, 我们可以理解为有一个正在绘制的灰度处理区域,在这个区域里同时绘制当前正在绘制的内容
    // Material
    const particlesMaterial = new THREE.PointsMaterial({
      color: '#ff88cc',
      size: 0.1, // 粒子大小
      sizeAttenuation: true, // 尺寸衰减,产生透视效果,粒子距离camera越远看起来越小
      transparent: true,
      alphaMap: particleTexture,  // webgl对一个粒子的渲染和创建是同时的,同样黑色部分透明度都为0,就无法分辨前后
      // alphaTest: 0.01, // 依然会有边缘出现
      // depthTest: false, // 不适用页面有多个不同颜色物体,会出现穿透效果,测试当前正在渲染的粒子是否在其他粒子前面,behind就不画
      depthWrite: false, // depth buffer存储已渲染的部分
    })
    
    depthWrite解决了穿透的问题.png
    • the last solution is Blending, with the blending property, we can tell the WebGl to add the color of the pixel to the color of the pixel already drawn, 在绘制过程中颜色是层层叠加的,所以我们会看到叠加的部分很亮,接近白色;使用 blending可能会影响性能,因此建议不要同时渲染太多或太大的particles
    // Material
    const particlesMaterial = new THREE.PointsMaterial({
      color: '#ff88cc',
      size: 0.1, // 粒子大小
      sizeAttenuation: true, // 尺寸衰减,产生透视效果,粒子距离camera越远看起来越小
      transparent: true,
      alphaMap: particleTexture,  // webgl对一个粒子的渲染和创建是同时的,同样黑色部分透明度都为0,就无法分辨前后
      // alphaTest: 0.01, // 依然会有边缘出现
      // depthTest: false, // 不适用页面有多个不同颜色物体,会出现穿透效果,测试当前正在渲染的粒子是否在其他粒子前面,behind就不画
      depthWrite: false, // depth buffer存储已渲染的部分
      blending: THREE.AdditiveBlending, // 颜色叠加
    })
    
    blending.png
  • Different Color of each particle,使用顶点着色
  /**
   * particles
   */
  // Geometry
  ...
  const colors = new Float32Array(count * 3)  // 颜色是由 r,g,b 组成的
  // 填充positions数组
  for(let i = 0; i < count * 3; i++) {
    ...
    colors[i] = Math.random()
  }
  // 3个值为一组(向缓冲区几何体添加属性)
  ...
  particlesGeometry.setAttribute(
    'color',
    new THREE.BufferAttribute(colors, 3)
  )

  // Material
  const particlesMaterial = new THREE.PointsMaterial({
    // color: '#ff88cc',
    ...
    depthWrite: false, // depth buffer存储已渲染的部分
    blending: THREE.AdditiveBlending, // 颜色叠加
    vertexColors: true, // 顶点颜色
  })
Different Color of each particle.png
  • Animate
    • make the particles move up and down, like waves
    • use the points as an object, 设置每个particle的动画
    • we can update each vertex separately in particlesGeometry.attributes.position.array, 这是一个一维数组
  const clock = new THREE.Clock()
  function animate () {
    let elapsedTime = clock.getElapsedTime()

    // update particles
    for(let i = 0; i < count; i++) {  // count表示particles的数量,但是每个particle都有x,y,z3个坐标信息
      const i3 = i * 3  // 对应每个particle的x坐标值
      const x = particlesGeometry.attributes.position.array[i3]
      particlesGeometry.attributes.position.array[i3 + 1] = Math.sin(elapsedTime + x)
    }
    particlesGeometry.attributes.position.needsUpdate = true  // geometry的属性改变时需要重置

    controls.update()
    requestAnimationFrame(animate)
    renderer.render(scene, camera)
  }
  animate()
particles动画的状态.png
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,080评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,422评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,630评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,554评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,662评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,856评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,014评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,752评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,212评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,541评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,687评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,347评论 4 331
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,973评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,777评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,006评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,406评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,576评论 2 349

推荐阅读更多精彩内容