Các chấm tròn tuyệt đẹp với WebGL
Gần đây, các quả địa cầu WebGL tương tác, đẹp mắt đã trở thành tâm điểm chú ý khi cả Stripe và GitHub đều giới thiệu chúng một cách nổi bật trên trang chủ của họ. Sau đó, mỗi người tiếp tục viết một bài đăng trên blog về cách họ đã làm như vậy (Stripe ở đây và GitHub ở đây nếu bạn tò mò).
Cả hai quả cầu đều được tạo thành chủ yếu từ các chấm, điều này khiến tôi suy nghĩ về nhiều cách khác nhau mà các chấm có thể phân bố trên bề mặt của một quả cầu. Đóng gói hình cầu là một câu đố phức tạp dưới sự cân nhắc tích cực của các nhà toán học, vì vậy, vì mục đích của bài viết này, tôi đã giới hạn bản thân mình trong việc đưa ra một vài cách tiếp cận cơ bản và cách đạt được chúng trong WebGL.
Thiết lập cảnh
Trước khi tiếp tục, chúng ta cần thiết lập một cảnh WebGL cơ bản để xây dựng hình cầu. Tôi đang sử dụng Three.js làm khung lựa chọn trên thực tế để tương tác với API WebGL. Tôi sẽ cố gắng giữ cho các đoạn mã trong bài viết này ngắn gọn và phù hợp nhất có thể; khám phá bất kỳ Hộp cát nhúng nào để có mã đầy đủ.
Sau khi tạo cảnh, chúng tôi thiết lập một dotGeometriesmảng cuối cùng sẽ chứa hình học cho tất cả các dấu chấm của chúng tôi. Sau đó, chúng tôi tạo một vectơ trống, một điểm 3D trong không gian bên trong cảnh, vị trí của điểm này sẽ được chỉ định lại mỗi khi chúng tôi tạo một dấu chấm.
// 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);
cách tiếp cận cơ bản
Cách dễ nhất để thêm các dấu chấm vào một hình cầu chỉ đơn giản là xác định số lượng đường kinh tuyến và vĩ độ mà chúng ta muốn hình cầu có, sau đó phân bổ các dấu chấm dọc theo chúng. Có một vài điều quan trọng cần lưu ý ở đây.
Đầu tiên, chúng tôi đang xác định góc phivà thetagóc cho từng dấu chấm. Các góc này tạo thành một phần của hệ tọa độ hình cầu, một hệ thống xác định chính xác vị trí của một điểm trong không gian 3D so với điểm gốc của nó (trong trường hợp của chúng ta là tâm của hình cầu).
Thứ hai, phivà thetacả hai đều được đo bằng radian, không phải bằng độ. Chìa khóa của vấn đề này là hãy nhớ rằng có π radian trong 180º . Vì vậy, để tìm phiở đây, tất cả những gì chúng ta phải làm là chia π cho số đường vĩ tuyến. Nhưng để tìm theta, chúng ta cần chia 2 * πcho số đường kinh độ vì chúng ta muốn các đường kinh độ của mình tiếp tục quay quanh 360º đầy đủ của hình cầu.
// 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);
}
}
Nếu bạn tương tác với quả cầu để xoay nó, bạn sẽ nhận thấy các vòng ở phía trên và phía dưới dày đặc hơn nhiều so với các vòng ở giữa. Điều này là do chúng tôi chưa thay đổi số lượng dấu chấm trên mỗi đường vĩ tuyến. Đây là nơi đóng gói hình cầu xuất hiện.
Phương pháp phyllotaxis
Nếu bạn đã từng nhìn vào phần đầu của một bông hoa hướng dương hoặc phần gốc của một quả tùng, bạn sẽ nhận thấy một hoa văn khác thường và đặc biệt. Mẫu này, được tạo bởi sự sắp xếp dựa trên dãy Fibonacci , được gọi là phyllotaxis. Chúng ta có thể sử dụng nó ở đây để định vị các chấm sao cho chúng có vẻ cách đều nhau hơn nhiều trên bề mặt của quả cầu.
Lần này, thay vì xác định số lượng đường kinh tuyến và vĩ độ, chúng ta chỉ cần xác định tổng số điểm mà chúng ta muốn xuất hiện trên hình cầu. Thay vì chạy vòng qua các đường vĩ độ, các chấm sẽ được hiển thị theo một hình xoắn ốc liên tục, duy nhất từ cực này sang cực kia của quả cầu.
// 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);
...
}
Điều này là thỏa mãn hơn nhiều. Nhưng nếu chúng ta muốn đóng gói các dấu chấm càng đều càng tốt, nhưng vẫn có quyền tự do xác định số lượng đường vĩ độ thì sao?
cách tiếp cận tuyến tính
Lần này, chúng tôi sẽ xác định số lượng đường vĩ độ cần thiết nhưng số lượng dấu chấm cũng sẽ chia tỷ lệ dựa trên chu vi của đường vĩ độ mà chúng được định vị. Để giúp chúng tôi kiểm soát tốt hơn khoảng cách, chúng tôi cũng sẽ xác định tham số mật độ điểm.
Phần khó sử dụng ở đây là tính toán bán kính của mỗi đường vĩ độ. Khi chúng tôi đã hiểu điều đó, tương đối đơn giản để tính xem có bao nhiêu dấu chấm sẽ hiển thị trên đó và sau đó tìm phivà thetacho từng dấu chấm theo cách tương tự như cách tiếp cận đầu tiên.
// 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;
...
}
}
Vì vậy, chúng tôi đã giới thiệu cách hiển thị các dấu chấm trên hình cầu. Nhưng còn việc đạt được những hiệu ứng phức tạp hơn thì sao?
mặt nạ hình dạng
Việc tìm ra cách hiển thị các dấu chấm theo các mẫu phức tạp hơn bao giờ hết có thể nhanh chóng trở thành một bài toán đau đầu. Tuy nhiên, bằng cách sử dụng một trong những sắp xếp đóng gói ở trên kết hợp với hình ảnh mặt nạ, chúng ta có thể đạt được một số hiệu ứng phi thường.
Để làm điều này, trước tiên chúng ta cần tạo một phần tử canvas HTML và vẽ hình ảnh mặt nạ của chúng ta lên đó. Phần tử này sẽ không thực sự được hiển thị trên màn hình; đó chỉ là một phương pháp thuận tiện để trích xuất dữ liệu pixel từ một hình ảnh . Chúng tôi chỉ cần thực hiện việc này một lần, vì vậy chúng tôi sẽ thực hiện trước và sau đó chuyển dữ liệu hình ảnh đã trích xuất cho renderScenechức năng của chúng tôi.
// 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);
}
Chúng ta có thể sử dụng các hình ảnh mặt nạ phức tạp hơn để đạt được các hình dạng như hiệu ứng trái đất này:
Hoặc thậm chí để hiển thị văn bản:
Đó là một bọc
Tôi đã sử dụng các kỹ thuật ánh xạ hình cầu này ở nhiều nơi khác nhau làm cơ sở cho các phần trưng bày WebGL. Hy vọng rằng họ truyền cảm hứng cho bạn để làm như vậy. Nếu bạn thích bài viết này hoặc nó đã giúp bạn theo một cách nào đó, xin vui lòng cho tôi biết! Trang web của tôi ở đây .

![Dù sao thì một danh sách được liên kết là gì? [Phần 1]](https://post.nghiatu.com/assets/images/m/max/724/1*Xokk6XOjWyIGCBujkJsCzQ.jpeg)



































