I'm currently working an a project which will visualize data on browser by rendering excessive amounts of animated stroked circles. I started evaluating 3D libraries and ended up trying to create a proof of concept application with three.js. It is capable of animating and rendering up to 150 000 point sprites at 60 fps on my 1440p monitor. Everything looks great until you start looking at the details. It has two rendering issues:
- It creates strange horizontal lines even when you turn animation off
- When you turn the camera, transparent areas of overlapping point sprites will show the background instead of underlying point sprites
Here is the proof of concept application: https://jsfiddle.net/tcpvfbsd/1/
var renderer, scene, camera, controls;
var points;
var stats;
var controls;
var worldWidth = 200;
var worldRadius = worldWidth / 2;
var patchSize = 10;
var pointsAmount = 100000;
function init() {
renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
scene = new THREE.Scene();
scene.background = new THREE.Color(0x1d252d);
camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 2000);
camera.position.set(0, worldWidth * 1.5, 0);
controls = new THREE.OrbitControls(camera, renderer.domElement);
controls.minDistance = 100;
controls.maxDistance = 1100;
scene.add(new THREE.GridHelper(2 * worldRadius, 2 * worldWidth / patchSize, 0x444444, 0x444444));
var geometry = new THREE.BufferGeometry();
var positions = new Float32Array(pointsAmount * 3);
var rotations = new Float32Array(pointsAmount * 1);
for (var i = 0; i < pointsAmount; i++) {
positions[i] = 0;
positions[i + 1] = 0;
positions[i + 2] = 0;
rotations[i] = 2 * Math.PI * Math.random();
}
controls = new function() {
this.speed = 10;
this.amount = 10;
};
var gui = new dat.GUI();
gui.add(controls, 'speed', 0, 100);
//gui.add(controls, 'amount', 0, 10000).step(1);;
geometry.addAttribute('position', new THREE.BufferAttribute(positions, 3));
geometry.addAttribute('rotation', new THREE.BufferAttribute(rotations, 1));
var loader = new THREE.TextureLoader();
loader.load('//i.imgur.com/AmQQnZc.png', function(texture) {
var material = new THREE.PointsMaterial({
size: 5,
transparent: true,
map: texture
});
points = new THREE.Points(geometry, material);
scene.add(points);
stats = new Stats();
document.body.appendChild(stats.dom);
animate();
});
}
function animate() {
requestAnimationFrame(animate);
var position = points.geometry.attributes.position;
var count = position.count;
var rotation = points.geometry.attributes.rotation;
var speed = patchSize * controls.speed / 100;
if (speed > 0) {
for (var i = 0; i < count; i++) {
var wiggle = Math.random() > 0.9 ? THREE.Math.randFloat(-0.1, 0.1) : 0;
var theta = rotation.getX(i) + wiggle;
let dx = speed * Math.cos(theta);
let dz = speed * Math.sin(theta);
var x0 = position.getX(i);
var z0 = position.getZ(i);
var x = THREE.Math.clamp(x0 + dx, -worldRadius, worldRadius);
var z = THREE.Math.clamp(z0 + dz, -worldRadius, worldRadius);
if (Math.abs(x) === worldRadius) dx = -dx;
if (Math.abs(z) === worldRadius) dz = -dz;
position.setX(i, x);
position.setZ(i, z);
position.setY(i, 1);
rotation.setX(i, Math.atan2(dz, dx));
}
}
position.needsUpdate = true;
stats.update();
renderer.render(scene, camera);
}
init();
Best way to see the issues is to wait couple of seconds for the point sprites to spread across to area, use speed control on top right corner to pause animation and the use mouse's left button to turn and rotate the camera.