🙌🏻 해당 글은 Three.js Journey의 강의 노트입니다.
우선 generateGalaxy 함수를 만듭니다. 함수를 실행할 때마다 하나의 은하를 만들 수 있도록 합니다.
galaxy의 모든 매개 변수를 포함할 객체를 만듭니다.
const parameters = {};
parameters.count = 1000;
parameters.size = 0.02;
const 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;
positions[i3 + 1] = (Math.random() - 0.5) * 3;
positions[i3 + 2] = (Math.random() - 0.5) * 3;
}
geometry.setAttribute("position", new THREE.BufferAttribute(positions, 3));
/**
* Material
*/
const material = new THREE.PointsMaterial({
size: parameters.size,
sizeAttenuation: true,
depthWrite: false,
blending: THREE.AdditiveBlending,
});
/**
* Points
*/
const points = new THREE.Points(geometry, material);
scene.add(points);
};
generateGalaxy();
parameters의 값을 변경할 수 있도록 GUI 툴에 추가를 합니다.
GUI툴로 값을 변경하면서 새로운 은하가 생성되는 것을 살펴볼텐데, 중요한 점은 새로 만들 때 기존에 있던 은하를 삭제해주어야 하는 점에 유의해야 한다.
// 매번 새로 생성해주어야 하므로, 아래 변수들은 함수 밖으로 꺼내준다.
let geometry = null;
let material = null;
let points = null;
// ...
const generateGalaxy = () => {
// Destroy old galaxy
if (points !== null) {
geometry.dispose();
material.dispose();
scene.remove(points);
}
//...
}
generateGalaxy();
gui
.add(parameters, "count")
.min(100)
.max(1000000)
.step(100)
.onFinishChange(generateGalaxy);
gui
.add(parameters, "size")
.min(0.001)
.max(0.1)
.step(0.001)
.onFinishChange(generateGalaxy);
사실 particles를 만들다보면 가장 처음에 의문이 드는 점은 왜 BoxGeometry의 형태로 구성이 되는가 하는 점일 것이다. 그러나 다양한 모양으로 이를 구성해줄 수 있다.
radius라는 속성을 parameters에 만들어준다.
radius를 설정함에 따라 모든 입자가 일단은 직선 위에 배치된다.
for(let i = 0; i < parameters.count; i++)
{
const i3 = i * 3
const radius = Math.random() * parameters.radius
positions[i3 ] = radius
positions[i3 + 1] = 0
positions[i3 + 2] = 0
}
뻗어나가는 가지를 만들어준다!
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
positions[i3 + 1] = 0
positions[i3 + 2] = Math.sin(branchAngle) * radius
}
은하의 모양처럼 보이도록 굴곡을 형성해준다!
for(let i = 0; i < parameters.count; i++)
{
const i3 = i * 3
const radius = Math.random() * parameters.radius
const spinAngle = radius * parameters.spin
const branchAngle = (i % parameters.branches) / parameters.branches * Math.PI * 2
positions[i3 ] = Math.cos(branchAngle + spinAngle) * radius
positions[i3 + 1] = 0
positions[i3 + 2] = Math.sin(branchAngle + spinAngle) * radius
}
완벽하게 정렬된 particles에 약간의 무작위성을 더해 퍼뜨려준다.
이 때 중심을 기준으로 모이는 모양을 만들어 주기 위해 randomnessPower라는 변수를 하나 더 생성해준다.
for(let i = 0; i < parameters.count; i++)
{
const i3 = i * 3
const radius = Math.random() * parameters.radius
const spinAngle = radius * parameters.spin
const branchAngle = (i % parameters.branches) / parameters.branches * Math.PI * 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
// const randomX = Math.pow(Math.random(), parameters.randomnessPower) * (Math.random() < 0.5 ? 1 : - 1) * parameters.randomness * radius
// const randomY = Math.pow(Math.random(), parameters.randomnessPower) * (Math.random() < 0.5 ? 1 : - 1) * parameters.randomness * radius
// const randomZ = Math.pow(Math.random(), parameters.randomnessPower) * (Math.random() < 0.5 ? 1 : - 1) * parameters.randomness * radius
positions[i3 ] = Math.cos(branchAngle + spinAngle) * radius + randomX
positions[i3 + 1] = randomY
positions[i3 + 2] = Math.sin(branchAngle + spinAngle) * radius + randomZ
}
우선 파라미터에 color를 추가해준다!
그리고나서 Geometry에 색상 속성을 추가한다.
geometry = new THREE.BufferGeometry()
const positions = new Float32Array(parameters.count * 3)
const colors = new Float32Array(parameters.count * 3)
for(let i = 0; i < parameters.count; i++)
{
// ...
colors[i3 ] = 1
colors[i3 + 1] = 0
colors[i3 + 2] = 0
}
geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3))
geometry.setAttribute('color', new THREE.BufferAttribute(colors, 3))
그 결과 붉은 색 galaxy를 얻을 수 있다!
이 다음으로는 은하 안쪽과 바깥쪽의 색상을 바꿔준다.
먼저 색상 인스턴스를 loop안에서 만들어준다.
일단 계획은 lerp메서드를 사용해서 안쪽 색상과 바깥쪽 색상을 중심에서의 거리에 따라 섞어주는 것이다.
const mixedColor = colorInside.clone()
mixedColor.lerp(colorOutside, radius / parameters.radius)
colors[i3 ] = mixedColor.r
colors[i3 + 1] = mixedColor.g
colors[i3 + 2] = mixedColor.b
와....