Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ThreeJS updating texture with HTML canvas gives "WebGL: INVALID_VALUE: texImage2D: no canvas" on mobile (iPhone Safari, etc)

I am trying to update a THREE.Texture ".image" property with an HTML5 canvas object. This works on Chromium (MacOSX) on my laptop. However, iPhone Safari and iPhone Chrome both does not work. What could be the root cause and how to fix this?

Using Web Inspector in Safari, I get this error message:

WebGL: INVALID_VALUE: texImage2D: no canvas.

I made sure the canvas is completely drawn before being updated, using the below code to update the material:

material.map.image = loaded_canvas[curr_id]; // loaded_canvas stores canvas that has been completed loaded already, drawn by Image() objects.
material.map.needsUpdate = true; 

Here is how material is used:

var geometry = new THREE.SphereGeometry(100.0, 32, 32);
var material = new THREE.MeshBasicMaterial({
    map: THREE.ImageUtils.loadTexture(image_path),
    side: THREE.BackSide,
});

Strangely, if I use THREE.ImageUtils.loadTexture to load an image, it works fine. However, my use case is that I have to use an canvas object (multiple images on a canvas).

Thanks.

like image 239
Log0 Avatar asked Jun 09 '15 06:06

Log0


1 Answers

According to the Texture docs, the .image property is more of a "getter" for the image element created via the TextureLoader.load() method. If you want to create a texture with a <canvas> element, you can create it with new THREE.Texture(canvas); as demonstrated via the snippet below. The texture creation takes place in the makeCanvasTexture() function:

function makeCanvasTexture() {
    // Create canvas context
    var canvas = document.createElement("canvas");
    var context = canvas.getContext("2d");
    canvas.width = 32;
    canvas.height = 32;

    // Draw radial gradient
    var grad = context.createRadialGradient(16, 16, 6, 16, 16, 16);
    grad.addColorStop(0, "rgba(255, 150, 0, 1.0");
    grad.addColorStop(0.5, "rgba(255, 0, 150, 1.0");
    grad.addColorStop(1, "rgba(0, 150, 255, 1.0)");
    context.fillStyle = grad;
    context.fillRect(0, 0, 32, 32);

    // Use canvas to create texture
    var canvasTexture = new THREE.Texture(canvas);
    canvasTexture.needsUpdate = true;

    return canvasTexture;
}

// Boilerplate Three.js setup
var camera, scene, renderer;
renderer = new THREE.WebGLRenderer({antialias: true});
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
scene = new THREE.Scene();
scene.background = new THREE.Color(0xe1e1e1);
camera = new THREE.PerspectiveCamera(35, window.innerWidth / window.innerHeight, 0.1, 100);
camera.position.set(0, 30, -30);
camera.lookAt(0, 0, 0);

const ambient = new THREE.DirectionalLight(0xddeeff, 1);
ambient.position.set(0, 1, 1);

// Using canvas as material map texture:
const floor = new THREE.Mesh(
    new THREE.BoxBufferGeometry(20, 0.25, 20),
    new THREE.MeshBasicMaterial({map: makeCanvasTexture()})
);

scene.add(ambient, floor);

function animate(t) {
	// Animate mesh
    floor.rotation.z += Math.sin(t * 0.001) * 0.1;
	renderer.render(scene, camera);
	requestAnimationFrame(animate);
}

animate(0);
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/109/three.min.js"></script>

Keep in mind that WebGL texture size is limited depending on each device's graphics card capabilities, so you'll get errors if you try to create a canvas that breaches this limitation. You can see the limit of your current device by visiting https://webglreport.com/ and looking at the "Max texture size" readout on the right. Mine is 8192px².

enter image description here

Likewise, you can access this information programmatically with THREE.WebGLRenderer.capabilities.maxTextureSize as explained in this documentation page so you can use this number to prevent canvas sizes from going beyond the device's limit.

like image 54
Marquizzo Avatar answered Nov 05 '22 21:11

Marquizzo