CubeTexture + [ ImageData ] == WebGL error

I’m trying to build what should be a simple piece of code to slice up a stereo 360 cube map (cross) into a nice CubeTexture without having to manually cut it up and load six different textures. So. I use ImageLoader and then draw the whole thing to a canvas, and then pull six different ImageData objects from that. So far, so good.

I can successfully apply any of those ImageData objects to a new THREE.Texture and it works fine. But when I try to pass an array of them to CubeTexture, I get this error once I set .needsUpdate true:

three.js:19863 THREE.WebGLState: TypeError: Failed to execute 'texImage2D' on 'WebGLRenderingContext': No function was found that matched the signature provided.

I get the same error if I call ImageUtils.getDataURL( imageData ) individually. They are successfully converted to PNGs (I can read out the URL) but the same type error happens when I try to apply them. Yet I can upload them individually as Texture. So if I wanted to make my cube out of six planes, that would work. It’s just that I don’t want to do that, I want to figure out what’s wrong with CubeTexture…

Anyone else run into this?

*** EDIT: I am reasonably sure this is a bug, because if I create a plain Texture out of each ImageData face and then a new MeshStandardMaterial from each of those textures, and push all those materials into the array for the cube, there are no errors. Code is below.

export class PanoramaCube extends THREE.Group {
    /* Take a stereoscopic equirectangular cross of arbitrary size, and slice it onto two three.js cubes,
     * one cube for each eye in WEBVR. */
    private _img:HTMLImageElement;
    private _geom:THREE.BoxBufferGeometry = new THREE.BoxBufferGeometry(20,20,20);
    private _oL:THREE.Mesh;
    private _oR:THREE.Mesh;
    constructor(public src:string) {
        super();
        this._img = new THREE.ImageLoader().load(src);
        $(this._img).on('load',this._createTextures.bind(this));                        
    }
    private async _createTextures(evt:Event):Promise<void> {
        THREE.ImageUtils.crossOrigin = '*';
        this._img.crossOrigin = '*';
        let canvas:HTMLCanvasElement = document.createElement('canvas');
        canvas.width = this._img.naturalWidth;
        canvas.height = this._img.naturalHeight;
        let ctx:CanvasRenderingContext2D = canvas.getContext('2d');
        ctx.drawImage(this._img, 0, 0, this._img.naturalWidth, this._img.naturalHeight); //
        
        let panels:any[] = [];
        let ord:number[] = [6,4,1,9,5,7]; //px, nx, py, ny, pz, nz... one side cross of the stereo image
        for (let i:number = 1; i < 3; i++) { //left and right
            for (let k of ord) {
                let row:number = Math.floor(k/4);
                let imgData:ImageData = ctx.getImageData((k % 4) * .125 * i * this._img.naturalWidth, row * (this._img.naturalHeight/3), 
                                                            this._img.naturalWidth * .125, this._img.naturalHeight/3);
                panels.push(imgData);
            }
            
            //THIS WORKS:
            let materials:THREE.MeshStandardMaterial[] = [];
            for (let p of panels) {
                let t:THREE.Texture = new THREE.Texture(p);
                t.needsUpdate = true;
                materials.push(new THREE.MeshStandardMaterial({map:t, side:THREE.BackSide}));
            }
            //it's not clear why applying an array of six materials to a cube causes each to be mapped onto its own side, but sure...
            if (i==1) this._oL = new THREE.Mesh(this._geom,materials);
                else this._oR = new THREE.Mesh(this._geom,materials);
            
            //THIS THROWS A WEBGL ERROR:
            let t:THREE.CubeTexture = new THREE.CubeTexture(panels); //give CubeTexture an array of ImageData
            t.needsUpdate = true;
            let testMat:THREE.MeshStandardMaterial = new THREE.MeshStandardMaterial({map:t}); 
            let someNewMesh:THREE.Mesh = new THREE.Mesh(this._geom,testMat); //crashland
            //
            
        }
        
        this._oL.layers.enable(1);
        this._oR.layers.set(2);
        this.add(this._oL);
        this.add(this._oR);
        
        $(canvas).remove();
    }
}

Hi!
Take a look at the source code of that example:
https://threejs.org/examples/?q=cube#webgl_panorama_cube
It uses a texture atlas.

1 Like

Well… that’s slower and worse than what I wrote, because it creates six canvases and then all the Image overhead. And it is creating six different materials and applying them to the cube…which is exactly what I don’t want to have to do. Mine draws one canvas and extracts the ImageDatas very efficiently. There should be no problem putting those six images in a single CubeTexture, and putting that into one material.

The problem is that CubeTexture is broken in some way.

More to the point, you should not have to create six separate textures for six separate materials to wrap a cube… and this is why CubeTexture exists.

As far as I know, cube maps intended to be used with .envMap parameter, not with .map.
If you want to put six images on the sides of a cube, you have to use an array of six materials. And that’s what the solution with a texture atlas does.

Everyting is Okay with THREE.CubeTexture().

If you want to use that cube texture as the background of a scene, then you can try to use it this way:
scene.background = yourCubeTexture;

1 Like

I believe the problem is not only with CubeTexture. The problem is in the new(ish) system of being able to pass an array of images to Texture. It fails to upload properly to the GPU if there is more than one ImageData in the array.

CubeTexture is intended to wrap a cube not just for backgrounds and not just for envMap… it is intended to wrap a cube and that is how it works with loaded images. It is failing when it is fed ImageData as an array, and the problem is in the way the data is being typed and/or possibly a crossdomain policy pollution occurring somewhere in ImageUtils. Again, I think it is a bug.

.map expects THREE.Texture(), not THREE.CubeTexture().

Any working examples of such an exotic technique?

2 Likes

As @prisoner849 mentioned, assigning a CubeTexture to Material.map is not valid. A CubeTexture is not a multi-material feature. If you have an object of type Geometry with multiple material indices or of type BufferGeometry with multiple groups, use an array of materials.

1 Like

how about using canvas.toDataURL()

var urlImage= document.createElement(‘img’);
urlImage.src=canvas.toDataURL();
images.push(urlImage);

var cubeTexture=new THREE.CubeTexture(images);