Loading HTML images into Uniform Shader

Greetings people!

I hope you are doing well.

I would be very grateful if anyone can help me fix this issue.

I am loading HTML images “using Array.from()” to use them as textures in Shader Material.

The code below is what I am using now which works, but, when the page first loads I get a black plane which shows that the images are not fully loaded.

However, on page refresh then all the images get rendered on the plane.

I would prefer the images get loaded and then fire the page-load to avoid the black screens.

What I can do to fix this problem? Thank you!

Here is my code:

// HTML to WebGL
let allMeshes = [];
let imagesToGL = Array.from(document.getElementsByClassName("work-gl-img"));
let geometry = new THREE.PlaneBufferGeometry(1, 1, 89, 89);

function htmlToWebgl() {

  imagesToGL.forEach((image) => {
    let texture = new THREE.Texture(image);
    texture.needsUpdate = true;

    let material = new THREE.ShaderMaterial({
      uniforms: {
        uTime: { type: "f", value: 0 },
        uDirection: { type: "f", value: 0 },
        uProgress: { type: "f", value: 0 },
        uMouseSpeed: { type: "f", value: 0 },
        uMouse: { type: "v2", value: new THREE.Vector2(0, 0) },
        uTexture: new THREE.Uniform(texture),
        uResolution: { type: "v4", value: new THREE.Vector4() },
      },

      side: THREE.DoubleSided,
      vertexShader: vertex,
      fragmentShader: fragment,

    });

    let mesh = new THREE.Mesh(geometry, material);
    mesh.userData.image = image;
    mesh.userData.material = material;

    setScalePosition(mesh);
    allMeshes.push(mesh);

    scene.add(mesh);

  });
}

htmlToWebgl();

This problem should automatically go away if you load your textures via TextureLoader instead. Any chances to refactor your code and not query the images from the document?

Otherwise you have to register custom event listeners to the load event for all images and only set Texture.needsUpdate to true in the respective callback.

BTW: The type property is not necessary anymore when defining custom uniforms.

@Mugen87 Thank you.

However, it will be even more helpful if you demonstrate how to do that using the code up there. Also, how can I call the images from the HTML if I do not query selecting them?

By the way, I am more than happy to share with you my source code in order to get constructive solution as to how to go about this as I have been trying to fix this issue with no success.

Thank you very much!

If you share your code as live example with your code, I can try to update it with my proposed solution:

https://jsfiddle.net/f2Lommf5/

1 Like

@Mugen87 I have just updated the code in the link you shared.
Also here is Google Drive link to the full source code.

When doing so, jsfiddle should generate a new link.

It seems I can’t access to it. Can you make it publicly accessible?

@Mugen87 I have updated the permission on the google drive link. Let’s use that instead of jsfiddle. Thank you.

Load your images like so:

const loader = new THREE.TextureLoader();
const requests = [
	loader.loadAsync( 'images/bl-image-2.jpg' ),
	loader.loadAsync( 'images/bl-image-3.jpg' ),
	loader.loadAsync( 'images/wk-image-1.jpg' ),
	loader.loadAsync( 'images/wk-image-2.jpg' ),
	loader.loadAsync( 'images/wk-image-3.jpg' ),
	loader.loadAsync( 'images/wk-image-4.jpg' )
];

And then you can do:

Promise.all( requests ).then( ( textures ) => {

    textures.forEach( ( texture ) => {

        // do something with each texture

    } );

} );

I’ve updated your code so you can actually see a texture on a plane. TBH, the code structure in your app was a bit confusing so I had to remove some of the code to see a usable output.

code.zip (2.7 MB)

@Mugen87 Thank you very much. I am very grateful.

One question, this means, that I cannot load a CMS based images using TextureLoader. I always have to pass in the assets URL directly into the loader argument. Is that so…?

OR is there a work-around for such…?

Once again, much appreciated my brother. Thank you!

When using TextureLoader, then yes.

Yes, but that means you have to register event listeners on the image elements like so:

image.addEventListener( 'load', onLoad, false );

In onLoad(), you can count until all images are loaded and then setup the textures like so:

const texture = new Texture( image );
texture.needsUpdate = true;

However, TextureLoader is the more appropriate solution.

@Mugen87 Thank you very much. You’ve been very helpful. :relaxed:

1 Like