three.js GPGPU SPH lineSegments, gui

728x90
반응형

 

파티클 큐브 생성

무작위 배열로 설정하였지만, 그래도 cube의 각 cell안에 

들어있는 형태로 변경해보자. 

function spawnParticlesInBox(
  particleMesh: THREE.InstancedMesh,
  numToSpawn: THREE.Vector3,  // x, y, z로 몇 개씩 스폰할지 설정
  spawnCenter: THREE.Vector3, // 스폰할 박스의 중심
  particleRadius: number,     // 입자의 반지름
  spawnJitter: number         // 입자가 얼마나 엇나가도록 할지 설정 (jitter)
) {
  let index = 0;

  for (let x = 0; x < numToSpawn.x; x++) {
      for (let y = 0; y < numToSpawn.y; y++) {
          for (let z = 0; z < numToSpawn.z; z++) {
              // 입자의 위치 계산
              const spawnPos = new THREE.Vector3(
                  spawnCenter.x + x * particleRadius * 2,
                  spawnCenter.y + y * particleRadius * 2,
                  spawnCenter.z + z * particleRadius * 2
              );

              // Jitter 적용 (무작위로 약간 엇나가게 설정)
              const jitter = new THREE.Vector3(
                  (Math.random() - 0.5) * 2 * particleRadius * spawnJitter,
                  (Math.random() - 0.5) * 2 * particleRadius * spawnJitter,
                  (Math.random() - 0.5) * 2 * particleRadius * spawnJitter
              );

              spawnPos.add(jitter); // Jitter를 스폰 위치에 추가

              // 각 인스턴스의 변환 행렬 설정
              const matrix = new THREE.Matrix4();
              matrix.setPosition(spawnPos);

              // InstancedMesh에 행렬 적용
              particleMesh.setMatrixAt(index, matrix);
              index++;
          }
      }
  }

  // InstancedMesh를 업데이트하여 변경 사항을 반영
  particleMesh.instanceMatrix.needsUpdate = true;
}

여기서 jitter를 0으로 하고 출력하면,

 

반듯하게 나온다.

다만 여백이 너무 넓다. 

 

입자간 거리를 0.1로 설정하고,

material을 phong shading으로 바꿨다. 

 

gizmo 그리기 

unity의 gizmo.

즉 디버깅에서 영역을 보기 쉽게하는 선을 그린다.

 

three.js에서는 material을 line으로 하는 mesh를 생성하면 된다.

 

three.js lineSegments

import * as THREE from 'three'

// 직육면체의 8개 꼭짓점 정의 (x, y, z 좌표)
const vertices = [
  -1, -1, -1,  // 0번 점
   1, -1, -1,  // 1번 점
   1,  1, -1,  // 2번 점
  -1,  1, -1,  // 3번 점
  -1, -1,  1,  // 4번 점
   1, -1,  1,  // 5번 점
   1,  1,  1,  // 6번 점
  -1,  1,  1   // 7번 점
];

// 각 점을 연결하는 선분 정의 (점의 인덱스)
const edges = [
  0, 1,  1, 2,  2, 3,  3, 0,  // 밑면
  4, 5,  5, 6,  6, 7,  7, 4,  // 윗면
  0, 4,  1, 5,  2, 6,  3, 7   // 옆면
];

export function drawBoundary(scene: THREE.Scene){
  const lineGeometry = new THREE.BufferGeometry()
  lineGeometry.setAttribute('position', new THREE.Float32BufferAttribute(vertices,3))
  lineGeometry.setIndex(edges);
  const wireCube = new THREE.LineSegments(lineGeometry, 
    new THREE.MeshBasicMaterial({color: 'purple'}));
  scene.add(wireCube)
}

이렇게 큐브 모양의 gizmo를 대체하여 선을 그렸는데,

저거 파티클이 거슬린다.

gui로 키고 끌 수 있게 해보자. 

lil-gui

import GUI from "lil-gui";
const gui = new GUI();

export function init(){
}

export function addBoolean(name: string, value: boolean){
  const object = {[name]: value};
  gui.add(object, name);
}

gui 스크립트를 추가해서 on off 기능을 넣었다.

참고로 object의 key를 변수의 string 값을 받아서 넣는 방법이 대괄호라는 것을 알았다. 

 

이렇게 만들었는데.

boolean값을 다른 스크립트에서 변경하는게 문제다.

다양한 방법이 있는데. 그냥 SPH object를 class로 만들어서 전달하는 형식으로 리팩토링하는게 좋겠다.

SPH class

import * as THREE from 'three'
import { GPUComputationRenderer } from 'three/examples/jsm/misc/GPUComputationRenderer';
import { Particle } from './particle';

export class SPH{
  //#region SPH variable - general
  public showSpheres: boolean = true;
  public numToSpawn: THREE.Vector3 = new THREE.Vector3(10, 10, 10);
  public totalParticles: number = this.numToSpawn.x * this.numToSpawn.y * this.numToSpawn.z;
  public spawnCenter: THREE.Vector3 = new THREE.Vector3(0, 0, 0);
  public particleRadius: number = 0.1;
  //#endregion

  //#region SPH variable - particle rendering
  public particleMesh: THREE.Mesh;
  public particleRenderSize: number = 8;
  public material: THREE.Material;
  //#endregion

  //#region SPH variable - compute
  public gpuCompute: GPUComputationRenderer;
  public dtPosition: THREE.DataTexture;
  public positionVariable: any;
  public particles: Particle[];
  //#endregion

  //#region SPH variable - compute
  // const gpuCompute = new GPUComputationRenderer(canvas.clientWidth,  canvas.clientHeight, renderer)
  // const dtPosition = gpuCompute.createTexture();
  // const positionVariable = gpuCompute.addVariable('uCurrentPosition', computefragment, dtPosition )
  // gpuCompute.setVariableDependencies(positionVariable, [positionVariable])
  // gpuCompute.init()
  // let particles: Particle[];
  //#endregion

  constructor(private canvas: HTMLElement, private renderer: THREE.WebGLRenderer) {
    this.particleMesh = new THREE.Mesh();

    this.gpuCompute = new GPUComputationRenderer(this.canvas.clientWidth, this.canvas.clientHeight, this.renderer);
  }

  public setShowSpheres(value: boolean){
    this.showSpheres = value
    if(this.showSpheres)
      this.particleMesh.visible = true;
    else
      this.particleMesh.visible = false;
    console.log(this.showSpheres)
  }
  public setShader(computefragment: string){
    this.dtPosition = this.gpuCompute.createTexture();
    this.positionVariable = this.gpuCompute.addVariable('uCurrentPosition', computefragment, this.dtPosition);
    this.gpuCompute.setVariableDependencies(this.positionVariable, [this.positionVariable]);

    this.gpuCompute.init();
  }
}

위와 같이 SPH class를 따로 준비하고 

 

  public setShowSpheres(value: boolean){
    this.showSpheres = value
    if(this.showSpheres)
      this.particleMesh.visible = true;
    else
      this.particleMesh.visible = false;
    console.log(this.showSpheres)
  }

three.js에서는 매 프레임마다 파티클을 그려내는게 아니므로,

particle mesh의 visible로 조정하기로 하였다. 

 

debugGui.init();
debugGui.setShowSphere('showSpheres', SPH.showSpheres, SPH);

gui에 SPH객체를 전달하고 

 

export function setShowSphere(name: string, value: boolean, SPH: _SPH){
  const object = {[name]: value};
  gui.add(object, name).onChange(value =>{
    SPH.setShowSpheres(value);
  })
}

gui 스크립트에서 SPH 객체를 전달받아 show sphere 함수를 실행한다. 

 

이제 gui를 이용해서 on off 를 할 수 있다. 

728x90
반응형
  • 네이버 블로그 공유
  • 네이버 밴드 공유
  • 페이스북 공유
  • 카카오스토리 공유