Skip to content

Instantly share code, notes, and snippets.

@vicapow
Last active March 8, 2023 07:37
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save vicapow/7f14a531dec565ca0884 to your computer and use it in GitHub Desktop.
Save vicapow/7f14a531dec565ca0884 to your computer and use it in GitHub Desktop.
WebGL + d3.layout.force

This demo uses d3.layout.force() to calculate the node positions and then passes those to webGL to render them on the GPU.

<!DOCTYPE html>
<html>
<head>
<title></title>
<style>
body {
margin: 0;
}
</style>
</head>
<body>
<script type="x-shader/x-vertex" id="vertexshader">
attribute float size;
uniform float pointSize;
uniform vec3 color;
uniform float alpha;
varying vec4 vColor;
varying vec2 myPosition;
varying float mySize;
void main() {
vec4 mvPosition = modelViewMatrix * vec4(position, 1.0);
gl_PointSize = size;
mySize = size;
gl_Position = projectionMatrix * mvPosition;
vColor = vec4(color, alpha);
}
</script>
<script type="x-shader/x-fragment" id="fragmentshader">
varying vec4 vColor;
varying vec2 myPosition;
varying float mySize;
void main() {
float f = 0.5;
float d = abs(distance(gl_PointCoord - f, vec2(0.0, 0.0)));
float pixelD = (d + 0.5) * mySize;
if(pixelD + 0.5 < mySize) {
gl_FragColor = vec4(vColor.xyz, 0.9);
} else if(pixelD < mySize) {
gl_FragColor = vec4(vColor.xyz, 0.1);
} else {
discard;
}
}
</script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.3/d3.min.js" charset="utf-8"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r70/three.min.js" charset="utf-8"></script>
<script>
'use strict'
var scene, camera, renderer;
var geometry, material, mesh;
var width = window.innerWidth, height = window.innerHeight
console.log(width, height)
scene = new THREE.Scene()
camera = new THREE.OrthographicCamera(width / - 2, width / 2, height / 2, height / - 2, 1, 10000)
scene.add(camera)
camera.position.z = 1000
// geometry = new THREE.BoxGeometry( 200, 200, 200 )
// material = new THREE.MeshBasicMaterial({color: 0xff0000})
// mesh = new THREE.Mesh(geometry, material)
// scene.add(mesh)
function uniforms(opts) {
opts = opts || {}
return {
color: {
type: 'c',
value: new THREE.Color(0x3498db)
},
alpha: { type: 'f', value: 0.7 },
pointSize: { type: 'f', value: 10 },
shouldResize: { type: '1i', value: opts.shouldResize ? 1 : 0 }
}
}
var particles = 1000
var mouseIdx = 200
var positions = new Float32Array(particles * 3)
var dx = 2
var norm = d3.random.normal(0, 100)
for(var i = 0; i < positions.length; i+=3) {
var x = norm(), y = norm(), z = norm()
if (i / 3 < particles / 3) x -= 0.5, y += 1, z -= 0.5
else if (i / 3 < particles / 3 * 2) x += dx, y += dx, z += dx
else x -= dx, y -= dx, z -= dx
positions[i] = x, positions[i + 1] = y, positions[i + 2] = z
}
var sizes = new Float32Array(particles)
for(var i = 0; i < particles; i++) sizes[i] = Math.random() * 10 + 3
var attributes = {
size: { type: 'f', value: [] }
}
var cloudMat = new THREE.ShaderMaterial({
uniforms: uniforms(),
attributes: attributes,
vertexShader: d3.select('#vertexshader').node().textContent,
fragmentShader: d3.select('#fragmentshader').node().textContent,
transparent: true,
setDepthTest: false,
// blending: THREE.CustomBlending,
// blendEquation: THREE.AddEquation,
// blendSrc: THREE.SrcAlphaSaturate,
// blendDst: THREE.OneMinusSrcAlphaFactor,
})
var cloudGeom = new THREE.BufferGeometry()
var posBuff = new THREE.BufferAttribute(positions, 3)
cloudGeom.addAttribute('position', posBuff)
cloudGeom.addAttribute('size', new THREE.BufferAttribute(sizes, 1))
cloudGeom.computeBoundingSphere()
var pointCloud = new THREE.PointCloud(cloudGeom, cloudMat)
scene.add(pointCloud)
renderer = new THREE.WebGLRenderer({alpha: true})
renderer.setSize(width, height)
document.body.appendChild(renderer.domElement)
var nodes = d3.range(particles).map(function(d) { return {} })
var mouseNode = { fixed: true }
nodes.push(mouseNode)
// mouseNode.fixed = true
var force = d3.layout.force()
.nodes(nodes)
.size([width, height])
.charge(function(d, i) { return -sizes[i] || -500 })
// .chargeDistance(10)
.start()
var mousePosition = [0, 0]
d3.timer(function(d) {
force.start()
mouseNode.x = mousePosition[0], mouseNode.y = mousePosition[1]
for(var i = 0; i + 1 < nodes.length; i++) {
positions[i * 3] = nodes[i].x - width / 2
positions[i * 3 + 1] = - (nodes[i].y - height / 2)
}
posBuff.needsUpdate = true // Important!
renderer.render(scene, camera)
})
d3.select('canvas')
.on('mousemove', updateMouse)
.call(d3.behavior.drag().on('drag', updateMouse))
function updateMouse() {
var p = d3.mouse(this)
mousePosition = p
}
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment