` ````
body {
margin: 0;
overflow: hidden;
}
```

` ````
const renderer = new THREE.WebGLRenderer({
antialias: false,
alpha: false,
canvas: document.querySelector('canvas')
});
let then = Date.now();
let time = 0;
const width = 512;
const height = 512;
renderer.setSize(width, height);
renderer.setPixelRatio(Math.min(2, window.devicePixelRatio));
renderer.sortObjects = false;
renderer.setClearColor('#d66751', 1);
const scene = new THREE.Scene();
const camera = new THREE.OrthographicCamera(-width / 2, width / 2, -height / 2, height / 2, -10, 10);
camera.position.set(width / 2, height / 2, 0);
const count = 50000;
// circle position on screen
// vec3(x, y, z)
const offsetAttrib = new THREE.InstancedBufferAttribute(new Float32Array(count * 3), 3);
// circle color & radius packed into a single GL buffer
// vec4(r, g, b, radius)
const rgbSizeAttrib = new THREE.InstancedBufferAttribute(new Float32Array(count * 4), 4);
rgbSizeAttrib.dynamic = true; // mark it as dynamic since it will change per frame
const geometry = createGeometry();
const material = createMaterial();
const mesh = new THREE.Mesh(geometry, material);
mesh.frustumCulled = false;
scene.add(mesh);
function createMaterial () {
const vertex = `
attribute vec3 offset;
attribute vec4 rgbSize;
varying vec3 color;
void main () {
float radius = rgbSize.w;
vec3 p = position.xyz * radius + offset.xyz;
color = rgbSize.rgb;
gl_Position = projectionMatrix * modelViewMatrix * vec4(p, 1.0);
}
`;
const frag = `
varying vec3 color;
void main () {
gl_FragColor = vec4(color, 1.0);
}
`
return new THREE.ShaderMaterial({
side: THREE.BackSide,
blending: THREE.NoBlending,
vertexShader: vertex,
fragmentShader: frag
});
}
function createGeometry () {
// how many sides for each circle (more = less perf)
const circleSides = 5;
// base geometry that we want to instance
const baseGeometry = new THREE.CircleBufferGeometry(1, circleSides);
// our actual geometry for instanced rendering
const geometry = new THREE.InstancedBufferGeometry();
geometry.maxInstancedCount = count;
// copy over position + index attributes form the base goemetry
const attributes = ['position'];
attributes.forEach(key => {
geometry.addAttribute(key, baseGeometry.getAttribute(key));
});
geometry.setIndex(baseGeometry.getIndex());
// and dispose the base one now that we've copied it over
baseGeometry.dispose();
// add our two attributes
geometry.addAttribute('offset', offsetAttrib);
geometry.addAttribute('rgbSize', rgbSizeAttrib);
// setup default position, color & radius for all circles
setupAttributes();
return geometry;
}
function setupAttributes () {
for (let i = 0; i < count; i++) {
// set initial XY position
offsetAttrib.setXY(i, Math.random() * width, Math.random() * height);
// set initial RGB color (white) and radius (1)
rgbSizeAttrib.setXYZW(i, 1, 1, 1, 1);
}
}
function updateColorAndSize () {
for (let i = 0; i < count; i++) {
const r = (Math.sin(time * 1.0 + i * count) * 0.5 + 0.5) * 0.75;
const g = 0.2;
const b = 0.35 * (Math.sin(time * 1.5) * 0.5 + 0.5);
const radius = (Math.sin(time * 2.0 + i * count) * 0.5 + 0.5) * 10;
rgbSizeAttrib.setXYZW(i, r, g, b, radius);
}
rgbSizeAttrib.needsUpdate = true;
}
function render () {
const now = Date.now();
const delta = now - then;
time += delta / 1000;
then = now;
requestAnimationFrame(render);
updateColorAndSize();
renderer.render(scene, camera);
}
requestAnimationFrame(render);
```

999px

Loading
..................

Alt F
Opt F
Find & Replace

Also see: Tab Triggers