实现发光的罩子
群里同学问问题发了个这样的图片
好多同学都夸好看,下面我们来看看怎样实现个效果
分析
这个效果图里最主要的是这个罩子,其他的效果对maptalks来说是很简单的,简单的弄个图层然后配置下样式即可.
关于罩子:
- maptalks里默认是没有提供这个图形要素的,也不会提供,这个显然是个自定义个图形要素,这时我们的threelayer图层就可以派上用场了
- 这个罩子还不是加个透明度这么简单,关于这个效果具体 参考 用shader写一个发光材质
- 这个罩子外面还有个线条,这个比较简单我们可以用纹理贴个图
罩子的实现
我们利用three插件开放出来的自定义能力定义一个图形即可,这个需要你对three.js有一定的了解的
js
const OPTIONS = {
radius: 50,
altitude: 0,
};
class EffectSphere extends maptalks.BaseObject {
constructor(coordinate, options, material, threeLayer) {
options = Object.assign({ coordinate }, OPTIONS, options);
super();
this._initOptions(options);
const { radius } = options;
const r = threeLayer.distanceToVector3(radius, radius).x;
this.options.r = r;
let geometry = new THREE.SphereBufferGeometry(
r,
100,
100,
0,
Math.PI * 2,
0,
Math.PI / 2,
);
const { altitude } = options;
const z = threeLayer.altitudeToVector3(altitude, altitude).x;
const centerPt = threeLayer.coordinateToVector3(coordinate);
centerPt.z = z;
this._createMesh(geometry, material);
this.getObject3d().position.copy(centerPt);
this.getObject3d().rotation.x = Math.PI / 2;
this.type = "EffectSphere";
}
}
整体的罩子效果我们可以用两个EffectSphere
叠加下,一个用发光材质,一个用贴图
发光的罩子
发光的shader我就直接贴代码了,具体的原理请参考用shader写一个发光材质
js
const vs1 = `
varying vec3 vNormal;
varying vec3 vPositionNormal;
void main()
{
vNormal = normalize( normalMatrix * normal ); // 转换到视图空间
vPositionNormal = normalize(( modelViewMatrix * vec4(position, 1.0) ).xyz);
gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
}
`;
const fs1 = `
uniform vec3 glowColor;
uniform float b;
uniform float p;
uniform float s;
varying vec3 vNormal;
varying vec3 vPositionNormal;
void main()
{
float a = pow( b + s * abs(dot(vNormal, vPositionNormal)), p );
gl_FragColor = vec4( glowColor, a );
}
`;
var customMaterial = new THREE.ShaderMaterial({
uniforms: {
s: { type: "f", value: -1.0 },
b: { type: "f", value: 1.0 },
p: { type: "f", value: 2.0 },
glowColor: { type: "c", value: new THREE.Color(color) },
},
vertexShader: vs1,
fragmentShader: fs1,
side: THREE.FrontSide,
blending: THREE.AdditiveBlending,
transparent: true,
});
线条的罩子
生成一个矩形边框的纹理贴下图即可
js
function generateTexture() {
const canvas = document.createElement("canvas");
canvas.width = 512;
canvas.height = 256;
const ctx = canvas.getContext("2d");
ctx.strokeStyle = color;
ctx.lineWidth = 12;
ctx.rect(2, 2, canvas.width + 1, canvas.height + 1);
ctx.stroke();
// document.body.appendChild(canvas)
const texture = new THREE.CanvasTexture(canvas);
texture.wrapS = texture.wrapT = THREE.RepeatWrapping;
texture.repeat.set(20, 10);
texture.needsUpdate = true;
return texture;
}
const gridTexture = generateTexture();
const material1 = new THREE.MeshBasicMaterial({
color: "#fff",
map: gridTexture,
transparent: true,
opacity: 1,
});
罩子边缘的线条
群友的问题是:用圆(Circle)来构造的,最后发现和罩子的边缘对不上?
出现这个问题的根本原因是:
Circle经过投影变换后不是标准的圆了,其实是个不规则的多边形环了,因为投影形变很小导致看到的像个圆而已 常见的墨卡托投影南北两极形变比较大 而threeLayer图层里利用CircleBufferGeometry生成的圆是个非常标准的圆了 这时就导致Circle和CircleBufferGeometry生成的图形不能完全重合了
圆的半径越大这个问题越明显,即如果圆的半径很小这个问题肉眼是看不出来的
如何解决这个问题呢?
我们可以把CircleBufferGeometry边界上的gl 点求出来,然后转成经纬度构造polygon即可,不要在用Circle了,
js
function generateShell(circle) {
const center = circle.options.coordinate;
const r = circle.options.r;
const centerPT = threeLayer.coordinateToVector3(center);
const coordinates = [];
const glRes = map.getGLRes();
for (let i = 0; i <= 360; i++) {
const rad = (i / 180) * Math.PI;
const x = Math.cos(rad) * r + centerPT.x;
const y = Math.sin(rad) * r + centerPT.y;
const p = new maptalks.Point(x, y);
const c = map.pointAtResToCoordinate(p, glRes);
coordinates.push(c);
}
const polygon = new maptalks.Polygon([coordinates], {
symbol: {
...symbol,
lineColor: "blue",
polygonOpacity: 0.2,
},
...baseOptions,
}).addTo(layer);
}
其他效果
这些我就不介绍了,属于maptalks的基本和常见的功能,弄个矢量图层,添加对应的面和点数据即可