Load plane texture when in camera viewport

I’ve created a data visualisation using Three.js that shows thousands of images in 3D space and would like to only load the plane texture when the plane is within a threshold distance from the camera in order to save device resources. I can’t seem to get the texture switching to work when the plan is within camera view, I’ve even tried creating a different shader. The functions in question are updateMaterialsVisibility, loadAndApplyTexture, and isPlaneInCameraView

Here is the code I am using:

  import React, { useEffect, useRef, useState, useCallback } from "react";
  import * as THREE from "three";

  const ImageVisualisation = () => {
    const containerRef = useRef();
    const [data, setData] = useState([]);
    let textures;
    let camera;

    const isPlaneInCameraView = (plane, thresholdDistance) => {
      const frustum = new THREE.Frustum();
      const cameraViewProjectionMatrix = new THREE.Matrix4();

      camera.updateMatrixWorld();
      camera.matrixWorldInverse.copy(camera.matrixWorld).invert();
      cameraViewProjectionMatrix.multiplyMatrices(
        camera.projectionMatrix,
        camera.matrixWorldInverse
      );
      frustum.setFromProjectionMatrix(cameraViewProjectionMatrix);

      if (
        frustum.intersectsObject(plane) &&
        plane.position.distanceTo(camera.position) <= thresholdDistance
      ) {
        return true;
      }
      return false;
    };

    const loadAndApplyTexture = (mesh, imagePath) => {
      return new Promise((resolve) => {
        const textureLoader = new THREE.TextureLoader();
        textureLoader.load(imagePath, (texture) => {
          mesh.material.map = texture;
          mesh.material.color.set(0xffffff);
          mesh.material.needsUpdate = true;
          resolve();
        });
      });
    };

    const updateMaterialsVisibility = (thresholdDistance) => {
      if (!data || data.length === 0) return [];

      const texturePromises = [];

      for (let i = 0; i < textures.length; i++) {
        const mesh = textures[i];
        if (isPlaneInCameraView(mesh, thresholdDistance)) {
          if (!mesh.material.map) {
            const promise = loadAndApplyTexture(mesh, data[i].ImagePath);
            texturePromises.push(promise);
          }
        } else {
          if (mesh.material.map) {
            mesh.material.map.dispose();
            mesh.material.map = null;
            mesh.material.color.set(0x0000ff);
            mesh.material.needsUpdate = true;
          }
        }
      }

      return texturePromises;
    };

    useEffect(() => {
      const fetchData = async () => {
        const response = await fetch("data.json");
        const jsonData = await response.json();
        setData(jsonData);
        init(jsonData);
      };

      let scene, renderer;

      const init = (data) => {
        scene = new THREE.Scene();
        camera = new THREE.PerspectiveCamera(
          75,
          window.innerWidth / window.innerHeight,
          0.1,
          1000
        );

        renderer = new THREE.WebGLRenderer();
        renderer.setSize(window.innerWidth, window.innerHeight);
        containerRef.current.appendChild(renderer.domElement);

        textures = data.map((item, index) => {
          const geometry = new THREE.PlaneGeometry(1, 1);
          const material = blueMaterial;
          const mesh = new THREE.Mesh(geometry, material);
          mesh.position.set(
            Math.random() * 50 - 25,
            Math.random() * 50 - 25,
            Math.random() * 50 - 25
          );
          scene.add(mesh);
          return mesh;
        });

        camera.position.z = 100;

        animate();
      };

      async function animate() {
        requestAnimationFrame(animate);
        textures.forEach((texture) => {
          texture.rotation.y += 0.01;
        });

        const texturePromises = updateMaterialsVisibility(200);
        await Promise.all(texturePromises);

        // Move camera forward
        camera.position.z -= 0.1;

        renderer.render(scene, camera);
      }

      fetchData();

      return () => {
        if (renderer) {
          containerRef.current.removeChild(renderer.domElement);
        }
      };
    }, []);

    return <div ref={containerRef}></div>;
  };
  export default ImageVisualisation;

Perhaps you could replace all this code with just a single LOD containing 2 planes - one with a textured material, and one without ?

I just did that and it worked, thank you so much!