[Three.js journey 강의노트] 18

9rganizedChaos·2021년 7월 8일
0
post-thumbnail

🙌🏻 해당 글은 Three.js Journey의 강의 노트입니다.

18 Galaxy Generator

Base particles

우선 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();

Tweaks

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);

Shape

사실 particles를 만들다보면 가장 처음에 의문이 드는 점은 왜 BoxGeometry의 형태로 구성이 되는가 하는 점일 것이다. 그러나 다양한 모양으로 이를 구성해줄 수 있다.

Radius

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
    }

Branches

뻗어나가는 가지를 만들어준다!

    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
    }

Spin

은하의 모양처럼 보이도록 굴곡을 형성해준다!

    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
    }

Randomness

완벽하게 정렬된 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
    }

Colors

우선 파라미터에 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

profile
부정확한 정보나 잘못된 정보는 댓글로 알려주시면 빠르게 수정토록 하겠습니다, 감사합니다!

1개의 댓글

comment-user-thumbnail
2023년 8월 3일

와....

답글 달기