1000 light source test (1 per mesh + instances)

I just figured I’d make a note here that I was surprised at the number of light sources I could pump out using the light.includedOnlyMeshes feature. No problem with 1000 objects + instances with 1000 light sources minus the increasingly recursive compilation times.

Demo url (works on mobile using webgl):
https://babylon-1000-lights.netlify.app/

Kind of curious if Babylon will see Forward+ in the future, but I guess you can kind of fake it as long as you are fine with a really long initial shader compilation as you get into the 700+ light source range. It’s a nice thing to go with instances otherwise. Can you export these scenes after you’ve done the initial compilation so you don’t have to do it more than once? That’s the only major drawback to this method if I wanted to actually deliver something.

Here’s a single threaded test with 20K objects on a single thread with WebGPU. About 35FPS average which is pretty impressive to me. (RTX 3070 so overqualified)

6 Likes

Welcome abroad! This is a very cool demo! Unfortunately I think the shader would have to be recompiled every time, right @sebavan ?

it should actually be cached for any consistent combinations of lights.

Like exportable cached? I see a number of tools around for that just not sure how it all works.

Is there one bulk array call to set the lights/objects in the scene? Just doing it in series makes it recursive AF. I’ve been messing around with transpilers and automated pipelines lately so am just wondering what’s going on under the hood for it to get exponentially slower when you set things up linearly.

This is how I’m learning wgsl, though I can’t get textures to render in my pipeline yet: GitHub - joshbrew/webgpujs: Write full featured WGSL pipelines in plain javascript.

not exportable cache, but in babylon any variants of shader is being cached under the hood.

This is not exportable as there are no webgl/webgpu ways to precompile shaders.

So in terms of setting up the scene though, pretty sure what’s happening is each time I create a light or object it’s going through and rebuilding the whole pipeline, is there a way around that? Takes a good minute to compile with 1000 lights/objects, just not clear if there’s a fast order of operations there.

could you create a tiny repro in the pg so we can have a look ?

I wrote this in like 10 min but I am working on a game demo rn so this is of interest to me to speed up the scene creation with all the sources, though I’ll probably use max 100 lights in practice.

<html>
<head>
    <title>BabylonJS Example</title>
    <script src="https://cdn.babylonjs.com/babylon.js"></script>
    <script src="https://cdn.babylonjs.com/loaders/babylonjs.loaders.min.js"></script>
</head>
<body>
    FPS: <span id="fps">0</span><br/>
    <canvas id="renderCanvas" touch-action="none" width="100%" height="100%" style="position:absolute; z-index:2; width:100%; height:100%;"></canvas>
    <span style="position:absolute">Compiling a 1000 light source shader is slow... (Note this is a single threaded test). Compiled:<span id="cp">0</span></span>
    <script>
        
        const setupScene = async () => {

            // Get the canvas element
            const canvas = document.getElementById("renderCanvas");

            // Initialize BabylonJS
            const engine = new BABYLON.Engine(canvas);
            //const engine = new BABYLON.WebGPUEngine(canvas);
            //engine.initAsync().then(async () => {
                // Create a basic BabylonJS scene
                const scene = new BABYLON.Scene(engine);

                const N = 1000;
                const rtN = Math.sqrt(N);

                // Add a camera
                const camera = new BABYLON.ArcRotateCamera("camera", -Math.PI/2, Math.PI / 2, 150, new BABYLON.Vector3(3*rtN/2, 3*rtN/2, 0), scene);
                camera.attachControl(canvas, true);

                // Store light data for rotation
                const lights = [];


                // Create 1000 instances with point lights
                for (let i = 0; i < N; i++) {

                    if(i % 100 === 0) {
                        document.getElementById('cp').innerText = i;
                        await new Promise((res)=>{setTimeout(()=>{res(true);},0.001);});
                    }

                    // Create a sphere instance
                    let sphere = BABYLON.MeshBuilder.CreateSphere("sphere", {diameter: 1}, scene);
                    sphere.position.x = (i % rtN) * 3;
                    sphere.position.y = Math.floor(i / rtN) * 3;

                    // Create a point light for each sphere with random color
                    let pointLight = new BABYLON.PointLight("pointLight" + i, new BABYLON.Vector3(sphere.position.x, sphere.position.y, sphere.position.z), scene);
                    pointLight.includedOnlyMeshes.push(sphere);
                    
                    // Random color
                    pointLight.diffuse = new BABYLON.Color3(Math.random(), Math.random(), Math.random());

                    // Store light data
                    lights.push({
                        light: pointLight,
                        sphere: sphere,
                        angle: Math.random() * Math.PI * 2, // Initial angle
                        rotationSpeed: (Math.random() > 0.5 ? -1 : 1) * 0.02, // Random rotation speed and direction
                        radius: 2
                    });

                    // Create additional instances projecting in the Z direction to show additional scene complexity
                    for (let j = 1; j <= 2; j++) {
                        let sphereInstance = sphere.createInstance("sphereInstance" + i + "_" + j);
                        sphereInstance.position = sphere.position.clone();
                        sphereInstance.position.z += j * 2; // Adjust Z position
                    }

                }

                let divFps = document.getElementById("fps")

                // Start the render loop
                engine.runRenderLoop(() => {
                    // Update light positions
                    lights.forEach((data) => {
                        data.angle += data.rotationSpeed;
                        data.light.position.x = data.sphere.position.x + data.radius * Math.cos(data.angle);
                        data.light.position.z = data.sphere.position.z + data.radius * Math.sin(data.angle);// Keep Y position constant to maintain a horizontal orbit
                        data.light.position.y = data.sphere.position.y + data.radius * Math.sin(data.angle); 
                    });

                    scene.render();
                    divFps.innerHTML = engine.getFps().toFixed()
                });

                // Resize the engine on window resize
                window.addEventListener('resize', function(){
                    engine.resize();
                });
           // });

        }

        setupScene();
      
    </script>
</body>
</html>

It seems very nice, full 120 fps

Hell yeah, not even in a dedicated worker too!

I’m working on randomly/dynamically lit maze cells this way which is a nice scaling problem.