Three.js如何加载跨域图片?

38

我知道这个问题之前已经被问过了,我已经阅读了所有能找到的问题和答案,但是没有一个有效果。

我正在本地服务器(IIS)上运行。我试图从imgur加载一张图片,然后使用以下代码将其作为物体的纹理:

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;
    }

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

我尝试在代码的开头、结尾和其他各个位置放置THREE.ImageUtils.crossOrigin = "anonymous";或其变体,同时添加了一个web.config文件。

<?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>

但是那并没有起作用,这个问题同样也出现在托管在bitbucket.org上的网站中,这告诉我我的代码还有缺陷。 似乎它在sphere.material = new THREE.MeshBasicMaterial({map: mapOverlay, needsUpdate: true});这一行出现了错误,如果我注释掉这行代码,则不会出现错误(但网格就无法更新)。 我真的不知道还有什么尝试的方法,在此感谢任何帮助。
5个回答

60

更新

在较新版本的THREE.js中,默认处理跨域图像。 THREE.ImageUtils.loadTexture已被弃用。通常使用TextureLoader

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

原始答案

这个有效

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

这是一个样例

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>

注意:在使用 THREE.ImageUtils.loadTexture 时不需要使用 new
为了将一个跨域的图片加载到 WebGL 中,发送图片的服务器必须响应正确的标头。仅仅告诉服务器你希望使用跨域图片是不够的,这只是告诉服务器你请求使用该图片的权限。
你可以设置 img.crossOrigin 或者在 THREE 中使用 THREE.ImageUtils.crossOrigin,将其设置为 '''anonymous'(与 '' 相同)或者 'use-credentials'。浏览器会看到你设置了 crossOrigin 并向服务器发送某些标头。服务器读取这些标头,决定你的域是否有使用该图片的权限,如果有则向浏览器发送某些标头。如果浏览器看到这些标头,则允许你使用该图片。
最重要的一点是,需要注意的是 服务器 必须发送这些标头。大多数服务器不会发送这些标头,但是 imgur.com 明显发送了这些标头。我怀疑 bitlocker 没有发送这些标头,但我没有测试过。
此外,你必须设置 crossOrigin。即使服务器发送了正确的标头,如果你没有设置 crossOrigin,浏览器也不会允许你以非预期的方式使用该图片。

看起来我的问题有两个。我在loadTexture()方法中使用了错误的变量,但我还使用了new关键字。移除new似乎让它正常工作了。谢谢! - Kurt
不错!我也在做和@Braffin一样的事情。谢谢 :) - Kevin Jurkowski
6
请注意,如果你不能控制的外部因素(例如第三方库)创建了一个 new THREE.TextureLoader(),你仍然可以使用 THREE.TextureLoader.prototype.crossOrigin = ''; 来设置它们的跨域策略。 - Andy Ray
3
这个答案对我们没用,下面这个是使用 loader.crossOrigin = ''; 的更新实现,并且有效。 - sanjsanj
1
@gman - 你能否详细说明一下有关设置crossOrigin的最后一句话?我遇到了类似的错误,在按照你(和Michal)的修复方法之后,现在出现了标准的“Origin is therefore not allowed access error”。我应该如何正确设置头文件?在js中吗?谢谢。 - Matteo
显示剩余2条评论

26

更新:已弃用的方法

我遇到了这个问题,并尝试了答案中提供的解决方案,但由于新版本的THREE.js中已经弃用了该方法,所以它无法正常工作。我发布这个答案,以防其他人遇到同样的问题。尽管已经弃用,gman在原始答案中提供的信息非常有用,我建议大家阅读。

THREE.ImageUtils.loadTexture()

这个方法自原问题和答案发布以来已经过时。

目前加载纹理的方式:

// 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 ) {}
);

4
谢谢!对于其他人而言,截至2016年6月,这是正确的答案。 - Jona
1
太遗憾了,旧版本提供了一个纹理对象,让你可以立即使用它。新版本在完成之前不会给你纹理对象,这意味着使用起来要困难得多。 - gman
谢谢!这同样适用于其他加载器,比如在这个例子中的MTLLoader:https://github.com/mrdoob/three.js/blob/master/examples/webgl_loader_obj_mtl.html - Paul

1

我找到了一个解决跨域问题的方案,适用于图片和JSON模型。这个方法在本地主机上也可以使用。

在我的情况下,我从端口3001的NodeJS中加载游戏,以便使用Socket.io。我想从端口80获取3D模型。

假设所有模型都在目录game/dist/models/**/*中。

我创建了一个PHP文件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 );
    });

玩得开心!


1

错误的屏幕截图

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)

我在编写Codepen演示时遇到了相同的问题:

https://codepen.io/fritx/project/editor/AoLRoy

通过将three.js从0.86升级到>=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) -->

0
在TextureLoader中的图像URL末尾添加“?not-from-cache-please”作为参数。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接