Hi,
I’ve started learning about Three.JS particles from articles, tutorials and looking at the code of others. I produced the following code (in React), which, from what I understood SHOULD be working.
I would like to ask you to review it with focus on why my particles are not visible at all. I suspect shaders, which I’m not fully understanding yet.
I would also appreciate any tips for optimization or good practices, since it’s my first project of sort.
componentDidMount = () => {
this.init();
}
init = () => {
const rand = (min,max) => min + Math.random()*(max-min);
const canvas = this.canvasRef.current;
this.color = new THREE.Color();
//scene
this.scene = new THREE.Scene();
// this.texture = this.generateTexture();
//renderer
this.renderer = new THREE.WebGLRenderer({ canvas, antialias: true, alpha: true });
this.renderer.setClearColor(0x666666);
this.renderer.setSize( window.innerWidth, window.innerHeight )
document.body.appendChild(this.renderer.domElement);
//camera
this.camera = new THREE.PerspectiveCamera(100, 0, 0.0001, 10000);
this.camera.position.z = 30;
this.camera.position.x = 0;
this.camera.position.y = 0;
this.camera.updateProjectionMatrix();
this.controls = new THREE.OrbitControls(this.camera, this.renderer.domElement);
this.controls.enableDamping = true;
this.controls.dampingFactor = 0.2;
this.controls.enableKeys = false;
this.gridHelper = new THREE.GridHelper(300, 60, 0xffffff, 0xffffff);
this.gridHelper.material.transparent = true;
this.gridHelper.material.opacity = .3;
this.scene.add(this.gridHelper);
this.axisHelper = new AxisHelper(150, .6);
this.scene.add(this.axisHelper);
this.camera.lookAt(new THREE.Vector3());
//lights
let hemiLight = new THREE.HemisphereLight(0xffffff, 0.91);
hemiLight.position.set(0, 50, 0);
// Add hemisphere light to scene
this.scene.add(hemiLight);
// geometry
this.geometry = new THREE.BufferGeometry();
this.particles = 10;
const radius = 200;
this.positions = new Float32Array(this.particles * 3);
this.colors = new Float32Array(this.particles * 3);
this.sizes = new Float32Array(this.particles);
this.size = 1000;
this.parts = [];
//vec3 attributes
this.geometry.setAttribute( 'position', new THREE.Float32BufferAttribute( this.positions, 3 ) );
this.geometry.setAttribute( 'color', new THREE.Float32BufferAttribute( this.colors, 3 ) );
this.geometry.setAttribute( 'size', new THREE.Float32BufferAttribute( this.sizes, 1 ));
for ( var i = 0; i < this.particles; i ++ ) {
let size = rand(10, 80);
this.color.setHSL( i / this.particles, 1.0, 0.5 );
let part = {
offset: 0,
position: new THREE.Vector3(
rand(-this.size / 2, this.size / 2),
rand(-this.size / 2, this.size / 2),
rand(-this.size / 2, this.size / 2)
),
baseSize: size,
size: size,
r: this.color.r,
g: this.color.g,
b: this.color.b,
a: 0.6,
life: 2,
decay: rand(0.05, 0.15),
firstRun: true
};
this.parts.push(part);
}
this.material = new THREE.ShaderMaterial( {
uniforms: {
pointTexture: { value: new THREE.TextureLoader().load( "../3d/particle.png" ) }
},
vertexShader: particlesVertexShader,
fragmentShader: particlesFragmentShader,
blending: THREE.AdditiveBlending,
depthTest: false,
transparent: true,
vertexColors: true
});
this.particleSystem = new THREE.Points(this.geometry, this.material);
this.scene.add(this.particleSystem);
this.updateParticleAttributes(true, true, true);
window.addEventListener( 'resize', this.onWindowResize, false );
this.update();
}
generateTexture=() =>{
let c = document.createElement('canvas');
let ctx = c.getContext('2d');
let size = 256;
c.width = size;
c.height = size;
let gradient = ctx.createRadialGradient(size * 0.5, size * 0.5, 0, size * 0.5, size * 0.5, size * 0.4);
gradient.addColorStop(0, 'hsla(100, 80%, 60%, 1)');
gradient.addColorStop(1, 'hsla(100, 80%, 60%, 0.6 )');
ctx.beginPath();
ctx.arc(size / 2, size / 2, size / 2, 0, Math.PI * 2);
ctx.fillStyle = gradient;
ctx.fill();
let texture = new THREE.Texture(c);
texture.needsUpdate = true;
return texture;
}
updateParticleAttributes=(color, position, size) =>{
let i = 0;
while(i<this.particles) {
let part = this.parts[i];
if(color) {
this.colors[i * 4 + 0] = part.r;
this.colors[i * 4 + 1] = part.g;
this.colors[i * 4 + 2] = part.b;
}
if(position) {
this.positions[i * 3 + 0] = part.position.x;
this.positions[i * 3 + 1] = part.position.y;
this.positions[i * 3 + 2] = part.position.z;
}
if(size) {
this.sizes[i] = part.size;
}
i++;
}
if(color) {
this.geometry.attributes.color.needsUpdate = true;
}
if(position) {
this.geometry.attributes.position.needsUpdate = true;
}
if(size) {
this.geometry.attributes.size.needsUpdate = true;
}
this.geometry.computeBoundingSphere();
}
update=()=>{
requestAnimationFrame(this.update);
const rand = (min,max) => min + Math.random()*(max-min);
let noiseTime = this.clock.getElapsedTime() * 0.0008;
let noiseVelocity = this.simplex.noise2D(0,1);
const noiseScale = 0.01;
for ( var i = 0; i < this.particles; i ++ ) {
let part = this.parts[i];
// let xScaled = part.position.x * noiseScale;
// let yScaled = part.position.y * noiseScale;
// let zScaled = part.position.z * noiseScale;
// let noise1 = this.simplex.noise4D(
// xScaled ,
// yScaled,
// zScaled,
// 50 + noiseTime
// )* 0.5 + 0.5;
// let noise2 = this.simplex.noise4D(
// xScaled +100,
// yScaled+100,
// zScaled+100,
// 50 + noiseTime
// )* 0.5 + 0.5;
// let noise3 = this.simplex.noise4D(
// xScaled +200,
// yScaled+200,
// zScaled+200,
// 50 + noiseTime
// )* 0.5 + 0.5;
// part.position.x += Math.sin(noise1 * Math.PI * 2) * this.clock.getDelta() * noiseVelocity;
// part.position.y += Math.sin(noise2 * Math.PI * 2) * this.clock.getDelta() * noiseVelocity;
// part.position.z += Math.sin(noise3 * Math.PI * 2) * this.clock.getDelta() * noiseVelocity;
// this.parts[i] = part;
if(part.life <= 0 || part.firstRun) {
part.life = 2;
part.position.x = rand(-this.size / 2, this.size / 2);
part.position.y = rand(-this.size / 2, this.size / 2);
part.position.z = rand(-this.size / 2, this.size / 2);
let hue = (this.clock.elapsedTime / 25 + rand(90)) % 360 + 110;
let lightness = Math.round(rand(10, 50));
this.color.set(`hsl(${hue}, 85%, ${lightness}%)`);
part.r = parseInt(this.color.r)+100;
part.g = parseInt(this.color.g);
part.b = parseInt(this.color.b);
part.firstRun = false;
}
this.parts[i] = part;
}
this.updateParticleAttributes(true, true, true);
this.renderer.render(this.scene, this.camera);
}
onWindowResize=()=> {
this.camera.aspect = window.innerWidth / window.innerHeight;
this.camera.updateProjectionMatrix();
this.renderer.setSize( window.innerWidth, window.innerHeight );
}
render=()=>{
return <div>
<canvas ref={ref=>{this.canvasRef = ref}}></canvas>
</div>
}
In the code above the animating part is commented since I was trying to make it work on the inital render first. Animation does not work either, however I can see it is calculating in Performace tab.
The Vertex shader:
export default 'attribute float size; \
varying vec3 vColor; \
void main() {\
vColor = color;\
vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );\
gl_PointSize = size * ( 300.0 / -mvPosition.z );\
gl_Position = projectionMatrix * mvPosition;\
}';
The fragment shader:
export default 'uniform sampler2D pointTexture;\
varying vec3 vColor;\
void main() {\
gl_FragColor = vec4( vColor, 0.8 );\
gl_FragColor = gl_FragColor * texture2D( pointTexture, gl_PointCoord ); \
}';
Many thanks in advance.