Bola Dot Menakjubkan dengan WebGL

Bola dunia WebGL yang indah dan interaktif memiliki momen yang menjadi sorotan akhir-akhir ini dengan Stripe dan GitHub menampilkannya secara mencolok di beranda mereka. Masing-masing kemudian menulis posting blog tentang bagaimana mereka melakukannya (Stripe ada di sini dan GitHub ada di sini jika Anda penasaran).
Kedua bola dunia terutama terdiri dari titik-titik, yang membuat saya berpikir tentang berbagai cara di mana titik-titik dapat didistribusikan ke seluruh permukaan bola. Sphere packing adalah teka-teki yang kompleks di bawah pertimbangan aktif oleh matematikawan sehingga untuk tujuan artikel ini saya membatasi diri untuk menyusun beberapa pendekatan dasar dan cara mencapainya di WebGL.
Menyiapkan tempat kejadian
Sebelum melangkah lebih jauh, kita perlu membuat adegan WebGL dasar untuk membuat bola. Saya menggunakan Three.js sebagai kerangka pilihan de facto untuk berinteraksi dengan API WebGL. Saya akan berusaha agar potongan kode dalam artikel ini seringkas dan serelevan mungkin; jelajahi salah satu Kotak Pasir tersemat untuk kode lengkapnya.
Setelah membuat adegan, kami membuat dotGeometries
larik yang pada akhirnya akan berisi geometri untuk semua titik kami. Kemudian kita membuat vektor kosong, titik 3D dalam ruang di dalam pemandangan, yang posisinya akan dipindahkan setiap kali kita membuat titik.
// 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);
Pendekatan dasar
Cara termudah untuk menambahkan titik ke bola adalah dengan menentukan jumlah garis lintang dan garis bujur yang kita inginkan untuk dimiliki bola, lalu mendistribusikan titik di sepanjang garis tersebut. Ada beberapa hal penting yang perlu diperhatikan di sini.
Pertama, kita mendefinisikan sudut phi
dan theta
untuk setiap titik. Sudut-sudut ini merupakan bagian dari sistem koordinat bola, sebuah sistem untuk menentukan dengan tepat di mana sebuah titik berada dalam ruang 3D dalam kaitannya dengan asalnya (yang dalam kasus kita adalah pusat bola kita).
Kedua, phi
dan theta
keduanya diukur dalam radian, bukan derajat. Kuncinya adalah mengingat ada π radian dalam 180º . Jadi untuk menemukan phi
di sini yang harus kita lakukan adalah membagi π dengan jumlah garis lintang. Tetapi untuk mencari theta
, kita perlu membaginya 2 * π
dengan jumlah garis bujur karena kita ingin garis bujur kita berlanjut mengelilingi 360º penuh bola.
// 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);
}
}
Jika Anda berinteraksi dengan bola untuk memutarnya, Anda akan melihat cincin di bagian atas dan bawah jauh lebih padat daripada yang ada di tengah. Ini karena kami belum memvariasikan jumlah titik pada setiap garis lintang. Di sinilah pengepakan bola masuk.
Pendekatan filotaksis
Jika Anda pernah melihat kepala bunga matahari atau pangkal biji pinus, Anda akan melihat pola yang tidak biasa dan khas. Pola ini, yang tercipta dari susunan berdasarkan deret Fibonacci , dikenal sebagai filotaksis. Kita dapat menggunakannya di sini untuk memposisikan titik-titik kita sedemikian rupa sehingga jaraknya tampak jauh lebih merata di atas permukaan bola.
Kali ini, alih-alih menentukan jumlah garis lintang dan bujur, kita cukup menentukan jumlah total titik yang ingin kita tampilkan pada bola. Alih-alih berputar melintasi garis lintang, titik-titik itu akan ditampilkan dalam satu spiral terus menerus dari satu kutub bola ke kutub lainnya.
// 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);
...
}
Ini jauh lebih memuaskan. Namun bagaimana jika kita ingin mengemas titik-titik tersebut serata mungkin, namun tetap memiliki kebebasan untuk menentukan jumlah garis lintang?
Pendekatan linier
Kali ini kita akan menentukan jumlah garis lintang yang diperlukan tetapi jumlah titik juga akan diskalakan berdasarkan keliling garis lintang tempat mereka diposisikan. Untuk memberi kita kontrol yang lebih besar terhadap jarak, kita juga akan menentukan parameter kerapatan titik.
Bagian yang rumit di sini adalah menghitung radius setiap garis lintang. Setelah kita mendapatkannya, relatif mudah untuk mengetahui berapa banyak titik yang akan ditampilkan dan kemudian menemukan phi
dan theta
untuk masing-masing titik dengan cara yang mirip dengan pendekatan pertama.
// 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;
...
}
}
Jadi kita telah membahas cara menampilkan titik-titik pada bola. Tetapi bagaimana dengan mencapai efek yang lebih kompleks?
Penyamaran bentuk
Mencari tahu bagaimana menampilkan titik-titik dalam pola yang semakin rumit dapat dengan cepat berubah menjadi sakit kepala matematis. Namun demikian, dengan menggunakan salah satu susunan pengemasan di atas yang dikombinasikan dengan gambar topeng, kita dapat memperoleh beberapa efek yang luar biasa.
Untuk melakukan ini, pertama-tama kita harus membuat elemen kanvas HTML dan menggambar gambar topeng kita di atasnya. Elemen ini sebenarnya tidak akan ditampilkan di layar; itu hanya metode yang nyaman untuk mengekstrak data piksel dari suatu gambar . Kita hanya perlu melakukan ini sekali, jadi kita akan melakukannya terlebih dahulu dan meneruskan data gambar yang diekstraksi ke renderScene
fungsi kita.
// 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);
}
Kita bisa menggunakan gambar topeng yang lebih kompleks untuk mendapatkan bentuk seperti efek bumi ini:
Atau bahkan untuk merender teks:
Itu bungkus
Saya telah menggunakan teknik pemetaan bola ini di berbagai tempat sebagai dasar benda pameran WebGL. Semoga mereka menginspirasi Anda untuk melakukan hal yang sama. Jika Anda menikmati artikel ini atau membantu Anda dalam beberapa hal, beri tahu saya! Situs web saya ada di sini .