How to create a comparable Light Probes WebGL vs WebGPU

Hi, I’m really motivated to learn about light probes but I find it hard to apply in WebGPU, I am running in different way to code Light Probes but it seems like it isn’t really working whatever techniques I am doing.

This is the example of WebGL. lightprobewebgl

currently this what I code have in light probe.

      <Canvas
        camera={{ position: [0, 5, 2], fov: 75 }}
        shadows
        dpr={[1, 2]}
        flat
        // frameloop="demand"
        gl={async (props) => {
          // const r = new THREE.WebGPURenderer({
          const r = new THREE.WebGLRenderer({
            ...props,
            antialias: true,
            powerPreference: 'high-performance',
          });
          r.shadowMap.enabled = true;
          // r.inspector = new InspectorNode()
          r.toneMapping = THREE.ACESFilmicToneMapping;
          // r.toneMapping = THREE.NoToneMapping;
          r.toneMappingExposure = 1;
          await r.init();
          return r;
        }}
        style={{ width: '100%', height: '100vh' }}
      >
...
     </Canvas>
const GlobalIlluminationGrid = ({ resolution = 6, showHelper = false, visible = true }) => {
    const { scene, gl } = useThree()
    const gridRef = useRef(null)
    const helperRef = useRef(null)

    useEffect(() => {
        // ── 0. Force renderer to match vanilla settings ───────────────────────
        // The vanilla sets these before anything is drawn. If your <Canvas>
        // didn't set them, do it here. Setting them again when already correct
        // is a no-op so this is always safe.
        gl.shadowMap.enabled = true
        gl.shadowMap.type = THREE.PCFSoftShadowMap
        gl.toneMapping = THREE.ACESFilmicToneMapping
        gl.toneMappingExposure = 1.0

        // ── 1. Tear down previous probes ─────────────────────────────────────
        if (gridRef.current) {
            scene.remove(gridRef.current)
            gridRef.current.dispose()
            gridRef.current = null
        }
        if (helperRef.current) {
            // LightProbeGridHelper has no .dispose() — remove only
            scene.remove(helperRef.current)
            helperRef.current = null
        }

        // ── 2. Force world matrix update ─────────────────────────────────────
        // Vanilla adds all scene objects synchronously then bakes.
        // In R3F, JSX children are added by the time useEffect fires, but we
        // must call updateMatrixWorld so the probe cube-cameras see correct transforms.
        scene.updateMatrixWorld(true)

        // ── 3. Create probe grid — dimensions match vanilla exactly ──────────
        //   new LightProbeGrid(5.6, 4.7, 5.6, res, res, res)
        //   probes.position.set(0, 2.45, 0)
        const probes = new LightProbeGrid(5.6, 4.7, 5.6, resolution, resolution, resolution)
        probes.position.set(0, 2.45, 0)
        probes.updateMatrixWorld(true)

        // ── 4. Bake — gl IS the renderer in R3F ─────────────────────────────
        probes.bake(gl, scene, { cubemapSize: 32, near: 0.05, far: 20 })

        probes.visible = visible
        scene.add(probes)
        gridRef.current = probes

        // ── 5. Force material shader recompile ───────────────────────────────
        // Materials that compiled before the probe grid was in the scene don't
        // have SH light probe support in their shaders. needsUpdate forces a
        // full shader recompile on the next render frame.
        scene.traverse((child) => {
            if (child.isMesh && child.material) {
                child.material.needsUpdate = true
            }
        })

        // ── 6. Optional debug helper ─────────────────────────────────────────
        if (showHelper) {
            const helper = new LightProbeGridHelper(probes)
            scene.add(helper)
            helperRef.current = helper
        }

        return () => {
            if (gridRef.current) {
                scene.remove(gridRef.current)
                gridRef.current.dispose()
                gridRef.current = null
            }
            if (helperRef.current) {
                scene.remove(helperRef.current)
                helperRef.current = null
            }
        }
    }, [scene, gl, resolution, showHelper, visible])

    return null
}

vs

my R3F + WebGPU.
Note: I also edited my Canvas with WebGPURenderer since I don’t want WebGLRenderer for this.

import * as THREE from 'three/webgpu'
import { useThree } from '@react-three/fiber'
import { useEffect, useMemo, useRef } from 'react'

// ─── WGPUMesh ─────────────────────────────────────────────────────────────────

const WGPUMesh = ({ geometry, color, ...meshProps }) => {
    const meshRef = useRef()

    useEffect(() => {
        if (!meshRef.current) return

        const mat = new THREE.MeshStandardNodeMaterial()
        mat.color.set(color)
        meshRef.current.material = mat

        return () => mat.dispose()
    }, [color])

    return (
        <mesh ref={meshRef} {...meshProps}>
            {geometry}
        </mesh>
    )
}

// ─── Single Probe ─────────────────────────────────────────────────────────────

const RealisticProbe = ({ position, showHelper }) => {
    const { scene } = useThree()
    const probeRef = useRef()

    const lightProbe = useMemo(() => {
        const probe = new THREE.LightProbe()
        probe.intensity = 1.0

        probe.sh.coefficients[0].set(0.38, 0.36, 0.32)
        probe.sh.coefficients[1].set(0.00, 0.25, 0.00)
        probe.sh.coefficients[2].set(0.00, 0.10, 0.00)
        probe.sh.coefficients[3].set(0.00, 0.00, 0.00)
        probe.sh.coefficients[4].set(-0.08, 0.00, 0.00)
        probe.sh.coefficients[5].set(0.00, 0.00, 0.00)
        probe.sh.coefficients[6].set(0.00, 0.06, 0.00)
        probe.sh.coefficients[7].set(0.00, 0.00, 0.00)
        probe.sh.coefficients[8].set(0.00, 0.00, 0.00)

        return probe
    }, [])

    useEffect(() => {
        if (probeRef.current) {
            probeRef.current.position.set(...position)
        }
    }, [position])

    useEffect(() => {
        if (!showHelper) return

        let helper
        import('three/addons/helpers/LightProbeHelper.js')
            .then(({ LightProbeHelper }) => {
                helper = new LightProbeHelper(lightProbe, 0.2)
                helper.position.set(...position)
                scene.add(helper)
            })
            .catch(() => { })

        return () => {
            if (helper) {
                scene.remove(helper)
                helper.dispose()
            }
        }
    }, [scene, lightProbe, showHelper, position])

    return <primitive ref={probeRef} object={lightProbe} position={position} />
}

And this is the error I received when I switch to WebGPU.

WebGPURenderer does not yet support this class. It is still experimental and we add a version for WebGPURenderer in one or two releases when we have more user feedback and know the class design works well.

Updated the docs to clarify this aspect:

Thanks, I hope it will be add soon. Cause I wanna create cool things in light probes :wink: