Loading EXR File causes Browser Hang

Hey everyone,

I’m facing a challenging issue and could really use some assistance. I’m trying to load an EXR file into my shader, following what I believe to be the standard implementation procedure. However, whenever I run my project, the browser hangs, and there’s no output in the console whatsoever.

Any insights or suggestions would be greatly appreciated. Thanks in advance for your help!

import * as THREE from "three";
import GSAP from "gsap";

import Animations from "./Animations";
import SmoothScroll from "./SmoothScroll";

import vertexShader from "./shaders/vertex.glsl";
import fragmentShader from "./shaders/fragment.glsl";

import { EXRLoader } from 'three/examples/jsm/loaders/EXRLoader.js';
/* import { EXRLoader } from "https://threejs.org/examples/jsm/loaders/EXRLoader.js" */


class ScrollStage {
  constructor() {
    this.element = document.querySelector(".content");
    this.elements = {
      line: this.element.querySelector(".layout__line"),
      exrTexture: { value: new THREE.DataTexture() }
    };

    this.time = 0;
    this.viewport = {
      width: window.innerWidth,
      height: window.innerHeight,
    };

    this.mouse = {
      x: 0,
      y: 0,
    };

    this.scroll = {
      height: 0,
      limit: 0,
      hard: 0,
      soft: 0,
      ease: 0.05,
      normalized: 0,
      running: false,
    };

    this.settings = {
      // vertex
      uFrequency: {
        start: 0,
        end: 3,
      },
      uAmplitude: {
        start: 4,
        end: 4,
      },
      uDensity: {
        start: 1,
        end: 1,
      },
      uStrength: {
        start: 0,
        end: 1.1,
      },
      // fragment
      uDeepPurple: {
        // max 1
        start: 1,
        end: 0,
      },
      uOpacity: {
        // max 1
        start: 0.1,
        end: 0.66,
      },
    };

    this.scene = new THREE.Scene();

    this.renderer = new THREE.WebGLRenderer({
      canvas: document.querySelector("#webgl"),
      antialias: true,
      alpha: true,
    });

    this.canvas = this.renderer.domElement;

    var frustumSize = 1;
    var aspect = window.innerWidth / window.innerHeight;
    this.camera = new THREE.OrthographicCamera(
      frustumSize / -2,
      frustumSize / 2,
      frustumSize / 2,
      frustumSize / -2,
      -1000,
      1000
    );




    this.clock = new THREE.Clock();

    this.smoothScroll = new SmoothScroll({
      element: this.element,
      viewport: this.viewport,
      scroll: this.scroll,
    });

    GSAP.defaults({
      ease: "power2",
      duration: 6.6,
      overwrite: true,
    });

    this.updateScrollAnimations = this.updateScrollAnimations.bind(this);
    this.update = this.update.bind(this);

    this.init();
  }

  init() {
    // Load EXR texture
    const exrLoader = new EXRLoader();
    exrLoader.load('/env.exr', (texture) => {
      texture.mapping = THREE.EquirectangularReflectionMapping;
      this.elements.exrTexture.value = texture;
      console.log("EXR texture loaded successfully!");
    }, undefined, (error) => {
      console.error("Error loading EXR texture:", error);
    }); 

    this.addCamera();
    this.addMesh();
    this.addEventListeners();
    this.onResize();
    this.update();
  }

  addCamera() {
    this.camera.position.set(0, 0, 2.5);
    this.scene.add(this.camera);
  }

  addMesh() {
    this.geometry = new THREE.PlaneGeometry(1, 1, 1, 1);
    this.material = new THREE.ShaderMaterial({
      blending: THREE.AdditiveBlending,
      transparent: true,
      vertexShader,
      fragmentShader,
      uniforms: {
        uFrequency: { value: this.settings.uFrequency.start },
        uAmplitude: { value: this.settings.uAmplitude.start },
        uDensity: { value: this.settings.uDensity.start },
        uStrength: { value: this.settings.uStrength.start },
        uDeepPurple: { value: this.settings.uDeepPurple.start },
        uOpacity: { value: this.settings.uOpacity.start },
        uResX: { value: this.viewport.width },
        uResY: { value: this.viewport.height },
        uTime: { value: this.time },
        uScroll: { value: this.scroll.normalized },
        uAspect: { type: "v2", value: new THREE.Vector2() },

        exrTexture: this.elements.exrTexture
      },
    });

    this.plane = new THREE.Mesh(this.geometry, this.material);
    this.scene.add(this.plane);
  }

  /**
   * SCROLL BASED ANIMATIONS
   */
  updateScrollAnimations() {
    this.scroll.running = false;
    this.scroll.normalized = (this.scroll.hard / this.scroll.limit).toFixed(1);
    this.material.uniforms.uScroll.value = this.scroll.normalized;
    console.info(`Scroll position: ${this.scroll.normalized}`);
    /* GSAP.to(this.plane.rotation, {
      x: this.scroll.normalized * Math.PI,
    }); */

    GSAP.to(this.elements.line, {
      scaleX: this.scroll.normalized,
      transformOrigin: "left",
      duration: 1.5,
      ease: "ease",
    });

    for (const key in this.settings) {
      if (this.settings[key].start !== this.settings[key].end) {
        GSAP.to(this.plane.material.uniforms[key], {
          value:
            this.settings[key].start +
            this.scroll.normalized *
            (this.settings[key].end - this.settings[key].start),
        });
      }
    }
  }

  /**
   * EVENTS
   */
  addEventListeners() {
    window.addEventListener("load", this.onLoad.bind(this));
    window.addEventListener("scroll", this.onScroll.bind(this));
    window.addEventListener("resize", this.onResize.bind(this));
  }

  onLoad() {
    document.body.classList.remove("loading");

    this.animations = new Animations(this.element, this.camera);
  }

  onMouseMove(event) {
    // play with it!
    // enable / disable / change x, y, multiplier …

    this.mouse.x = (event.clientX / this.viewport.width).toFixed(2) * 4;
    this.mouse.y = (event.clientY / this.viewport.height).toFixed(2) * 2;

    GSAP.to(this.plane.material.uniforms.uFrequency, { value: this.mouse.x });
    GSAP.to(this.plane.material.uniforms.uAmplitude, { value: this.mouse.x });
    GSAP.to(this.plane.material.uniforms.uDensity, { value: this.mouse.y });
    GSAP.to(this.plane.material.uniforms.uStrength, { value: this.mouse.y });
    // GSAP.to(this.plane.material.uniforms.uDeepPurple, { value: this.mouse.x })
    // GSAP.to(this.plane.material.uniforms.uOpacity, { value: this.mouse.y })

    console.info(`X: ${this.mouse.x}  |  Y: ${this.mouse.y}`);
  }

  printSettings(settings) {
    //console.log("uFrequency:", settings.uFrequency.start);
    /* console.log("uAmplitude:", settings.uAmplitude);
    console.log("uDensity:", settings.uDensity);
    console.log("uStrength:", settings.uStrength);
    console.log("uDeepPurple:", settings.uDeepPurple);
    console.log("uOpacity:", settings.uOpacity); */
  }

  onScroll() {
    if (!this.scroll.running) {
      window.requestAnimationFrame(this.updateScrollAnimations);

      this.scroll.running = true;
    }
  }

  onResize() {
    this.viewport.width = window.innerWidth;
    this.viewport.height = window.innerHeight;

    this.smoothScroll.onResize();

    const imageAspect = 1; // Set the desired image aspect ratio
    const viewportRatio = this.viewport.width / this.viewport.height;

    let a1, a2;
    if (viewportRatio > imageAspect) {
      a1 = imageAspect / viewportRatio;
      a2 = 1;
    } else {
      a1 = 1;
      a2 = viewportRatio / imageAspect;
    }

    this.camera.aspect = this.viewport.width / this.viewport.height;
    this.camera.updateProjectionMatrix();
    this.renderer.setSize(this.viewport.width, this.viewport.height);
    this.renderer.setPixelRatio(Math.min(window.devicePixelRatio, 1.5));
    this.material.uniforms.uResX.value = this.viewport.width;
    this.material.uniforms.uResY.value = this.viewport.height;
    this.material.uniforms.uAspect.value.x = a1;
    this.material.uniforms.uAspect.value.y = a2;

    //console.log(`${this.viewport.width}/${this.viewport.height}`);
  }

  /**
   * LOOP
   */
  update() {
    const elapsedTime = this.clock.getElapsedTime();
    /* this.plane.rotation.y = elapsedTime * 0.05; */
    this.smoothScroll.update();
    this.render();
    window.requestAnimationFrame(this.update);
  }

  /**
   * RENDER
   */
  render() {
    this.printSettings(this.settings);
    this.time += 0.05;
    this.material.uniforms.uTime.value = this.time;
    this.renderer.render(this.scene, this.camera);
  }
}

new ScrollStage();

How big is the file? Does the network tab show that it’s getting fully loaded?

The file is small (approx. 1 MB) and “Pending” is displayed in the network view.

Does it still freeze if you comment out EXR loading? Does stop freezing if you comment out any specific method in the shared class?

And on a separate note:

  1. You should not put requestAnimationFrame in events. You should have only a single rendering loop, running independently of everything else (if you want to pause rendering, just put an if-statement around the renderer.render call, but keep the single loop.)

  2. Console.log value of “this” in your update method.

if I remove this part of the code it works fine.

Thanks for the separate notes, I’m going to look into it!