GPU에서 처리되는 StorageBufferAttribute 생성 (Compute Shader)

수백만개의 BOIDS에 대한 데이터를 빠르게 처리하기 위한 방안 중 가장 최고의 선택은 GPU에서 처리하는 것입니다. 처리하고자 하는 BOID의 개수가 다음과 같다고 할때 최종적으로 GPU에서 읽고 쓸 수 있는 StorageBufferAttribute 객체를 생성하는 과정을 코드로 정리합니다.

const BOIDS = 9;

BOID의 위치값에 대한 데이터라고 한다면 먼저 Float32Array 객체를 생성합니다.

const positionArray = new Float32Array(BOIDS * 3);

이 배열 객체에 BOID의 정보를 채움니다. 아래는 격자 형태로 BOID들이 배치되도록 합니다.

const cellSize = 0.5;

for (let i = 0; i < BOIDS; i++) {
  const offset = i * 3;
  const row = (i % 3) - 1;
  const col = (~~(i / 3)) - 1;

  positionArray[offset + 0] = col * cellSize;
  positionArray[offset + 1] = row * cellSize;
  positionArray[offset + 2] = 0; // 이미 0으로 초기화 되어 있으므로 불필요함 코드
}

이렇게 만든 배열 객체를 StorageBufferAttribute로 생성하기 위한 코드는 다음과 같습니다.

const positionStorage = attributeArray(positionArray, "vec3");

이제 positionStorage를 통해 GPU에서 BOID의 위치를 읽고 쓸 수 있습니다.

GPU를 통해 positionStorage를 읽는 TSL 코드 예시는 다음과 같습니다.

const flockVertexTSL = Fn(() => {
  const instanceID = attribute("instanceID");
  const finalVert = modelWorldMatrix.mul(
    positionLocal.add(positionStorage.element(instanceID))
  ).toConst();
  return cameraProjectionMatrix.mul(cameraViewMatrix).mul(finalVert);
});

2번 코드는 positionStorage에 저장된 BOID 중 읽을 녀석에 대한 인덱스값입니다. 위의 경우는 별도의 attribute에 BOID의 인덱스를 저장해 사용하고 있습니다. flockVerteTSL은 재질의 vertexNode에 지정하면 됩니다.

만약 positionStorage에 저장된 BOID 들의 위치를 변경하고자 할때 GPU에서 수행할 수 있고 이를 Compute Shader를 통해 동시에 처리가 가능합니다. 아래는 해당 처리를 수행하는 GPU에서 실행되는 함수의 예시입니다.

const radius = uniform(float(0.7));
const delta = uniform(float(1));

const computePosition = Fn(() => {
  const PI2 = float(6.2832).toConst();
  const theta = PI2.div(BOIDS).toConst();
  const idx = instanceIndex.toConst();
  const posx = cos(time.add(theta.mul(idx))).mul(radius).toConst();
  const posy = sin(time.add(theta.mul(idx))).mul(radius).toConst();
  const cellSize = .5;
  const row = float(idx).mod(3.0).sub(1.0).toConst();
  const col = floor(float(idx).div(3.0)).sub(1.0).toConst();
  const v1 = vec3(posx, posy, 0).toConst();
  const v2 = vec3(col.mul(cellSize), row.mul(cellSize), 0).toConst();
  positionStorage.element(idx).assign(mix(v2, v1, delta));
})().compute(BOIDS);

위의 코드 중 instanceIndex는 처리되고 있는 BOID의 인덱스입니다. 코드의 마지막에 compute 매서드를 통해 인자로 받은 개수만큼 병렬로 동시에 실행되어질 수 있도록 해줍니다. 위에서 정의된 computePosition는 (필요하다면 필드화하여) 매 프레임마다 렌더링될 때 실행해줘야 하며 아래는 그 코드 예시입니다.

this._renderer.compute(this._computePosition);

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다