Skip to content
目录

实现发光的罩子

群里同学问问题发了个这样的图片

PMYCKY%60NKU%248T%7DA~V7%7DE900.jpg

好多同学都夸好看,下面我们来看看怎样实现个效果

发光的罩子效果demo

ECLALH~HSD~%25I(6K(718CVO.png

分析

这个效果图里最主要的是这个罩子,其他的效果对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)来构造的,最后发现和罩子的边缘对不上?

2025-06-19_105435.png

出现这个问题的根本原因是:

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的基本和常见的功能,弄个矢量图层,添加对应的面和点数据即可

maptalks教程 document auto generated by mdpress and vitepress