38

I know this has been asked before, and I've read ever question and answer I've been able to find, but nothing works.

I'm running this on a local server (IIS). I'm trying to load an image from imgur and then use that as texture for an object using the code:

var savedImage = /[^?]*$/.exec(location.search)[0];
if (savedImage != "") { savedImageLoad("http://i.imgur.com/" + savedImage + ".jpg"); };

    function savedImageLoad(image) {
        var mapOverlay = new THREE.ImageUtils.loadTexture(image);
        sphere.material = new THREE.MeshBasicMaterial({map: mapOverlay, needsUpdate: true});;
        sphere.geometry.buffersNeedUpdate = true;
        sphere.geometry.uvsNeedUpdate = true;
    }

But it's giving the error:

Uncaught SecurityError: Failed to execute 'texImage2D' on 'WebGLRenderingContext': The cross-origin image at http://i.imgur.com/uBD0g95.jpg may not be loaded.

I've tried placing THREE.ImageUtils.crossOrigin = "anonymous";, or some variation of, at the beginning of my code, at the end, and at other various points. I've added a web.config with

<?xml version="1.0" encoding="utf-8"?>
<configuration>
 <system.webServer>
  <httpProtocol>
    <customHeaders>
      <add name="Access-Control-Allow-Origin" value="*" />
      <add name="Access-Control-Allow-Methods" value="GET,PUT,POST,DELETE,OPTIONS" />
      <add name="Access-Control-Allow-Headers" value="Content-Type" />
    </customHeaders>
  </httpProtocol>
 </system.webServer>
</configuration>

but that didn't work. This also doesn't work on a site hosted on bitbucket.org, which to me says I'm missing something in my code.

It seems to be failing at the sphere.material = new THREE.MeshBasicMaterial({map: mapOverlay, needsUpdate: true});; line, as if I comment that out then there's no error (but then the mesh isn't updated).

I'm really at a loss of what else to try here and any help would be appreciated.

gman
  • 100,619
  • 31
  • 269
  • 393
Kurt
  • 1,868
  • 3
  • 21
  • 42

5 Answers5

60

Update

In newer versions of THREE.js cross origin images are handled by default. THREE.ImageUtils.loadTexture is deprecated. It's common to use TextureLoader

const loader = new THREE.TextureLoader();
const mapOverlay = loader.load('http://i.imgur.com/3tU4Vig.jpg');

Original Answer

This works

THREE.ImageUtils.crossOrigin = '';
var mapOverlay = THREE.ImageUtils.loadTexture('http://i.imgur.com/3tU4Vig.jpg');

Here's a sample

var canvas = document.getElementById("c");
var renderer = new THREE.WebGLRenderer({canvas: canvas});

var camera = new THREE.PerspectiveCamera( 20, 1, 1, 10000 );
var scene = new THREE.Scene();
var sphereGeo = new THREE.SphereGeometry(40, 16, 8);

var light = new THREE.DirectionalLight(0xE0E0FF, 1);
light.position.set(200, 500, 200);
scene.add(light);
var light = new THREE.DirectionalLight(0xFFE0E0, 0.5);
light.position.set(-200, -500, -200);
scene.add(light);

camera.position.z = 300;

THREE.ImageUtils.crossOrigin = '';
var texture = THREE.ImageUtils.loadTexture('http://i.imgur.com/3tU4Vig.jpg');
var material = new THREE.MeshPhongMaterial({
    map: texture,
    specular: 0xFFFFFF,
    shininess: 30,
    shading: THREE.FlatShading,
});
var mesh = new THREE.Mesh(sphereGeo, material);
scene.add(mesh);

function resize() {
    var width = canvas.clientWidth;
    var height = canvas.clientHeight;
    if (canvas.width != width ||
        canvas.height != height) {
          renderer.setSize(canvas.clientWidth, canvas.clientHeight, false);  // don't update the style. Why does three.js fight CSS? It should respect CSS :(
        
        camera.aspect = canvas.clientWidth / canvas.clientHeight;
        camera.updateProjectionMatrix();
    }
}

function render(time) {
    time *= 0.001;  // seconds
    resize();
    mesh.rotation.y = time;
    renderer.render(scene, camera);
    requestAnimationFrame(render);
}
requestAnimationFrame(render);
body {
    margin: 0;
}

#c {
    width: 100vw;
    height: 100vh;
    display: block;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/84/three.min.js"></script>
<canvas id="c"></canvas>

Note: You don't use new with THREE.ImageUtils.loadTexture

In order to load an image cross-origin into WebGL the server that's sending the image has to respond with the correct headers. It's not enough for you to say you want to use the image cross-origin. All that does is tell the server you're requesting permission to use the image.

You can set img.crossOrigin, or in THREE's case THREE.ImageUtils.crossOrigin, to either '', 'anonymous' which is the same as '', or you can set it to 'use-credentials' which sends even more info to the server. The browser sees you set crossOrigin and sends certain headers to the server. The server reads those headers, decides if your domain has permission to use the image and if you do have permission it sends certain headers back to the browser. The browser, if it sees those headers will then let you use the image.

The biggest point to take away from the above is the server has to send the headers. Most servers don't send those headers. imgur.com does apparently. I suspect bitlocker does not though I didn't test it.

Also you have to set crossOrigin. If you don't the browser won't allow you to use the img in ways your not supposed to be able to even if the server sends the correct header.

gman
  • 100,619
  • 31
  • 269
  • 393
  • Seems my problem was two-fold. I was using the incorrect variable in the loadTexture() method, but I was also using `new` with said method. Removing `new` seems to have made this work. Thanks! – Kurt Jun 09 '14 at 16:42
  • Nice! Was doing the same thing as @Braffin. Thanks :) – Kevin Jurkowski Feb 04 '15 at 20:36
  • 6
    Note, if someone outside of your control (like a third party library) is making a `new THREE.TextureLoader()`, you can still set their cross origin policy with `THREE.TextureLoader.prototype.crossOrigin = '';` – Andy Ray Jan 03 '16 at 21:58
  • 3
    This answer did not work for us, the one below is the newer implementation using `loader.crossOrigin = '';` and works. – sanjsanj Jun 17 '16 at 09:47
  • 1
    @gman - Can you please elaborate a bit more on your last sentence about setting crossOrigin? I was getting a similar error and after following your (and Michal's) fix I now get the standard `Origin is therefor not allowed access error`. How should I set the headers properly? In the `js`? Thanks – Matteo Jul 13 '17 at 17:56
  • THREE.ImageUtils.crossOrigin = '*' Worked for me, loading from s3 – Richard Lindhout Oct 30 '17 at 09:39
  • Working in 2019 -- Thanks! var textureLoader = new THREE.TextureLoader(); textureLoader.crossOrigin = "Anonymous"; – Andy Oct 01 '19 at 20:39
26

UPDATE: Deprecated method

I came across this problem and applied solution from the answer to find it not working due to the deprecated method in newer releases of the THREE.js. I'm posting this answer in case anyone get the same issue. Despite deprecation, information provided by gman in original answer are most helpful and I recommend reading it.

THREE.ImageUtils.loadTexture()

method became deprecated since the original question and answer.

Current way to load the texture:

// instantiate a loader
var loader = new THREE.TextureLoader();

//allow cross origin loading
loader.crossOrigin = '';

// load a resource
loader.load('textures/land_ocean_ice_cloud_2048.jpg',
    // Function when resource is loaded
    function ( texture ) {},
    // Function called when download progresses
    function ( xhr ) {},
    // Function called when download errors
    function ( xhr ) {}
);
Michal
  • 416
  • 4
  • 6
  • 4
    Thank you! To anyone else, this is the right answer as of June 2016 – Jona Jun 15 '16 at 08:36
  • 1
    That's so sad, the old one gave you a texture object so you could use it immediately. The new one doesn't give you an texture object until it finishes which means it's much harder to use correctly. – gman Mar 17 '17 at 14:13
  • Thanks! This equally applies to other loaders, such as `MTLLoader` in this example: https://github.com/mrdoob/three.js/blob/master/examples/webgl_loader_obj_mtl.html – Paul May 16 '17 at 15:14
1

I found a solution for images and JSON models according to the cross-domain issue. This always works, also on Localhost.

In my case, I'm loading the game from NodeJS at port 3001, to work with Socket.io. I want the 3d models from port 80.

Assume all models are in directory: game/dist/models/**/*

I created a PHP file game/dist/models/json.php:

<?php

header('Access-Control-Allow-Credentials: true');
header('Access-Control-Allow-Methods: GET');
header('Access-Control-Allow-Origin: http://localhost:3001');


if (isset($_GET['file']))
{
    switch($_GET['file'])
    {
        case 'house1':
            header('Content-Type: application/json');
            $file = 'houses/house1.json';
            $json = json_decode(file_get_contents($file),TRUE);
            echo json_encode($json, TRUE);
            die();
        break;
    }
}
?>

ThreeJS:

var loader = new THREE.ObjectLoader();  
    loader.load(distPath+"models/json.php?file=house1",function ( obj ) {
         scene.add( obj );
    });

Have fun!

1

A screencapture of the error

VM266 three.min.js:127 THREE.WebGLState: DOMException: Failed to execute 'texImage2D' on 'WebGLRenderingContext': The image element contains cross-origin data, and may not be loaded.
    at Object.texImage2D (https://unpkg.com/three@0.86.0/build/three.min.js:127:99)
    at dg.r [as setTexture2D] (https://unpkg.com/three@0.86.0/build/three.min.js:103:189)

Just met the same issue while working on my codepen demo: https://codepen.io/fritx/project/editor/AoLRoy

Solved by upgrading three.js from 0.86 to >=0.87

- <script src="https://unpkg.com/three@0.86.0/build/three.min.js"></script>
+ <script src="https://unpkg.com/three@0.87.0/build/three.min.js"></script><!-- >=0.87 (img.crossorigin) -->
Fritz Lin
  • 351
  • 3
  • 2
0

add "?not-from-cache-please" at the end of image URL as a parameter in the TextureLoader.

Shawnpy
  • 21
  • 3