Custom lightning symbols using a shader

Custom lightning symbols using a shader

This example demonstrates how to customized the appearance of lightning symbols using a custom WebGL fragment shader using the symbol.shader paint property. The shader is used to create a custom lightning effect that is animated and changes based on the age of the lightning strike.

custom-lightning-shader.html
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <title>MapsGL SDK - Custom lightning symbols using a shader</title>
    <meta name="description" content="Use a custom WebGL fragment shader to create a custom effect for lightning strike data." />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
 
    <link href="https://api.mapbox.com/mapbox-gl-js/v2.8.0/mapbox-gl.css" rel="stylesheet" />
    <script defer src="https://api.mapbox.com/mapbox-gl-js/v2.8.0/mapbox-gl.js"></script>
 
    <link href="https://cdn.aerisapi.com/sdk/js/mapsgl/latest/aerisweather.mapsgl.css" rel="stylesheet" />
    <script defer src="https://cdn.aerisapi.com/sdk/js/mapsgl/latest/aerisweather.mapsgl.js"></script>
    
 
    <style>
    body, html {
        margin: 0;
        padding: 0;
    }
    #map {
        height: 100vh;
        width: 100%;
    }
    </style>
 
</head>
<body>
    <div id="map"></div>
 
    <script>
    window.addEventListener('load', () => {
 
        mapboxgl.accessToken = 'MAPBOX_TOKEN';
        const map = new mapboxgl.Map({
            container: document.getElementById('map'),
            style: 'mapbox://styles/mapbox/dark-v9',
            center: [-73.961, 31.984],
            zoom: 2
        });
        
        const account = new aerisweather.mapsgl.Account('CLIENT_ID', 'CLIENT_SECRET');
        const controller = new aerisweather.mapsgl.MapboxMapController(map, { account });
 
        controller.on('load', () => {
            controller.addWeatherLayer('lightning-strikes', {
                type: 'symbol',
                paint: {
                    symbol: {
                        shader: `
        #extension GL_OES_standard_derivatives : enable
 
        precision mediump float;
 
        uniform vec2 resolution;
        uniform float dpr;
        uniform float time;
 
        varying vec2 vUv;
        varying vec2 vPosition;
        varying float vFactor;
        varying float vRandom;
 
        float rand(float x) {
            return fract(sin(x)*75154.32912);
        }
 
        float rand3d(vec3 x) {
            return fract(375.10297 * sin(dot(x, vec3(103.0139,227.0595,31.05914))));
        }
 
        float noise(float x) {
        float i = floor(x);
        float a = rand(i), b = rand(i+1.);
        float f = x - i;
            return mix(a,b,f);
        }
 
        float perlin(float x) {
            float r=0.,s=1.,w=1.;
            for (int i=0; i<6; i++) {
                s *= 2.0;
                w *= 0.5;
                r += w * noise(s*x);
            }
            return r;
        }
 
        float noise3d(vec3 x) {
            vec3 i = floor(x);
            float i000 = rand3d(i+vec3(0.,0.,0.)), i001 = rand3d(i+vec3(0.,0.,1.));
            float i010 = rand3d(i+vec3(0.,1.,0.)), i011 = rand3d(i+vec3(0.,1.,1.));
            float i100 = rand3d(i+vec3(1.,0.,0.)), i101 = rand3d(i+vec3(1.,0.,1.));
            float i110 = rand3d(i+vec3(1.,1.,0.)), i111 = rand3d(i+vec3(1.,1.,1.));
            vec3 f = x - i;
            return mix(mix(mix(i000,i001,f.z), mix(i010,i011,f.z), f.y),
                mix(mix(i100,i101,f.z), mix(i110,i111,f.z), f.y), f.x);
        }
 
        float perlin3d(vec3 x) {
            float r = 0.0;
            float w = 1.0, s = 1.0;
            for (int i=0; i<5; i++) {
                w *= 0.5;
                s *= 2.0;
                r += w * noise3d(s * x);
            }
            return r;
        }
 
        #define COL1 vec4(0, 0, 0, 0) / 255.0
        #define COL2 vec4(235, 241, 245, 255) / 255.0
 
        #define SIZE 100
        #define FLASH_POWER .8
        #define RADIUS .001
        #define SPEED .0018
        #define SEED 
 
        void main() {
            vec2 pos = vUv;
 
            float dist = length(2.0 * pos - 1.0) * 2.0;
            float x = time + 0.1;
 
            float m = 0.2 + 0.2 * vFactor; // max duration of strike
            float i = floor(x/m);
            float f = x/m - i;
            float k = vFactor; // frequency of strikes
            float n = noise(i);
            float t = ceil(n-k); // occurrence
            float d = max(0., n-k) / (1.-k); // duration
            float o = ceil(t - f - (1. - d)); // occurrence with duration
 
            float fx = 4.;
            if (o == 1.) {
                fx += 10. * vFactor;
            }
 
            fx = max(4., fx);
            float g = fx / (dist * (10. + 20.)) * FLASH_POWER; 
 
            // smooth out edges to avoid fading extending beyond the symbol's bounds
            float edgeFadeFactor = smoothstep(0.5, 1.0, dist);
            float invertedEdgeFadeFactor = 1.0 - edgeFadeFactor;
 
            vec4 color = mix(COL1, COL2, g);
            color.a *= min(1.0, 0.5 + vFactor) * invertedEdgeFadeFactor;
            
            gl_FragColor = color;
            gl_FragColor.rgb *= gl_FragColor.a;
    }
                        `,
                        size: { width: 60, height: 60 },
                        animated: true,
                        blending: 2,
                        pitchWithMap: true,
                        allowOverlap: true,
                        factor: (data) => {
                            const max = 200;
                            return (200 - data.age) / 200;
                        }
                    }
                }
            });
        });
 
    });
    </script>
 
</body>
</html>