Unity Fluid Simulation
unity로 구현된 SPH fluid simulation을
GPU computation을 three.js 상의 GPGPU를 사용하는 방법으로 구현해볼 것이다.
과정을 하나하나 팔로우 하면서 차근차근해보자.
particle class
우선 파티클 객체를 만들어야한다.
기존 unity C#의 방식을 참고해서 three.js 에서 이용하는 방식으로 변경하는게
많은 부분에서 걸릴 것 같다.
vector3
class Particle{
pressure: number; // 4
density: number; // 8
currentForce: vec3
}
vector3를 선언하는데 벌써 고민이다.
gl-matrix의 vec3타입과 three.js의 Vector3 타입이 둘 다 있다.
두 방법에 큰 차이는 없겠지만, 계산 최적화에는
객체을 생성하지 않고 함수로 진행하는 gl-matrix가 적합하다.
SPH 변수
SPH에 필요한 변수들을 세팅하도록 하자.
compute shader buffer
unity의 compute shader
이걸 이제 GPUComputationRenderer로 어떻게 대처해야할지 고민이다.
//#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
아직 어떤 방식으로 shader를 사용할지 모르니 gpu compute로 정의만 해놓는다.
그리고 buffer라..
unity에서는 compute shader로 데이터를 전달하기 위해서 buffer를 사용하는데,
webgpu도 아닌 webgl기반의 three.js에서는 GPUComputationRenderer에 buffer를 사용해서 전달하지 않는다.
//#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[];
let _argsBuffer;
let _particlesBuffer;
//#endregion
예상에는 GPU compute에 addVariable 변수로 들어가는 포지션 같은데 일단 만들어만 놓자.
초기화
unity의 awake 함수에 초기화를 하는 동작들이 담겨있다.
여기서 문제가 있는데, particleMesh는 unityEngine의 Mesh 타입이다.
Engine 자체적인 타입을 three.js type으로 변경해야한다.
Mesh는 three.js에서 buffer geometry의 attribute로 접근해서 처리해보자.
우선 unity에서 mesh의 GetIndexCount가 뭐였더라.
args는 args buffer에 담기고, 결국 particle 입자들을 렌더링하기 위한 것이다.
여기에 맞추기 위해서 argsBuffer를 사용한 것인데, three.js에는 그리는 방법이 다르므로,
그냥 particle로 이루어진 box를 생성한다는 관점에서 코드를 짜야할 것 같다.
function initialize(){
// 파티클 인스턴스 생성
particleMesh.geometry = new THREE.SphereGeometry(0.1);
particleMesh.material = new THREE.MeshBasicMaterial({color: 'blue'});
particleMesh = new THREE.InstancedMesh(particleMesh.geometry, particleMesh.material,
1000);
scene.add(particleMesh)
}
initialize();
three.js에서는 unity처럼 일정한 buffer 정보를 가지고 그래픽API로 그려내는 동작을 하지 못한다.
그래서 여러 파티클을 instanced mesh를 사용해서 생성하도록 하자.
현재 1000개의 입자를 만들었지만, 각기 겹쳐있다.
function initialize(){
// 파티클 인스턴스 생성
particleMesh.geometry = new THREE.SphereGeometry(0.1);
particleMesh.material = new THREE.MeshBasicMaterial({color: 'blue'});
particleMesh = new THREE.InstancedMesh(particleMesh.geometry, particleMesh.material,
1000);
scene.add(particleMesh)
// 각 인스턴스에 대해 행렬(matrix) 설정
for (let i = 0; i < (particleMesh as THREE.InstancedMesh).count; i++) {
const matrix = new THREE.Matrix4();
matrix.setPosition(Math.random() * 10, Math.random() * 10, Math.random() * 10);
(particleMesh as THREE.InstancedMesh).setMatrixAt(i, matrix);
}
}
initialize();
이렇게 instanced mesh는 내부에 들어있는 인덱스 각각에 대해서
행렬로 위치를 변경해주면 된다.
이렇게 천 개의 입자들이 사각형 안에 랜덤으로 퍼져있도록 설정되었다.