three.js学习笔记(八)——创建星系

本文介绍如何使用GUI面板调整星系生成器的参数,如粒子数量、大小、形状等,通过随机性和幂律分布实现高随机性的粒子效果。关键步骤包括创建参数对象、动态生成星系并管理内存,以及利用THREE.js库实现3D螺旋星系模型。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

本次我们将创建一个星系并且能够使用GUI面板来调整各项属性参数

基本粒子

创建generateGalaxy函数

/**
 * Galaxy
 */
const generateGalaxy = ()=>{

}

generateGalaxy()

创建parameters对象

对象里面将存放所有关于星系的参数,后面这些参数将被加到GUI面板中

const parameters = {}
// 粒子总数
parameters.count = 1000
// 粒子大小
parameters.size = 0.02

创建随机粒子

在generateGalaxy函数中

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

  /***
   * Material
   */
  const material = new THREE.PointsMaterial({
      //粒子大小
      size:parameters.size,
      //开启尺寸衰减
      sizeAttenuation:true,
      //停用depthWrite
      depthWrite:false,
      //激活混合
      blending:THREE.AdditiveBlending
  })

  /**
   * Points
   */
  const points = new THREE.Points(geometry,material)
  scene.add(points)

面板参数

gui.add(parameters,'count').min(100).max(10000).step(100).name('粒子总数').onFinishChange(generateGalaxy)
gui.add(parameters,'size').min(0.001).max(0.1).step(0.001).name('粒子大小').onFinishChange(generateGalaxy)

在这里插入图片描述
当我们把参数往大了的方向调整完后,再往小的方向调整回去,会发现粒子的总数还是不断增加,这是因为我们每次调整参数都会调用一次generateGalaxy函数,而没去把前面渲染出来的粒子从场景中移除。
很简单,几何体、材质和点我们在外部定义而不是在函数内部

let geometry = null
let material = null
let points = null

之后在generateGalaxy函数开头对点数进行判断。dispose方法是从内存中销毁对象,每次调用函数生成新星系时,如果点数不为空,那么我们从内存中销毁几何体和材质,然后把点从场景里移除出去,接下去再去新生成点

/**
   * 移除旧星系
   */
  if(points !== null){
      geometry.dispose()
      material.dispose()
      scene.remove(points)
  }  

星系形状

我们将创建一个螺旋星系,像下图这种
在这里插入图片描述

半径

添加半径参数并添加至面板中

parameters.radius = 5
......
gui.add(parameters,'radius').min(0.01).max(20).step(0.01).name('星系半径').onFinishChange(generateGalaxy)

首先先更改点的坐标,使其位于一条直线上,从中心开始直至半径距离

在循环体中修改点坐标

	const radius = Math.random() * parameters.radius
    positions[i3] = radius       // x
    positions[i3 + 1] = 0   // y
    positions[i3 + 2] = 0   // z

在这里插入图片描述

之后创建多条半径分支

添加分支参数并添加至面板中

parameters.branches = 3
......
gui.add(parameters,'branches').min(2).max(20).step(1).name('星系分支').onFinishChange(generateGalaxy)

以三条分支为例,先确定分支角度,因为只有三条分支,所以分支的角度无非就是0°,120°和240°,可以借助i % 3不断循环得到0、1、2三个值,然后将之除以3,再乘2Π,便可得到三分支的所需要的角度了。之后再通过三角函数设置每个顶点的xz坐标,再乘以半径,便可得到下图效果

for (let i = 0; i < parameters.count; i++) {
    const i3 = i * 3
    // 粒子半径
    const radius = Math.random() * parameters.radius
    // 分支角度
    const branchAngle = (i % parameters.branches) / parameters.branches * Math.PI * 2
    //顶点位置
    positions[i3] = Math.cos(branchAngle) * radius// x
    positions[i3 + 1] = 0 // y
    positions[i3 + 2] = Math.sin(branchAngle) * radius // z
  }

在这里插入图片描述

螺旋

添加螺旋参数并添加至面板中

parameters.spin = 1
...... 
gui.add(parameters, 'spin').min(-5).max(5).step(0.001).name('螺旋').onFinishChange(generateGalaxy)

距离中心越远的点旋转角度越大

for (let i = 0; i < parameters.count; i++) {
    const i3 = i * 3
    // 粒子半径
    const radius = Math.random() * parameters.radius
    // 分支角度
    const branchAngle = (i % parameters.branches) / parameters.branches * Math.PI * 2
    // 螺旋角度
    const spinAngle = radius * parameters.spin
    //顶点位置
    positions[i3] = Math.cos(branchAngle + spinAngle) * radius// x
    positions[i3 + 1] = 0 // y
    positions[i3 + 2] = Math.sin(branchAngle + spinAngle) * radius // z
  }

在这里插入图片描述

随机性

添加随机性参数并添加至面板中

parameters.randomness = 0.2
......
gui.add(parameters, 'randomness').min(0).max(2).step(0.001).name('随机性').onFinishChange(generateGalaxy)

我们希望粒子沿着分支方向向外随机扩散。首先要先为每个轴创建一个随机值,并将其乘以半径和随机性参数,然后设置给position

	const randomX = (Math.random()-0.5) * parameters.randomness
    const randomY = (Math.random()-0.5) * parameters.randomness
    const randomZ = (Math.random()-0.5) * parameters.randomness

    //顶点位置
    positions[i3] = Math.cos(branchAngle + spinAngle) * radius + randomX // x
    positions[i3 + 1] = 0 + randomY // y
    positions[i3 + 2] = Math.sin(branchAngle + spinAngle) * radius + randomZ // z

在这里插入图片描述
观察上图可以看出有点效果,但是还是达不到我们想要的那种高随机性的粒子扩散分布,这是因为我们的随机数总是处在一个固定范围内,分支上这些点的整体分布是接近线性增长曲线的分布形,而我们要的是类似指数增长曲线的分布形。

创建随机性幂参数并添加到面板中

parameters.randomnessPower = 3
......
gui.add(parameters, 'randomnessPower').min(1).max(10).step(0.001).name('随机性幂').onFinishChange(generateGalaxy)
	const randomX = Math.pow(Math.random(),parameters.randomnessPower)*(Math.random() < 0.5 ? 1 : -1 )
    const randomY = Math.pow(Math.random(),parameters.randomnessPower)*(Math.random() < 0.5 ? 1 : -1 )
    const randomZ = Math.pow(Math.random(),parameters.randomnessPower)*(Math.random() < 0.5 ? 1 : -1 )

在这里插入图片描述

颜色

我们想要里面的粒子是一种颜色,外面的粒子是一种颜色
创建内颜色和外颜色参数并添加到面板中

parameters.insideColor = '#ff6030'
parameters.outsideColor = '#1b3984'
gui
  .addColor(parameters, 'insideColor')
  .name('内颜色')
  .onFinishChange(generateGalaxy)
gui
  .addColor(parameters, 'randomnessPower')
  .name('外颜色')
  .onFinishChange(generateGalaxy)

设置材质的vertexColors属性为true开启顶点着色

	//激活顶点着色
    vertexColors:true

实例化内外颜色,并设置几何体的color属性

  const colorInside = new THREE.Color(parameters.insideColor)
  const colorOutside = new THREE.Color(parameters.outsideColor)
  ......
  geometry.setAttribute('color',new THREE.BufferAttribute(colors,3))

在循环体内,通过克隆内颜色colorInside来创建第三个Color类实例mixedColor,然后使用lerp()方法将之与外颜色colorOutside混合。之所以克隆内颜色是因为lerp方法会改变原有的值。
.lerp ( color : Color, alpha : Float ) : this
color - 用于收敛的颜色。
alpha - 介于0到1的数字。
将该颜色的RGB值线性插值到传入参数的RGB值。alpha参数可以被认为是两种颜色之间的比例值,其中设置为 0 时是当前颜色,设置为 1 时是第一个参数用于收敛的颜色。
我们想要的是越趋近于星系中心,粒子颜色越趋于内颜色colorInside,越远离星系中心,粒子颜色越趋近于外颜色colorOutside,因此lerp方法的第二个参数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

最终效果

在这里插入图片描述

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值