I am working on a 3D force-graph visualisation and I'm using the great library:
https://github.com/vasturiano/3d-force-graph
I would like to try to get some sort of glowing effect on the nodes of the force-graph and I was having a look at something like this to accomplish it, but it is proving a challenge:
https://github.com/jeromeetienne/threex.geometricglow
So far I only have the ability to add new shapes and texture them. I have also added text sprites that follow the nodes, so I suspect an approach that combines these two might be possible. See below the code.
Adding a glow effect or texture to this base code would also be very helpful:
https://github.com/vasturiano/3d-force-graph/blob/master/example/async-load/index.html
<head>
<style>
body {
margin: 0;
}
</style>
<script src="//unpkg.com/three"></script>
<script src="//unpkg.com/three-spritetext"></script>
<script src="//unpkg.com/3d-force-graph"></script>
<!--<script src="../../dist/3d-force-graph.js"></script>-->
</head>
<body>
<div id="3d-graph"></div>
<script>
const loader = new THREE.TextureLoader();
const Graph = ForceGraph3D()
(document.getElementById('3d-graph')).jsonUrl('../datasets/testdata.json').nodeLabel('id').backgroundColor('#F7F8FA').nodeAutoColorBy('group').nodeThreeObjectExtend(true).nodeThreeObject(node => {
// extend link with text sprite
const sprite = new SpriteText(`${node.id}`);
sprite.color = 'lightgrey';
sprite.textHeight = 4
sprite.fontFace = "Comic Sans MS"
sprite.position.set(5, 5, 5)
return sprite;
}).nodeVal('size').linkWidth(2)
const distance = 600;
//
const sphereGeometry = new THREE.SphereGeometry(18);
const sphereMaterial = new THREE.MeshBasicMaterial({
map: loader.load('../datasets/texture.jpg')
});
const mesh = new THREE.Mesh(sphereGeometry, sphereMaterial);
mesh.position.set(9, 17, 22);
Graph.scene().add(mesh);
//
// camera orbit
let angle = 0;
setInterval(() => {
Graph.cameraPosition({
x: distance * Math.sin(angle),
z: distance * Math.cos(angle)
});
angle += Math.PI / 1000;
}, 10); //
let materialArray = [];
let texture_ft = new THREE.TextureLoader().load('../datasets/penguins/kenon_star_ft.jpg');
let texture_bk = new THREE.TextureLoader().load('../datasets/penguins/kenon_star_bk.jpg');
let texture_up = new THREE.TextureLoader().load('../datasets/penguins/kenon_star_up.jpg');
let texture_dn = new THREE.TextureLoader().load('../datasets/penguins/kenon_star_dn.jpg');
let texture_rt = new THREE.TextureLoader().load('../datasets/penguins/kenon_star_rt.jpg');
let texture_lf = new THREE.TextureLoader().load('../datasets/penguins/kenon_star_lf.jpg');
materialArray.push(new THREE.MeshBasicMaterial({
map: texture_ft
}));
materialArray.push(new THREE.MeshBasicMaterial({
map: texture_bk
}));
materialArray.push(new THREE.MeshBasicMaterial({
map: texture_up
}));
materialArray.push(new THREE.MeshBasicMaterial({
map: texture_dn
}));
materialArray.push(new THREE.MeshBasicMaterial({
map: texture_rt
}));
materialArray.push(new THREE.MeshBasicMaterial({
map: texture_lf
}));
for (let i = 0; i < 6; i++) materialArray[i].side = THREE.BackSide;
let skyboxGeo = new THREE.BoxGeometry(10000, 10000, 10000);
let skybox = new THREE.Mesh(skyboxGeo, materialArray);
Graph.scene().add(skybox);
/* loader.load('https://images.pexels.com/photos/1205301/pexels-photo-1205301.jpeg', function(texture) {
scene.background = texture;
});
*/
</script>
</body>
Thanks
I've been interested in getting this to work as well.
I used this threex example and seen that these two lines were necessary:
var glowMesh = new THREEx.GeometricGlowMesh(mesh)
mesh.add(glowMesh.object3d)
Combine that with this 3d-force-graph example for the custom node geometry.
The whole basic working thing is below:
// Random tree data
var generateData = function() {
const N = 50;
const gData = {
nodes: [...Array(N).keys()].map(i => ({ id: i })),
links: [...Array(N).keys()]
.filter(id => id)
.map(id => ({
source: id,
target: Math.round(Math.random() * (id-1))
}))
};
return gData;
}
// Create a three.js sphere mesh.
var sphereMesh = function(id) {
var mesh = new THREE.Mesh(
[
new THREE.SphereGeometry(10, 32, 32)
][id%1],
new THREE.MeshLambertMaterial({
color: '#277ec9',
transparent: true,
opacity: 1.0
})
)
// Make it glow.
var glowMesh = new THREEx.GeometricGlowMesh(mesh);
mesh.add(glowMesh.object3d);
var insideUniforms = glowMesh.insideMesh.material.uniforms;
insideUniforms.glowColor.value.set('yellow');
var outsideUniforms = glowMesh.outsideMesh.material.uniforms;
outsideUniforms.glowColor.value.set('yellow');
return mesh
}
const Graph = ForceGraph3D()
(document.getElementById('3d-graph'))
.graphData(generateData())
.nodeThreeObject(({ id }) => sphereMesh(id))
.nodeLabel('id');
/*
The following three snippets of code are the minimum required from the THREEx library.
*/
// ========== threex.dilategeometry.js =============
/**
* @namespace
*/
var THREEx = THREEx || {}
/**
* dilate a geometry inplace
* @param {THREE.Geometry} geometry geometry to dilate
* @param {Number} length percent to dilate, use negative value to erode
*/
THREEx.dilateGeometry = function(geometry, length){
// gather vertexNormals from geometry.faces
var vertexNormals = new Array(geometry.vertices.length);
geometry.faces.forEach(function(face){
if( face instanceof THREE.Face4 ){
vertexNormals[face.a] = face.vertexNormals[0];
vertexNormals[face.b] = face.vertexNormals[1];
vertexNormals[face.c] = face.vertexNormals[2];
vertexNormals[face.d] = face.vertexNormals[3];
}else if( face instanceof THREE.Face3 ){
vertexNormals[face.a] = face.vertexNormals[0];
vertexNormals[face.b] = face.vertexNormals[1];
vertexNormals[face.c] = face.vertexNormals[2];
}else console.assert(false);
});
// modify the vertices according to vertextNormal
geometry.vertices.forEach(function(vertex, idx){
var vertexNormal = vertexNormals[idx];
vertex.x += vertexNormal.x * length;
vertex.y += vertexNormal.y * length;
vertex.z += vertexNormal.z * length;
});
};
// ========== threex.atmospherematerial.js =============
var THREEx = THREEx || {}
/**
* from http://stemkoski.blogspot.fr/2013/07/shaders-in-threejs-glow-and-halo.html
* @return {[type]} [description]
*/
THREEx.createAtmosphereMaterial = function(){
var vertexShader = [
'varying vec3 vVertexWorldPosition;',
'varying vec3 vVertexNormal;',
'varying vec4 vFragColor;',
'void main(){',
' vVertexNormal = normalize(normalMatrix * normal);',
' vVertexWorldPosition = (modelMatrix * vec4(position, 1.0)).xyz;',
' // set gl_Position',
' gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);',
'}',
].join('\n')
var fragmentShader = [
'uniform vec3 glowColor;',
'uniform float coeficient;',
'uniform float power;',
'varying vec3 vVertexNormal;',
'varying vec3 vVertexWorldPosition;',
'varying vec4 vFragColor;',
'void main(){',
' vec3 worldCameraToVertex= vVertexWorldPosition - cameraPosition;',
' vec3 viewCameraToVertex = (viewMatrix * vec4(worldCameraToVertex, 0.0)).xyz;',
' viewCameraToVertex = normalize(viewCameraToVertex);',
' float intensity = pow(coeficient + dot(vVertexNormal, viewCameraToVertex), power);',
' gl_FragColor = vec4(glowColor, intensity);',
'}',
].join('\n')
// create custom material from the shader code above
// that is within specially labeled script tags
var material = new THREE.ShaderMaterial({
uniforms: {
coeficient : {
type : "f",
value : 1.0
},
power : {
type : "f",
value : 2
},
glowColor : {
type : "c",
value : new THREE.Color('pink')
},
},
vertexShader : vertexShader,
fragmentShader : fragmentShader,
//blending : THREE.AdditiveBlending,
transparent : true,
depthWrite : false,
});
return material
}
// ========== threex.geometricglowmesh.js =============
var THREEx = THREEx || {}
THREEx.GeometricGlowMesh = function(mesh){
var object3d = new THREE.Object3D
var geometry = mesh.geometry.clone()
THREEx.dilateGeometry(geometry, 0.01)
var material = THREEx.createAtmosphereMaterial()
material.uniforms.glowColor.value = new THREE.Color('cyan')
material.uniforms.coeficient.value = 1.1
material.uniforms.power.value = 1.4
var insideMesh = new THREE.Mesh(geometry, material );
object3d.add( insideMesh );
var geometry = mesh.geometry.clone()
THREEx.dilateGeometry(geometry, 0.1)
var material = THREEx.createAtmosphereMaterial()
material.uniforms.glowColor.value = new THREE.Color('cyan')
material.uniforms.coeficient.value = 0.1
material.uniforms.power.value = 1.2
material.side = THREE.BackSide
var outsideMesh = new THREE.Mesh( geometry, material );
object3d.add( outsideMesh );
// expose a few variable
this.object3d = object3d
this.insideMesh = insideMesh
this.outsideMesh= outsideMesh
}
<script src="https://threejs.org/build/three.js"></script>
<head>
<style> body { margin: 0; } </style>
<script src="//unpkg.com/3d-force-graph"></script>
</head>
<body>
<div id="3d-graph"></div>
</body>
Here's the same thing on jsfiddle. You can see the results better here.