Splendide sfere di punti con WebGL
I globi WebGL belli e interattivi hanno avuto un momento sotto i riflettori ultimamente con Stripe e GitHub che li presentano in primo piano nelle loro home page. Ognuno in seguito ha continuato a scrivere un post sul blog su come lo hanno fatto (Stripe è qui e GitHub è qui se sei curioso).
Entrambi i globi sono costituiti principalmente da punti, il che mi ha fatto pensare ai vari modi in cui i punti possono essere distribuiti sulla superficie di una sfera. L'impacchettamento di sfere è un enigma complesso sotto deliberazione attiva da parte dei matematici, quindi per gli scopi di questo articolo mi sono limitato a delineare alcuni approcci di base e come realizzarli in WebGL.
Allestimento della scena
Prima di andare oltre, dobbiamo stabilire una scena WebGL di base in cui costruire la sfera. Sto usando Three.js come framework de facto preferito per interagire con l'API WebGL. Mirerò a mantenere i frammenti di codice in questo articolo il più concisi e pertinenti possibile; esplora uno qualsiasi dei sandbox incorporati per il codice completo.
Dopo aver creato una scena, stabiliamo un dotGeometriesarray che alla fine conterrà le geometrie per tutti i nostri punti. Quindi creiamo un vettore vuoto, un punto 3D nello spazio all'interno della scena, la cui posizione verrà riassegnata ogni volta che creiamo un punto.
// Set up the scene.
const scene = new THREE.Scene();
// Define an array to hold the geometries of all the dots.
const dotGeometries = [];
// Create a blank vector to be used by the dots.
const vector = new THREE.Vector3();
// We'll create and position the dots here!
// Merge all the dot geometries together into one buffer geometry.
const mergedDotGeometries = BufferGeometryUtils.mergeBufferGeometries(
dotGeometries
);
// Define the material for the dots.
const dotMaterial = new THREE.MeshBasicMaterial({
color: DOT_COLOR,
side: THREE.DoubleSide
});
// Create the dot mesh from the dot geometries and material.
const dotMesh = new THREE.Mesh(mergedDotGeometries, dotMaterial);
// Add the dot mesh to the scene.
scene.add(dotMesh);
L'approccio di base
Il modo più semplice per aggiungere punti a una sfera è semplicemente definire il numero di linee di latitudine e longitudine che vorremmo che la sfera avesse, quindi distribuire i punti lungo di esse. Ci sono un paio di cose importanti da notare qui.
Per prima cosa, definiamo gli angoli phie per ogni punto. thetaQuesti angoli fanno parte del sistema di coordinate sferiche, un sistema per definire esattamente dove si trova un punto nello spazio 3D in relazione alla sua origine (che nel nostro caso è il centro della nostra sfera).
In secondo luogo, phie thetasono entrambi misurati in radianti, non in gradi. La chiave per questo è ricordare che ci sono π radianti in 180º . Quindi per trovare phiqui tutto ciò che dobbiamo fare è dividere π per il numero di linee di latitudine. Ma per trovare theta, dobbiamo dividere 2 * πper il numero di linee di longitudine perché vogliamo che le nostre linee di longitudine continuino intorno ai 360º completi della sfera.
// Loop across the latitudes.
for (let lat = 0; lat < LATITUDE_COUNT; lat += 1) {
// Loop across the longitudes.
for (let lng = 0; lng < LONGITUDE_COUNT; lng += 1) {
// Create a geomtry for the dot.
const dotGeometry = new THREE.CircleGeometry(DOT_SIZE, 5);
// Defin the phi and theta angles for the dot.
const phi = (Math.PI / LATITUDE_COUNT) * lat;
const theta = ((2 * Math.PI) / LONGITUDE_COUNT) * lng;
// Set the vector using the spherical coordinates generated from the sphere radius, phi and theta.
vector.setFromSphericalCoords(SPHERE_RADIUS, phi, theta);
// Make sure the dot is facing in the right direction.
dotGeometry.lookAt(vector);
// Move the dot geometry into position.
dotGeometry.translate(vector.x, vector.y, vector.z);
// Push the positioned geometry into the array.
dotGeometries.push(dotGeometry);
}
}
Se interagisci con la sfera per ruotarla, noterai che gli anelli in alto e in basso sono molto più fitti di quelli al centro. Questo perché non abbiamo variato il numero di punti su ciascuna linea di latitudine. È qui che entra in gioco l'imballaggio delle sfere.
L'approccio della fillotassi
Se hai mai guardato la testa di un girasole o la base di una pigna avrai notato un motivo insolito e distintivo. Questo modello, creato da una disposizione basata sulla sequenza di Fibonacci , è noto come fillotassi. Possiamo usarlo qui per posizionare i nostri punti in modo tale che appaiano molto più uniformemente distanziati sulla superficie della sfera.
Questa volta, invece di definire il numero di linee di latitudine e longitudine, definiamo semplicemente il numero totale di punti che vogliamo che appaiano sulla sfera. Invece di scorrere attraverso le linee di latitudine, i punti saranno resi in un'unica spirale continua da un polo all'altro della sfera.
// Loop across the number of dots.
for (let dot = 0; dot < DOT_COUNT; dot += 1) {
// Create a geomtry for the dot.
const dotGeometry = new THREE.CircleGeometry(DOT_SIZE, 5);
// Work out the spherical coordinates of each dot, in a phyllotaxis pattern.
const phi = Math.acos(-1 + (2 * dot) / DOT_COUNT);
const theta = Math.sqrt(DOT_COUNT * Math.PI) * phi;
// Set the vector using the spherical coordinates generated from the sphere radius, phi and theta.
vector.setFromSphericalCoords(SPHERE_RADIUS, phi, theta);
...
}
Questo è molto più soddisfacente. Ma cosa succede se vogliamo impacchettare i punti nel modo più uniforme possibile, ma avere ancora la libertà di definire il numero di linee di latitudine?
L'approccio lineare
Questa volta definiremo il numero di linee di latitudine richieste, ma anche il numero di punti verrà ridimensionato in base alla circonferenza della linea di latitudine su cui sono posizionati. Per darci un maggiore controllo sulla spaziatura definiremo anche un parametro di densità dei punti.
La parte complicata qui è calcolare il raggio di ogni linea di latitudine. Una volta ottenuto ciò, è relativamente semplice capire quanti punti visualizzare su di esso e quindi trovare phie thetaper ciascuno in modo simile al primo approccio.
// Loop across the latitude lines.
for (let lat = 0; lat < LATITUDE_COUNT; lat += 1) {
// Calculate the radius of the latitude line.
const radius =
Math.cos((-90 + (180 / LATITUDE_COUNT) * lat) * (Math.PI / 180)) *
SPHERE_RADIUS;
// Calculate the circumference of the latitude line.
const latitudeCircumference = radius * Math.PI * 2 * 2;
// Calculate the number of dots required for the latitude line.
const latitudeDotCount = Math.ceil(latitudeCircumference * DOT_DENSITY);
// Loop across the dot count for the latitude line.
for (let dot = 0; dot < latitudeDotCount; dot += 1) {
const dotGeometry = new THREE.CircleGeometry(DOT_SIZE, 5);
// Calculate the phi and theta angles for the dot.
const phi = (Math.PI / LATITUDE_COUNT) * lat;
const theta = ((2 * Math.PI) / latitudeDotCount) * dot;
...
}
}
Quindi abbiamo spiegato come visualizzare i punti sulla sfera. Ma per quanto riguarda il raggiungimento di effetti più complessi?
Mascheratura della forma
Capire come visualizzare i punti in schemi sempre più complicati può trasformarsi rapidamente in un mal di testa matematico. Tuttavia, utilizzando una delle suddette disposizioni di imballaggio in combinazione con un'immagine maschera, possiamo ottenere alcuni effetti straordinari.
Per fare ciò, dobbiamo prima creare un elemento canvas HTML e disegnare su di esso l'immagine della nostra maschera. Questo elemento non verrà effettivamente visualizzato sullo schermo; è solo un metodo conveniente con cui estrarre i dati dei pixel da un'immagine . Dobbiamo farlo solo una volta, quindi lo faremo in anticipo e quindi passeremo i dati dell'immagine estratti alla nostra renderScenefunzione.
// Initialise an image loader.
const imageLoader = new THREE.ImageLoader();
// Load the image used to determine where dots are displayed. The sphere
// cannot be initialised until this is complete.
imageLoader.load(MASK_IMAGE, (image) => {
// Create an HTML canvas, get its context and draw the image on it.
const tempCanvas = document.createElement("canvas");
tempCanvas.width = image.width;
tempCanvas.height = image.height;
const ctx = tempCanvas.getContext("2d");
ctx.drawImage(image, 0, 0);
// Read the image data from the canvas context.
const imageData = ctx.getImageData(0, 0, image.width, image.height);
renderScene(imageData);
});
// Utility function to convert a dot on a sphere into a UV point on a
// rectangular texture or image.
const spherePointToUV = (dotCenter, sphereCenter) => {
// Create a new vector and give it a direction from the center of the sphere
// to the center of the dot.
const newVector = new THREE.Vector3();
newVector.subVectors(sphereCenter, dotCenter).normalize();
// Calculate the UV coordinates of the dot and return them as a vector.
const uvX = 1 - (0.5 + Math.atan2(newVector.z, newVector.x) / (2 * Math.PI));
const uvY = 0.5 + Math.asin(newVector.y) / Math.PI;
return new THREE.Vector2(uvX, uvY);
};
// Utility function to sample the data of an image at a given point. Requires
// an imageData object.
const sampleImage = (imageData, uv) => {
// Calculate and return the data for the point, from the UV coordinates.
const point =
4 * Math.floor(uv.x * imageData.width) +
Math.floor(uv.y * imageData.height) * (4 * imageData.width);
return imageData.data.slice(point, point + 4);
};
// Move the dot geometry into position.
dotGeometry.translate(vector.x, vector.y, vector.z);
// Find the bounding sphere of the dot.
dotGeometry.computeBoundingSphere();
// Find the UV position of the dot on the land image.
const uv = spherePointToUV(
dotGeometry.boundingSphere.center,
new THREE.Vector3()
);
// Sample the pixel on the land image at the given UV position.
const sampledPixel = sampleImage(imageData, uv);
// If the pixel contains a color value (in other words, is not transparent),
// continue to create the dot. Otherwise don't bother.
if (sampledPixel[3]) {
// Push the positioned geometry into the array.
dotGeometries.push(dotGeometry);
}
Possiamo utilizzare immagini di maschere più complesse per ottenere forme come questo effetto terra:
O anche per rendere il testo:
Questo è un involucro
Ho utilizzato queste tecniche di mappatura sferica in vari luoghi come base per i capolavori WebGL. Spero che ti ispirino a fare lo stesso. Se questo articolo ti è piaciuto o ti ha aiutato in qualche modo, fammelo sapere! Il mio sito web è qui .

![Che cos'è un elenco collegato, comunque? [Parte 1]](https://post.nghiatu.com/assets/images/m/max/724/1*Xokk6XOjWyIGCBujkJsCzQ.jpeg)



































