Help optimizing scene

"use client";

import { useGLTF } from "@react-three/drei";
import React, { Suspense, useEffect, useRef, useState, useMemo } from "react";
import type { Mesh, MeshStandardMaterial, Group } from "three";
import * as THREE from "three";
import type { GLTF } from "three-stdlib";
import { Canvas, useFrame } from "@react-three/fiber";
import { Bloom, EffectComposer, ToneMapping } from "@react-three/postprocessing";
import { BlendFunction, ToneMappingMode } from "postprocessing";
import useSettingsStore from "@/lib/SettingsStore";

type GLTFResult = GLTF & {
	nodes: {
		nurbsToPoly1001: Mesh;
		stamendisk_proto_v1obj: Mesh;
		petal_proto2obj: Mesh;
		stamen_proto_2obj: Mesh;
	};
	materials: {
		"initialShadingGroup.002": MeshStandardMaterial;
		"Default.017": MeshStandardMaterial;
		"Default.016": MeshStandardMaterial;
		"Default.025": MeshStandardMaterial;
	};
};

type FlowerProps = JSX.IntrinsicElements["group"] & {
	delay: number;
	position: THREE.Vector3;
};

// Optimized firefly with more efficient point light settings
function Firefly({
	targetRangeMaxX,
	targetRangeMinX,
	targetRangeMaxY,
	targetRangeMinY,
	targetRangeMaxZ,
	targetRangeMinZ,
}: {
	targetRangeMaxX: number;
	targetRangeMaxY: number;
	targetRangeMaxZ: number;
	targetRangeMinX: number;
	targetRangeMinY: number;
	targetRangeMinZ: number;
}) {
	const groupRef = useRef<Group>(null);
	const flyRef = useRef<Mesh>(null);
	// Store velocity and target position
	const velocity = useRef(new THREE.Vector3(0, 0, 0));
	const targetPosition = useRef(new THREE.Vector3());

	// Initial position - using useMemo to prevent recreation on rerenders
	const position = useMemo(() => new THREE.Vector3(
		Math.random() * 7 - 1,
		Math.random(),
		Math.random() * 7 - 5,
	), []);

	// Generate new random target position
	const updateTarget = () => {
		targetPosition.current.set(
			Math.random() * (targetRangeMaxX - targetRangeMinX) + targetRangeMinX,
			Math.random() * (targetRangeMaxY - targetRangeMinY) + targetRangeMinY,
			Math.random() * (targetRangeMaxZ - targetRangeMinZ) + targetRangeMinZ,
		);
	};

	// Initialize first target using useEffect to ensure it runs only once
	useEffect(() => {
		updateTarget();
	}, []);

	useFrame((state, delta) => {
		if (!groupRef.current) return;

		// Calculate direction to target
		const direction = new THREE.Vector3().subVectors(
			targetPosition.current,
			groupRef.current.position,
		);

		// Get distance to target
		const distance = direction.length();

		// If we're close to target, generate new one
		if (distance < 0.1) {
			updateTarget();
		}

		// Normalize direction and apply speed
		direction.normalize().multiplyScalar(0.5);

		// Smooth velocity changes (steering behavior)
		velocity.current.lerp(direction, 0.1);

		// Update position
		groupRef.current.position.add(velocity.current.clone().multiplyScalar(delta));

		// Add subtle wobble
		groupRef.current.position.y += Math.sin(state.clock.elapsedTime * 2) * 0.002;
	});

	return (
		<group ref={groupRef} position={position}>
			<mesh ref={flyRef} scale={1} castShadow>
				<sphereGeometry args={[0.04, 4, 8]} />
				<meshStandardMaterial
					emissive={new THREE.Color("#caf816")}
					emissiveIntensity={2}
				/>
				<pointLight
					color={"white"}
					intensity={1}
					distance={2}
					decay={2}
					castShadow={false} // Disable shadow casting for firefly lights
					shadow-mapSize-width={256} // Reduced shadow map size
					shadow-mapSize-height={256} // Reduced shadow map size
				/>
			</mesh>
		</group>
	);
}

export function Flower(props: FlowerProps) {
	const { nodes, materials } = useGLTF(
		"/wildflower-transformed.glb",
	) as GLTFResult;

	// Create firefly UUIDs only once
	const fireflyIds = useMemo(() =>
		Array.from({ length: 10 }, () => crypto.randomUUID()),
		[]);

	return (
		<group {...props} dispose={null}>
			<mesh
				scale={[0.128, 0.185, 0.128]}
				geometry={nodes.nurbsToPoly1001.geometry}
				material={materials["initialShadingGroup.002"]}
				rotation={[Math.PI / 2, 0, 0]}
				castShadow
				receiveShadow
			/>

			<group
				position={[0, 0, 0]}
			>
				<mesh
					geometry={nodes.stamendisk_proto_v1obj.geometry}
					material={materials["Default.017"]}
					position={[0.15, 5.385, -0.203]}
					scale={-0.016}
					rotation={[-0.867, 0.252, 2.512]}
					castShadow
					receiveShadow
				/>
				<mesh
					geometry={nodes.petal_proto2obj.geometry}
					material={materials["Default.016"]}
					scale={1.19}
					position={[0.266, 5.143, -0.353]}
					rotation={[2.852, -1.135, 2.268]}
					castShadow
					receiveShadow
				/>
				<mesh
					geometry={nodes.stamen_proto_2obj.geometry}
					material={materials["Default.025"]}
					position={[0.015, 5.338, -0.224]}
					scale={0.93}
					rotation={[0.566, -0.829, 0.048]}
					castShadow
					receiveShadow
				/>
				<Suspense fallback={null}>
				{fireflyIds.map((id) => (
					<Firefly
						key={`firefly-${id}`}
						targetRangeMaxX={5}
						targetRangeMaxY={7}
						targetRangeMaxZ={1}
						targetRangeMinX={-5}
						targetRangeMinY={4}
						targetRangeMinZ={-0.1}
					/>
				))}
				</Suspense>
			</group>
		</group>
	);
}

useGLTF.preload("/wildflower-transformed.glb");

export default function Wildflower() {
	// Get 3D enabled state from Zustand store
	const is3DEnabled = useSettingsStore((state) => state.is3DEnabled);

	// Performance settings for the entire scene
	const [dpr, setDpr] = useState<[number, number]>([1, 2]); // Default pixel ratio

	// Set optimal DPR based on device capability on mount only
	useEffect(() => {
		const optimalDpr = Math.min(1.5, window.devicePixelRatio);
		setDpr([1, optimalDpr]);
	}, []);

	// If 3D is disabled, don't render anything
	if (!is3DEnabled) {
		return null;
	}

	// Otherwise, render the 3D scene
	return (
		<main className="w-screen h-screen top-0 left-0 fixed pointer-events-none">
			<Canvas
				dpr={dpr}
				gl={{
					powerPreference: 'high-performance',
					antialias: false,
					stencil: false
				}}
			>
				<color attach="background" args={['#000000']} />
				<fog attach="fog" args={['#000000', 10, 40]} />
				<Suspense fallback={null}>
					<Flower position={new THREE.Vector3(1.8, -4.8, 0)} delay={0.8} />
					<Flower
						position={new THREE.Vector3(4.5, -8, -4)}
						rotation={[0, -Math.PI / 3, 0]}
						delay={0.5}
					/>
					<Flower
						position={new THREE.Vector3(9, -8, -4)}
						rotation={[0, 0, 0]}
						delay={1}
					/>
					<Flower
						position={new THREE.Vector3(8, -6, -1)}
						rotation={[0, -Math.PI / 21, 0]}
						delay={1}
					/>
					<Flower
						position={new THREE.Vector3(0.4, -2.5, 1.9)}
						scale={0.4}
						rotation={[0, -Math.PI / 36, Math.PI / 9]}
						delay={1}
					/>

					{/* Main light source - optimized settings */}
					<ambientLight intensity={0.01} />
				</Suspense>
				<EffectComposer>
					<Bloom
						blendFunction={BlendFunction.ADD}
						intensity={1.0}
						luminanceThreshold={0.9}
						luminanceSmoothing={0.025}
						mipmapBlur={false}
					/>
					<ToneMapping
						blendFunction={BlendFunction.NORMAL}
						mode={ToneMappingMode.REINHARD} // Reinhard tone mapping
					/>
				</EffectComposer>
			</Canvas>
		</main>
	);
}

The initial render causes my webpage to freeze, and increasing the number of point lights to 15 reduced fps by a lot. The scene is just a dimly lit bunch of flowers slowly being lit up by a bunch of fireflies.

I assume that you applied tips from here

and here

…but… maybe you can squeeze that lemon :lemon: more…

Lights are really expensive in 3d rendering generally.
Usually you want to keep #of lights below 6 and limit your shadowcasting lights to 1.