HyG Front-end Dev Engineer

Three.js 之 13 Galaxy 银河效果生成器

2022-06-27
HyG

本系列为 Three.js journey 教程学习笔记。

接下来,我们学习一下如何使用粒子,本节将开发一个银河生成器,使用粒子效果生成各种各样的银河效果。

创建粒子

根据上一节创建一个粒子立方体,并加入 debug UI,设置尺寸和数量

import * as THREE from 'three'
import './style.css'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
import * as dat from 'lil-gui'
import stats from '../common/stats'
import { listenResize, dbClkfullScreen } from '../common/utils'

// Canvas
const canvas = document.querySelector('#mainCanvas') as HTMLCanvasElement

// Scene
const scene = new THREE.Scene()

// Size
const sizes = {
  width: window.innerWidth,
  height: window.innerHeight,
}

// Camera
const camera = new THREE.PerspectiveCamera(75, sizes.width / sizes.height, 0.1, 100)
camera.position.set(4, 1.8, 4)

const controls = new OrbitControls(camera, canvas)
controls.enableDamping = true
controls.zoomSpeed = 0.3

/**
 * Galaxy
 */
const parameters = {
  count: 1000,
  size: 0.02,
}

let geometry: THREE.BufferGeometry
let material: THREE.PointsMaterial
let points: THREE.Points<THREE.BufferGeometry, THREE.PointsMaterial>

const generatorGalaxy = () => {
  if (points) {
    geometry.dispose()
    material.dispose()
    scene.remove(points)
  }

  // Geometry
  geometry = new THREE.BufferGeometry()
  const position = new Float32Array(parameters.count * 3)
  for (let i = 0; i < parameters.count; i += 1) {
    const i3 = i * 3
    position[i3] = (Math.random() - 0.5) * 3
    position[i3 + 1] = (Math.random() - 0.5) * 3
    position[i3 + 2] = (Math.random() - 0.5) * 3
  }
  geometry.setAttribute('position', new THREE.BufferAttribute(position, 3))

  // Material
  material = new THREE.PointsMaterial({
    size: parameters.size,
    sizeAttenuation: true,
    depthWrite: false,
    blending: THREE.AdditiveBlending,
  })

  points = new THREE.Points(geometry, material)
  scene.add(points)
}

generatorGalaxy()

// Renderer
const renderer = new THREE.WebGLRenderer({
  canvas,
})
renderer.setSize(sizes.width, sizes.height)
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))

listenResize(sizes, camera, renderer)
dbClkfullScreen(document.body)

// Animations
const tick = () => {
  stats.begin()

  controls.update()

  // Render
  renderer.render(scene, camera)
  stats.end()
  requestAnimationFrame(tick)
}

tick()

/**
 * Debug
 */
const gui = new dat.GUI()

gui.add(controls, 'autoRotate')
gui.add(controls, 'autoRotateSpeed', 0.1, 10, 0.01)

gui.add(parameters, 'count', 100, 1000000, 100).onFinishChange(generatorGalaxy)
gui.add(parameters, 'size', 0.001, 0.1, 0.001).onFinishChange(generatorGalaxy)

形状

半径

增加半径配置参数

/**
 * Galaxy
 */
const parameters = {
  count: 1000,
  size: 0.02,
  radius: 5,
}

沿着 x 轴半径内随机渲染

  // Geometry
  geometry = new THREE.BufferGeometry()
  const position = new Float32Array(parameters.count * 3)
  for (let i = 0; i < parameters.count; i += 1) {
    const i3 = i * 3
    const radius = Math.random() * parameters.radius
    position[i3] = radius
    position[i3 + 1] = 0
    position[i3 + 2] = 0
  }
  geometry.setAttribute('position', new THREE.BufferAttribute(position, 3))

分支

增加配置

/**
 * Galaxy
 */
const parameters = {
  count: 1000,
  size: 0.02,
  radius: 5,
  branches: 3,
}

修改点的位置

  // Geometry
  geometry = new THREE.BufferGeometry()
  const position = new Float32Array(parameters.count * 3)
  for (let i = 0; i < parameters.count; i += 1) {
    const i3 = i * 3
    const radius = Math.random() * parameters.radius
    const branchesAngle = ((i % parameters.branches) / parameters.branches) * Math.PI * 2
    position[i3] = Math.cos(branchesAngle) * radius
    position[i3 + 1] = 0
    position[i3 + 2] = Math.sin(branchesAngle) * radius
  }
  geometry.setAttribute('position', new THREE.BufferAttribute(position, 3))

偏转角度

增加配置

/**
 * Galaxy
 */
const parameters = {
  count: 1000,
  size: 0.02,
  radius: 5,
  branches: 3,
  spin: 1,
}

增加偏转角度

  // Geometry
  geometry = new THREE.BufferGeometry()
  const position = new Float32Array(parameters.count * 3)
  for (let i = 0; i < parameters.count; i += 1) {
    const i3 = i * 3
    const radius = Math.random() * parameters.radius
    const branchesAngle = ((i % parameters.branches) / parameters.branches) * Math.PI * 2
    const spinAngle = radius * parameters.spin

    position[i3] = Math.cos(branchesAngle + spinAngle) * radius
    position[i3 + 1] = 0
    position[i3 + 2] = Math.sin(branchesAngle + spinAngle) * radius
  }
  geometry.setAttribute('position', new THREE.BufferAttribute(position, 3))

随机扩散

增加如下代码

  randomness: 0.2,
    const randomX = (Math.random() - 0.5) * parameters.randomness * radius
    const randomY = (Math.random() - 0.5) * parameters.randomness * radius
    const randomZ = (Math.random() - 0.5) * parameters.randomness * radius

    position[i3] = Math.cos(branchesAngle + spinAngle) * radius + randomX
    position[i3 + 1] = randomY
    position[i3 + 2] = Math.sin(branchesAngle + spinAngle) * radius + randomZ

但仔细看效果还不是很好,我们借助幂函数来提升效果

  randomnessPower: 3,
    const randomX = Math.random() ** parameters.randomnessPower
      * (Math.random() < 0.5 ? 1 : -1)
      * parameters.randomness
      * radius
    const randomY = Math.random() ** parameters.randomnessPower
      * (Math.random() < 0.5 ? 1 : -1)
      * parameters.randomness
      * radius
    const randomZ = Math.random() ** parameters.randomnessPower
      * (Math.random() < 0.5 ? 1 : -1)
      * parameters.randomness
      * radius

效果好多了

颜色

为了有更好的效果我们添加渐变色的感觉

  insideColor: '#ff6030',
  outsideColor: '#1b3984',
gui.addColor(parameters, 'insideColor').onFinishChange(generatorGalaxy)
gui.addColor(parameters, 'outsideColor').onFinishChange(generatorGalaxy)

我们要为每个顶点设置颜色,所以需要将 vertexColors 设置为 true

  // Material
  material = new THREE.PointsMaterial({
    size: parameters.size,
    sizeAttenuation: true,
    depthWrite: false,
    blending: THREE.AdditiveBlending,
    vertexColors: true,
  })

然后添加颜色属性,如上一节课学习的那样

  const position = new Float32Array(parameters.count * 3)
  const colors = new Float32Array(parameters.count * 3)
  for (let i = 0; i < parameters.count; i += 1) {

    ...

    colors[i3] = 1
    colors[i3 + 1] = 0
    colors[i3 + 2] = 0
  }
  geometry.setAttribute('color', new THREE.BufferAttribute(colors, 3))

接下来设置渐变色

.lerp ( color : Color, alpha : Float ) : this

color - 用于收敛的颜色。 alpha - 介于0到1的数字。

将该颜色的RGB值线性插值到传入参数的RGB值。alpha参数可以被认为是两种颜色之间的比例值,其中0是当前颜色和1.0是第一个参数的颜色。

.lerpColors ( color1 : Color, color2 : Color, alpha : Float ) : this

color1 - 开始的颜色。 color2 - 结束收敛的颜色。 alpha - 介于0到1的数字。

    const mixedColor = colorInside.clone()
    mixedColor.lerp(colorOutside, radius / parameters.radius)

    ...

    colors[i3] = mixedColor.r
    colors[i3 + 1] = mixedColor.g
    colors[i3 + 2] = mixedColor.b

最终效果如下

在线 demo 链接

可扫码访问

demo 源码

小结

本节我们学习了如何设置各种debug参数,来调节我们的银河效果。对粒子特效有了更深入的研究。动画部分我们会在后续的课程中再详细学习。


Similar Posts

Comments