FBO ping-pong texture mapping issue R3F

Hello!!. I’m trying to make an interactive particle system using R3F and Frame Buffer Objects. So, at this point there should be a disc shape with particles. And it works too. But when I’m doing the ping-pong texture mapping in useFrame() and try to map the FBO textures, the disc shaped particles become one single square. I tried for days to fix it but couldn’t find what the issue is. It’d be a big help if someone can help me in this. I’ll also attach the project file so you can try it out if you need. Thank you in advance!!

Project:
portfolio.rar (204.8 KB)

import { useRef, useMemo, useState, useEffect } from 'react';
import { extend, useFrame, useThree, createPortal } from '@react-three/fiber';
import { OrthographicCamera, shaderMaterial, useFBO } from '@react-three/drei';
import * as THREE from 'three';

import simVertexShader from '@/shaders/simulation/simVertex.glsl';
import simFragmentShader from '@/shaders/simulation/simFragment.glsl';

import particleVertexShader from '@/shaders/simulation/particleVertex.glsl';
import particleFragmentShader from '@/shaders/simulation/particleFragment.glsl';

const Sim = () => {
  const pingPong = useRef(0);

  const simSize = 128;
  const count = simSize ** 2;

  // FBOs
  const fbo0 = useFBO({
    width: simSize,
    height: simSize,
    minFilter: THREE.NearestFilter,
    magFilter: THREE.NearestFilter,
    format: THREE.RGBAFormat,
    type: THREE.FloatType,
    stencilBuffer: false,
  });

  const fbo1 = useFBO({
    width: simSize,
    height: simSize,
    minFilter: THREE.NearestFilter,
    magFilter: THREE.NearestFilter,
    format: THREE.RGBAFormat,
    type: THREE.FloatType,
    stencilBuffer: false,
  });

  // FBO scene and camera
  const fboScene = useMemo(() => new THREE.Scene(), []);

  const fboCamera = useMemo(() => {
    const cam = new THREE.OrthographicCamera(-1, 1, 1, -1, -1, 1);
    cam.position.set(0, 0, 0.5);
    cam.lookAt(0, 0, 0);

    return cam;
  }, []);

  // FBO Texture Data
  const fboData = useMemo(() => {
    const data = new Float32Array(simSize * simSize * 4);

    for (let i = 0; i < simSize; i++) {
      for (let j = 0; j < simSize; j++) {
        let index = (i + j * simSize) * 4;

        let theta = Math.random() * Math.PI * 2;
        let rad = Math.random() * 0.5 + 0.5;

        data[index] = rad * Math.cos(theta);
        data[index + 1] = rad * Math.sin(theta);
        data[index + 2] = 1;
        data[index + 3] = 1;
      }
    }

    return data;
  }, [simSize]);

  // FBO Texture
  const fboTexture = useMemo(() => {
    const texture = new THREE.DataTexture(
      fboData,
      simSize,
      simSize,
      THREE.RGBAFormat,
      THREE.FloatType
    );
    texture.magFilter = THREE.NearestFilter;
    texture.minFilter = THREE.NearestFilter;
    texture.needsUpdate = true;

    return texture;
  }, [fboData, simSize]);

  // FBO Material
  const fboMaterial = useMemo(() => {
    const material = new THREE.ShaderMaterial({
      uniforms: {
        uTime: { value: 0 },
        uPositions: { value: fboTexture },
      },
      vertexShader: simVertexShader,
      fragmentShader: simFragmentShader,
      // transparent: true,
    });

    return material;
  }, []);

  // Object Material
  const objectMaterial = useMemo(() => {
    const material = new THREE.ShaderMaterial({
      uniforms: {
        uTime: { value: 0 },
        uPositions: { value: fboTexture },
      },
      vertexShader: particleVertexShader,
      fragmentShader: particleFragmentShader,
      // transparent: true,
      transparent: true,
depthWrite: false,
blending: THREE.AdditiveBlending
    });

    return material;
  }, []);

  // Particle Geometry
  const geometry = new THREE.BufferGeometry();
  const positions = new Float32Array(count * 3);
  const uv = new Float32Array(count * 2);

  useEffect(() => {
    for (let i = 0; i < simSize; i++) {
      for (let j = 0; j < simSize; j++) {
        let index = i + j * simSize;

        positions[index * 3] = Math.random();
        positions[index * 3 + 1] = Math.random();
        positions[index * 3 + 2] = 0;

        uv[index * 2 + 0] = i / simSize;
        uv[index * 2 + 1] = j / simSize;
      }
    }

    geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
    geometry.setAttribute('uv', new THREE.BufferAttribute(uv, 2));

    return () => {
      geometry.dispose();
    };
  }, [simSize]);

  // Render
  useFrame((state, delta) => {
    const { gl, clock } = state;

    const readFBO = pingPong.current % 2 === 0 ? fbo0 : fbo1;
    const writeFBO = pingPong.current % 2 === 0 ? fbo1 : fbo0;

    fboMaterial.uniforms.uTime.value += 0.05;
    objectMaterial.uniforms.uTime.value += 0.05;

    fboMaterial.uniforms.uPositions.value = readFBO.texture;
    objectMaterial.uniforms.uPositions.value = writeFBO.texture;

    gl.setRenderTarget(writeFBO);
    gl.clear();
    gl.render(fboScene, fboCamera);
    gl.setRenderTarget(null);

    // Swap FBOs for next frame
    pingPong.current = (pingPong.current + 1) % 2;
  });

  return (
    <>
      {createPortal(
        <mesh material={fboMaterial} geometry={geometry} />,
        fboScene
      )}

      <points material={objectMaterial} geometry={geometry} />
    </>
  );
};

export default Sim;

//simFragment

float PI = 3.141592653589793238;

uniform float uTime;
uniform float uProgress;
uniform sampler2D uPositions;
uniform vec4 uResolution;
uniform vec2 uPixels;

varying vec2 vUv;
varying vec3 vPosition;

void main() {
  vec4 pos = texture2D(uPositions, vUv);

  float radius = length(pos.xy);
  float angle = atan(pos.y, pos.x) - 0.1;

  vec3 targetPos = vec3(cos(angle), sin(angle), 0.0) * radius;

  // pos.xy += (targetPos.xy - pos.xy) * 0.1;
  pos.xy += 0.001;
  
  gl_FragColor = vec4(pos.xyz, 1.0);
}
//particleVertex

float PI = 3.141592653589793238;

uniform float uTime;
uniform vec2 uPixels;
uniform sampler2D uPositions;

varying vec2 vUv;
varying vec3 vPosition;


void main(){
  vUv = uv;
    
  vec4 pos = texture2D(uPositions, uv);

  vec4 mvPosition = modelViewMatrix * vec4(pos.xyz, 1.0);

  gl_PointSize = 10.0 * (1.0 / -mvPosition.z);

  gl_Position = projectionMatrix * mvPosition;
}