threejs-16:galaxy-generate

1.准备工作

创建一个星系工厂函数

1
2
3
4
5
6
7
/*
Galaxy generate
*/
const generateGalxy = () => {
console.log("generate the galaxy")
}

创建一个 parameter 对象,将会包含星系所有的参数

1
2
3
const parameter = {}

parameter.count = 1000 //1000个粒子

首先创建随机粒子

1
2
3
4
5
6
7
8
9
10
11
12
const generateGalxy = () => {
const geometry = new THREE.BufferGeometry()
const position = new Float32Array(parameter.count * 3)

for(let i = 0; i < parameter.count ; i++){
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))
}

创建 PointsMaterial 类并添加 size 属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
parameter.size = 0.02
const generateGalxy = () => {
//...
const material = new THREE.PointsMaterial({
size: parameter.size,
sizeAttenuation: true,
depthWrite: false,
blending: THREE.AdditiveBlending
})

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

为 tweaks 添加调试

1
2
gui.add(parameters, 'count').min(100).max(100000).step(100).onFinishChange(DreamFusion)
gui.add(parameters, 'size').min(0.001).max(0.1).step(0.001).onFinishChange(DreamFusion)

每次更新tweaks时会使粒子重复生成造成内存飙升,所以我们需要处理一下这个问题:将 geometry,material,points 变量移到 generateGalaxy函数外面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
let geometry = null
let material = null
let points = null

const generateGalaxy = () => {
//...
geometry = new THREE.BufferGeometry()

//...

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

然后在赋值之前,我们先测试它们是否存在,若存在则使用 dispose() 方法销毁几何体和材质,并用 remove() 方法把点从场景中移除

1
2
3
4
5
6
7
const generateGalaxy = () => {
if(points != null) {
geometry.dispose()
material.dispose()
scene.remove(points)
}
}

2.造型

接下来创造一个螺旋星系

添加 radius 属性

1
2
3
parameter.radius = 5    //星系半径
//..
gui.add(parameters, 'radius').min(0.001).max(20).step(0.001).onFinishChange(DreamFusion)

先从简单的开始:创建从中心向外的几条射线

1
2
3
4
5
6
7
    for (let i = 0; i < parameter.count; i++) {
const i3 = i * 3
const radius = Math.random() * parameter.radius
position[i3] = radius
position[i3 + 1] = 0
position[i3 + 2] = 0
}

然后创建星系的其它分支

1
2
3
4
//...
parameter.branches = 3
gui.add(parameters, 'branches').min(2).max(20).step(1).onFinishChange(DreamFusion)
//...

将粒子放在分支上

1
2
3
4
5
6
7
8
    for (let i = 0; i < parameter.count; i++) {
const i3 = i * 3
const radius = Math.random() * parameter.radius
const branchAngle = (i % parameter.branches) / parameter.branches * Math.PI * 2
position[i3] = Math.cos(branchAngle) * radius
position[i3 + 1] = 0
position[i3 + 2] = Math.sin(branchAngle) * radius
}

3.旋转星系

添加 spin 属性

1
2
parameter.spin = 1
gui.add(parameters, 'spin').min(-5).max(5).step(0.01).onFinishChange(DreamFusion)

spinspinAngle 以旋转粒子

1
2
3
4
5
6
7
8
9
    for (let i = 0; i < parameter.count; i++) {
const i3 = i * 3
const radius = Math.random() * parameter.radius
const spinAngle = radius * parameter.spin
const branchAngle = (i % parameter.branches) / parameter.branches * Math.PI * 2
position[i3] = Math.cos(branchAngle + spinAngle) * radius
position[i3 + 1] = 0
position[i3 + 2] = Math.sin(branchAngle + spinAngle) * radius
}

4.添加随机性

添加 randomeness 属性

1
2
parameter.randomness = 0.2
gui.add(parameters, 'randomness').min(0).max(2).step(0.001).onFinishChange(DreamFusion)

我们想让粒子随机的分布在分支周围,就要给每个轴创建一个随机值,并用它乘 radiusrandomness

1
2
3
4
5
6
7
8
9
10
11
12
13
14
    for (let i = 0; i < parameter.count; i++) {
const i3 = i * 3
const radius = Math.random() * parameter.radius

const spinAngle = radius * parameter.spin
const branchAngle = (i % parameter.branches) / parameter.branches * Math.PI * 2

const randomX = Math.random() * parameter.randomness * radius
const randomY = Math.random() * parameter.randomness * radius
const randomZ = Math.random() * parameter.randomness * radius
position[i3] = Math.cos(branchAngle + spinAngle) * radius + randomX
position[i3 + 1] = randomY
position[i3 + 2] = Math.sin(branchAngle + spinAngle) * radius + randomZ
}

注意,此时随机位置是从0到1,我们最好把随机值域改为[-0.5, 0.5]

1
2
3
4
5
//...
const randomX = (Math.random() - 0.5) * parameter.randomness * radius
const randomY = (Math.random() - 0.5) * parameter.randomness * radius
const randomZ = (Math.random() - 0.5) * parameter.randomness * radius
//...

但实际上,随机的粒子多了后会呈现整体变为一个“正方体”,我们需要的效果是中心多,四周少的效果。所以我们需要使用加权的方式获得非线性的随机数

添加 randomnessPower 属性

1
2
parameter.randomnessPower = 3
gui.add(parameters, 'randomnessPower').min(1).max(10).step(0.001).onFinishChange(DreamFusion)
1
2
3
4
5
//...
const randomX = Math.pow(Math.random(), parameter.randomnessPower) * (Math.random() < 0.5 ? 1 : -1)
const randomY = Math.pow(Math.random(), parameter.randomnessPower) * (Math.random() < 0.5 ? 1 : -1)
const randomZ = Math.pow(Math.random(), parameter.randomnessPower) * (Math.random() < 0.5 ? 1 : -1)
//...

5.添加颜色

我们想让粒子从内到外做出一个渐变。添加 innerColoroutsideColor 属性

1
2
3
4
5
parameter.innerColor = '#ff6030'
parameter.outsideColor = '#1b3984'

gui.addColor(parameter, "innerColor").onFinishChange(generateGalxy)
gui.addColor(parameter, "outsideColor").onFinishChange(generateGalxy)

然后再PointMaterial中开启 vertexColors

1
2
3
4
5
6
7
material = new THREE.PointsMaterial({
size: parameter.size,
sizeAttenuation: true,
depthWrite: false,
blending: THREE.AdditiveBlending,
vertexColors: true
})

geometry 添加 color 属性

1
2
3
4
5
6
7
8
9
10
11
12
13
geometry = new THREE.BufferGeometry()
const position = new Float32Array(parameter.count * 3)
const colors = new Float32Array(parameter.count * 3)

for(let i = 0; i < parameter.count ; i++){
//...
//color
colors[i3] = 1
colors[i3] = 0
colors[i3] = 0
geometry.setAttribute('color', new THREE.BufferAttribute(colors, 3))
}

然后分别给内部颜色与外部颜色建立 Color 实例

1
2
3
4
const generateGalaxy = () => {
const colorInside = new THREE.Color(parameter.insideColor)
const colorOutside = new THREE.Color(parameter.outColor)
}

创建第三个 Color 实例,并使用 lerp()进行插值

1
2
3
4
5
6
7
8
const mixedColor = colorInside.clone()  //防止原色污染
mixedColor.lerp(colorOutside, radius / parameters.radius)
//...
//color
colors[i3] = mixedColor.r
colors[i3] = mixedColor.g
colors[i3] = mixedColor.b


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!