设有一张图片width/height = 2:1。这个比例很重要。因为这个比例的全景图片刚好可以还原成一张球形全景图。比喻展开的世界地图。
把图片的宽 当成成 纬度,范围[0-2π]。
把图片的高当成经度,范围[-π/2,π/2]。
在半径为 r 的球面坐标中,设:水平转动角度为θ[0,2π],上下转动角度为φ[-π/2,π/2],所以球面上一点的三维坐标:
x = rcosθcosφ
y = rcosθsinφ
z = r*sinφ
从上面可以看出两者存在着对于的关系。至于什么关系写起来有点麻烦。
想象一下把展开的地图再次包裹在地球仪上。
或者想象一下用刀子把地球仪沿着本初子午线切开。然后展开成一张2:1的图片,至于怎么展开,参考下面的图,在南极北极开个口子,然后扯开,然后切开,展平。纹理的采样和切分的区域数量有关,下图是切割了8块,可以切割更多的小块来提高精度。
图片的宽高–球体的经纬度–球坐标系–空间坐标系
这四个参数之间有对应关系。
百度球坐标系
为了方便观察,我制作了一张黑白的图片。经过经纬度转换之后可以变成下面的效果。下图的粒子效果是把图片中黑色的部分像素提出出来换成粒子代替。
如果用一张黑白的世界地图来代替的话就会生成一个炫酷的镂空粒子地球。
核心代码:
function imgTodata(src, r, d, pcolor, name, imgCallBack) {
var imgData = [];
var image = new Image;
image.src = src;
image.onload = function () {
var canvas = document.createElement("canvas");
canvas.width = image.width;
canvas.height = image.height;
imgData.width = image.width;
imgData.height = image.height;
var context = canvas.getContext("2d");
context.drawImage(image, 0, 0, image.width, image.height);
var data = context.getImageData(0, 0, canvas.width, canvas.height);
var dLength = data.data.length;
for (var i = 0; i < dLength; i += 4) {
var x = (i / 4) % canvas.width;
var y = (i / 4 - x) / canvas.width;
if (i / 4 % 2 == 1 && y % 2 == 1 && 0 === data.data[i]) {
var u = (360 / canvas.width) * x - 180;
var v = (180 / canvas.height) * y - 90;
var xyz = uvToxyz(u, v, r);
imgData.push(xyz);
}
}
var globeGeometry = new THREE.BufferGeometry();
positionsa = [];
var sprite = new THREE.TextureLoader().load('../img/disc1.png');
var globeMaterial = new THREE.PointsMaterial({
size: d + 0.2,
color: pcolor,
map: sprite,
side: THREE.DoubleSide,
opacity: 0.5,
});
imgData.forEach(e => {
positionsa.push(e[0], e[1], e[2]);
});
globeGeometry.addAttribute('position', new THREE.Float32BufferAttribute(positionsa, 3));
globeGeometry.computeBoundingSphere();
var globes = new THREE.Points(globeGeometry, globeMaterial);
globes.name = name;
imgCallBack.call(this, globes);
}
}
// 经纬度 转 xyz uv to xyz
function uvToxyz(u, v, r) {
var wd = (u + 90) * Math.PI / 180,
jd = v * Math.PI / 180,
x = -r * Math.cos(jd) * Math.cos(wd),
y = -r * Math.sin(jd),
z = r * Math.cos(jd) * Math.sin(wd);
return [x, y, z];
}
代码中图片转换成数据,使用的canvas画布的图片处理功能实现的,逐像素读取if (i / 4 % 2 == 1 && y % 2 == 1 && 0 === data.data[i])
,代码中添加了筛选函数,%2
就是每隔一个点取样一次,%5
就是每个5个取样一次,通过这个值就可以控制取样点的数量,也就是后期中粒子的数量。如果像素是黑色就读取坐标值保存在数组中,不是就抛弃。
x,y 对应列和行