Fixing performances in scene

Hi its been a long time since I wasn’t here. I was looking for how to improve performances my web that I’ve created before but I don’t know how to like improve performances in 3D scene cause I was use in basics and medium strategy… I don’t know where to research deep performances in advance 3D scene so…I’m using typescript here so its kinda messy I think?

I got this super high network in dev tools and I don’t like it.

image

And this is the website you can look for it for yourself.

I can show my full codes here if its fine. I just wanna know how to be able have a good performance both in desktop or cellphones…

This my MusicAmp.jsx that you can see in the music that does like spectrum.

import React, { useEffect, useRef, useMemo } from 'react';
import { useFrame } from '@react-three/fiber';
import * as THREE from 'three'
import { useAudio } from './music/AudioSpectrum';

const MusicAmp = () => {
    const { analyser,bufferLength,dataArray,numBars,barRefs } = useAudio()

    let barWidth:number = 0.005; // Adjusted barWidth
    let gap:number = 0.020; // Adjusted gap
    
    const smoothingFactor = 0.085;
    
    useFrame(() => {
        if (analyser.current && bufferLength.current > 0) {
            analyser.current.getByteFrequencyData(dataArray.current);
            const half = Math.ceil(numBars / 2);
            let maxFrequency = 0;
    
            for (let i = 0; i < half; i++) {
                const barHeight = dataArray.current[i] / 256;
    
                maxFrequency = Math.max(maxFrequency, barHeight);
    
                const leftBar = barRefs.current[half - i - 1]?.current;
                if (leftBar) {
                    const targetScaleY = barHeight * 3 || 0.1;
                    leftBar.scale.y += (targetScaleY - leftBar.scale.y) * smoothingFactor;
                }
    
                const rightBar = barRefs.current[half + i]?.current;
                if (rightBar) {
                    const targetScaleY = barHeight * 3 || 0.1;
                    rightBar.scale.y += (targetScaleY - rightBar.scale.y) * smoothingFactor;
                }
            }
        }
    });
    



    return (
        <>
            <group position={[0, 0, -0.2]}>
            {Array(numBars)
                .fill(null)
                .map((_, i) => {
                const half = Math.ceil(numBars / 2);
                const position = i < half
                    ? [
                        -(half - i) * (barWidth + gap) + gap / 2,
                        0.35,
                        0
                    ]
                    : [
                        (i - half) * (barWidth + gap) + gap / 2,
                        0.35,
                        0
                    ];

                return (
                    <mesh key={i} ref={barRefs.current[i]} position={new THREE.Vector3(...position)}>
                    <boxGeometry args={[barWidth, 0.1, barWidth]} />
                    <meshStandardMaterial 
                        color={0x3268a8} 
                        toneMapped={false}
                        emissive={0xffffff}
                        emissiveIntensity={1}
                    />
                    </mesh>
                );
                })}
            </group>
        </>
    );
};

export default MusicAmp;

And this is the whole scene.

import * as THREE from 'three'
import { useState, useRef, Suspense, useMemo, useEffect, useLayoutEffect } from 'react'
import { Canvas, useThree, useFrame, useLoader, extend } from '@react-three/fiber'
import { OrbitControls, useTexture } from '@react-three/drei'
import { SVGLoader } from 'three/examples/jsm/loaders/SVGLoader'
import { EffectComposer, Bloom } from '@react-three/postprocessing'
import { RigProps } from '@/props/CanvasProps'
import { gsap } from 'gsap'
import Ground from './Ground'
import MusicAmp from './MusicAmp'
import { useAudio } from './music/AudioSpectrum'


function Diamonds({color, position,scale,highFrequency }:any) {

    const ref = useRef<any>(null)
    const { paths: [path] } : any = useLoader(SVGLoader, '/bloom/triangle.svg') // prettier-ignore
    const geom1 = useMemo(() => SVGLoader.pointsToStroke(path.subPaths[0].getPoints(200), path.userData.style), [])

    const { paths: [music] } : any = useLoader(SVGLoader, '/bloom/music.svg') // prettier-ignore
    const geom2 = useMemo(() => SVGLoader.pointsToStroke(music.subPaths[0].getPoints(200), music.userData.style), [])

    return (
        <group ref={ref}>
            <mesh
                geometry={geom1}
                position={position}
                scale={scale}
            >
                <meshStandardMaterial
                    color={color} 
                    toneMapped={false} 
                    emissive={0xffffff}
                    emissiveIntensity={2}
                />
            </mesh>
            <mesh
                geometry={geom2}
                position={[position[0] + 0.05,0.9,position[2]]}
                scale={scale}
                rotation={[ Math.PI ,0,0]}
            >
                <meshStandardMaterial
                    side={THREE.DoubleSide}
                    color={color} 
                    toneMapped={false} 
                    emissive={0xffffff}
                    emissiveIntensity={2}
                />
            </mesh>
        </group>
    )
}

function Circle({...props}:any) {

    const { videoRef,loading,play } = useAudio()
    const circleRef = useRef<any>(null);

    useLayoutEffect(() => {
        let g = circleRef.current
        let z = 1.25
        let p = g.parameters;
        let hw = p.width * 0.8;
        
        let a = new THREE.Vector2(-hw, 0);
        let b = new THREE.Vector2(0, z);
        let c = new THREE.Vector2(hw, 0);
        
        let ab = new THREE.Vector2().subVectors(a, b);
        let bc = new THREE.Vector2().subVectors(b, c);
        let ac = new THREE.Vector2().subVectors(a, c);
        
        let r = Math.round((ab.length() * bc.length() * ac.length()) / (2 * Math.abs(ab.cross(ac))));
        
        // console.log(r)
        let center = new THREE.Vector2(0, z - r);
        let baseV = new THREE.Vector2().subVectors(a, center);
        let baseAngle = baseV.angle() - (Math.PI / 2);

        let arc = baseAngle * 2;
        
        let uv = g.attributes.uv;
        let pos = g.attributes.position;
        let mainV = new THREE.Vector2();
        console.log(uv)
        console.log(pos)
        for (let i = 0; i < uv.count; i++){
            let uvRatio = 1 - uv.getX(i);
            let y = pos.getY(i);
            mainV.copy(c).rotateAround(center, (arc * (uvRatio)));
            pos.setXYZ(i , mainV.x, y, -mainV.y);
        }
        
        pos.needsUpdate = true;
    },[circleRef])

    useEffect(() => {
        if (videoRef.current && play) {
            videoRef.current.volume = 0.2
            videoRef.current.load();
            if (loading && play) {
                videoRef.current.play();
            }
        }
    }, [loading]);
    
    const texture = useMemo(() => {
        if (videoRef.current) {
            return new THREE.VideoTexture(videoRef.current);
        }
    }, [videoRef]);
    
    return (
        <>
            <mesh
            {...props}
            >
                <planeGeometry args={[2,2.5,32]}
                    ref={circleRef}
                />
                <meshStandardMaterial
                    color={0xedebeb}
                    toneMapped={false}
                    map={texture}
                    side={THREE.DoubleSide}
                />
            </mesh>
        </>
    )
}

function Rig({ children, loading }: RigProps) {
    const ref = useRef<any>(null);
    const vec = new THREE.Vector3();
    const { camera, mouse } = useThree();

    const [animationStarted, setAnimationStarted] = useState(false);

    const lookAtStart = new THREE.Vector3(0, 1, 8);
    const lookAtMid = new THREE.Vector3(1, 1, 0);
    const lookAtEnd = new THREE.Vector3(0, 1, -8);

    const lookAtSpline = new THREE.CatmullRomCurve3([lookAtStart, lookAtMid, lookAtEnd]);

    const animationDuration = 4;
    const elapsedTimeRef = useRef<number>(0);

    useEffect(() => {
        camera.lookAt(lookAtStart);
    }, []);

    useEffect(() => {
        if (loading) {
            const timer = setTimeout(() => {
                setAnimationStarted(true);
            }, 1000);
            return () => clearTimeout(timer);
        } else {
            setAnimationStarted(false);
        }
    }, [loading]);

    useFrame((state, delta) => {
        if (loading && animationStarted) {
            elapsedTimeRef.current += delta;
            const t = Math.min(elapsedTimeRef.current / animationDuration, 1);
    
            camera.position.lerpVectors(new THREE.Vector3(0, 1, 1.2), new THREE.Vector3(0, 1, 1.2), t);
    
            const currentLookAt = lookAtSpline.getPoint(t);
            camera.lookAt(currentLookAt);
    
            if (ref.current) {
                const clampedMouseY = mouse.y > 0 ? THREE.MathUtils.clamp(mouse.y, -Infinity, 0.5) : -0.5 ;
                ref.current.position.lerp(vec.set(mouse.x * 0.02, clampedMouseY * 0.1, 0), 0.005);
                ref.current.rotation.y = THREE.MathUtils.lerp(
                    ref.current.rotation.y,
                    (-mouse.x * Math.PI) / 100,
                    0.1
                );
            }
        }
    });    

    return <group ref={ref}>{children}</group>;
}



const Scene = () => {

    const {loading,highFrequency} = useAudio()

    const lightRef1 = useRef(null)
  

    const { camera, gl }:any = useThree();

    useEffect(() => {
        const onWindowResize = () => {
            if (camera) {
                camera.aspect = window.innerWidth / window.innerHeight;
                camera.updateProjectionMatrix();
                gl.setSize(window.innerWidth, window.innerHeight);
            }
        };

        window.addEventListener("resize", onWindowResize);
    }, [camera, gl, camera]);
    
    return(
    <>  
        <color attach="background" args={['black']} />
        <ambientLight ref={lightRef1} />
        {/* <OrbitControls 
            // enableZoom={false} 
            // enablePan={false} 
            // enableRotate={false} 
        /> */}

        <Suspense fallback={null}>
            <Rig
                loading={loading}
            >
                <Circle
                    position={[0,1,-0.4]}
                />
                <MusicAmp />
                <Diamonds
                    position={[-1.5,0.5,-0.4]}
                    scale={0.002}
                    highFrequency={highFrequency}
                />
                <Diamonds
                    position={[1,0.5,-0.4]}
                    scale={0.002}
                    highFrequency={highFrequency}
                />
                <Ground 
                    position={[0,0,0]} 
                    rotation={[- Math.PI / 2 , 0 , 0 ]}
                />
                <Ground 
                    position={[0,2.5,2.5]} 
                    rotation={[- Math.PI , 0 , 0 ]}
                />
                <Ground 
                    position={[-2.5,2.5,0]} 
                    rotation={[ 0 ,  Math.PI / 2 , 0 ]}
                />
                <Ground 
                    position={[-0,2.5, -2.5]} 
                    rotation={[ 0 , 0 , 0 ]}
                />
                <Ground 
                    position={[2.5,2.5, 0]} 
                    rotation={[ 0 ,  -Math.PI / 2 , 0 ]}
                />
            </Rig>
            <EffectComposer multisampling={4}>
                <Bloom kernelSize={1} luminanceThreshold={1} luminanceSmoothing={0.4} intensity={0.5} />
            </EffectComposer>
        </Suspense>
    </>
    )
}


const Loaders = () => {

    const { play,loading,playRef,pauseRef,setLoading,videoRef } = useAudio()

    const refName = useRef<HTMLDivElement>(null)
    const refSelectorName = useRef<HTMLDivElement>(null)

    useEffect(() => {
        let tl = gsap.timeline()
        .to(refSelectorName.current,{
            "--name-height":"100%",
            duration:0.8,
            ease:"power4.inOut"
        })
        .to(refName.current,{
            translateX:0,
            opacity:1,
            duration:2,
            ease:"power4.inOut"
        })
    },[])


    const bricks = useRef<Array<HTMLSpanElement | null>>([null, null]);
    useEffect(() => {
        const tl = gsap.timeline({ repeat: 0 });
        const totalDuration = 3; // Animation should complete in 5 seconds.
        const totalProgress = 99; // 99 steps for 00% to 99%
        const stepDuration = totalDuration / totalProgress;

        for (let step = 0; step < totalProgress; step++) {
            bricks.current.forEach((brick, index) => {
                const digitIndex = 1 - index; // Reverse the index to make the last digit faster
                const newNumber = Math.floor((step + 1) / Math.pow(10, digitIndex)) % 10;
                if (brick) {
                    tl.to(
                    brick,
                        {
                            duration: stepDuration,
                            ease: 'none',
                            onComplete: () => {
                                brick.textContent = newNumber + '';
                            },
                        },
                    step * stepDuration
                    );
                }
            });
        }

        tl.to('.loader_display', { duration: 0.5, opacity: 0, display: 'none', ease: 'power2.in' }, totalDuration).then(() => {
            if (!loading) {
                setLoading(true);
            }
        });
    }, []);


    return (
        <div className="loaders">
            <video
                ref={videoRef}
                style={{ display: 'none' }}
                src="./img/shelter-cut.mp4"
                autoPlay
            />
            <div className="name" ref={refSelectorName}>
                <h4>
                    <span className="word" ref={refName}>
                        Julle Myth Vicentillo
                    </span>
                </h4>
            </div>
            <div className="loader_display">
                <h3 className="text">
                    {Array.from({ length: 2 }, (_, index) => (
                        <span key={index} className="load_text brick">
                        <span
                            className="slide"
                            ref={(el) => {
                            if (el) {
                                bricks.current[index] = el;
                            }
                            }}
                        >
                            {index === 0 ? '0' : '0'}
                        </span>
                        </span>
                    ))}
                    <span className="load_text">%</span>
                </h3>
            </div>
            
            <Canvas
                className="loaders-composition"
                camera = {{ position: [0,1,1.2] }}
                shadows gl={{ antialias: true }}
            >
                <Scene />
            </Canvas>
            <div className="play-button" style={ loading ? { display:'flex' } : {display:'none'}}>
                <svg 
                    ref={playRef} width="24" height="24" 
                    viewBox="0 0 24 24" fill="none" 
                    xmlns="http://www.w3.org/2000/svg"
                    style={ play ? { display:'none' } : {display:"flex"}}
                >
                ....
                </svg>
                <svg 
                    ref={pauseRef} width="20" height="20" 
                    viewBox="0 0 20 20" fill="none" 
                    xmlns="http://www.w3.org/2000/svg"
                    style={ play ? { display:'flex' } : {display:"none"}}
                >
               ....
                </svg>
            </div>
        </div>
    )
}

export default Loaders



And this is the wall and ground from both back to back and left to right sides of wall.

import { MeshReflectorMaterial, useTexture } from '@react-three/drei'
import { LinearEncoding, RepeatWrapping, TextureLoader } from 'three'

import * as THREE from 'three'

import React, { useEffect } from 'react'
import { useLoader } from '@react-three/fiber'

type GroundProps = {
  rotation: number[];
  position: number[];
}

const Ground: React.FC<GroundProps> = ({ position,rotation }) => {

    const [roughness,normal] = useLoader(TextureLoader,[
        "./bloom/surface_texture.jpg",
        "./bloom/surface_normal.jpg",
    ])

    useEffect(() => {
        [normal,roughness].forEach((t) => {
            t.wrapS = RepeatWrapping
            t.wrapT = RepeatWrapping
            t.repeat.set(5,5)
        })
        normal.encoding = LinearEncoding
    },[normal,roughness])

    return (
        <>
            <mesh 
                rotation={new THREE.Euler(...rotation)}
                position={new THREE.Vector3(...position)}
            >
                <planeGeometry args={[5,5]}/>
                {/* <boxGeometry args={[5,5,5]}/> */}
                <MeshReflectorMaterial
                    envMapIntensity={0}
                    normalMap={normal}
                    roughnessMap={roughness}
                    dithering={true}
                    color={[0.015,0.015,0.015]}
                    roughness={0.4}
                    opacity={1}
                    blur={[1000,400]}
                    mixBlur={30}
                    transparent={true}
                    mixStrength={40}
                    mixContrast={1}
                    resolution={1024}
                    mirror={0}
                    side={THREE.DoubleSide}
                    depthScale={0.01}
                    minDepthThreshold={0.9}
                    maxDepthThreshold={1}
                    depthToBlurRatioBias={0.25}
                    reflectorOffset={0.2}
                />
            </mesh>
        </>
    )
}

export default Ground

Do you think there is better code to make a proper performances in cellphone and desktop?

You’re loading a 61 megabyte video…