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