开发者问题收集

Angular 或 Three.js 不从服务器提取图像,而是从浏览器缓存中提取图像

2015-09-23
1390

请参阅本文末尾的更新 3,了解最新活动/插件

我在 Node/Express 应用上使用 AngularJS。在此应用中,我使用 three.js 显示 3D 对象。包裹此对象的纹理会发生变化,但文件名不会发生变化(例如,纹理图像的颜色会发生变化,但文件名仍为 Texture_0.png)。

纹理文件存储在 Azure Blob 存储中,我已启用 CORS,因此可以正常显示。如果我在新的会话中更新图像并将其上传到 Azure Blob 存储,则首次渲染 3D 对象时,它会显示带有变化的纹理。但是,每当我上传新的更改并尝试重新渲染时,它都会显示浏览器缓存的图像,而不是服务器中存储和引用的新图像。需要刷新页面才能看到更改,但我需要在单击弹出模式的按钮时显示该更改,而不是刷新页面。

如何让 AngularJS 在不刷新页面的情况下显示纹理图像的服务器版本而不是浏览器缓存?

我尝试添加一些随机数学运算来强制浏览器从服务器下载,但它不起作用。相关代码如下:

Three.JS 3D 对象代码:

$scope.generate3D = function () {

// 3D OBJECT - Variables
var texture0 = baseBlobURL + 'Texture_0.png?' + Math.random();
var boxDAE = baseBlobURL + 'Box.dae?' + Math.random();
var scene;
var camera;
var renderer;
var box;
var controls;
var newtexture;


// 3D OBJECT - Generate  

newtexture = THREE.ImageUtils.loadTexture(texture0);

//Instantiate a Collada loader
var loader = new THREE.ColladaLoader();

loader.options.convertUpAxis = true;
loader.load(boxDAE, function (collada) {

box = collada.scene;

box.traverse(function (child) {

if (child instanceof THREE.SkinnedMesh) {

var animation = new THREE.Animation(child, child.geometry.animation);
animation.play();

}
});

box.scale.x = box.scale.y = box.scale.z = .2;
box.updateMatrix();

init();
animate();
});

function init() {
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
renderer = new THREE.WebGLRenderer();

renderer.setClearColor(0xdddddd);

renderer.setSize(500, 500);

// Load the box file
scene.add(box);

// Lighting
var light = new THREE.AmbientLight();
scene.add(light);

// Camera
camera.position.x = 40;
camera.position.y = 40;
camera.position.z = 40;

camera.lookAt(scene.position);

// Rotation Controls
controls = new THREE.OrbitControls(camera, renderer.domElement);

controls.rotateSpeed = 5.0;
controls.zoomSpeed = 5;

controls.noZoom = false;
controls.noPan = false;

var myEl = angular.element(document.querySelector('#webGL-container'));
myEl.append(renderer.domElement);

}

function animate() {
requestAnimationFrame(animate);
renderer.render(scene, camera);               
}
}

Angular 函数调用 3D 对象并在 Modal 中弹出:

$scope.boxPreview = function () {
$scope.generate3D();
var modalInstance = $modal.open({
templateUrl: 'myModalContent.html',
controller: 'ModalInstanceCtrl',
size: 'lg'
}

Modal 嵌入为脚本标记(我需要在视图中使用这个,因为图像 URL 是唯一的,并且运行我的视图的代码需要这些变量以及 3D 对象代码):

<script type="text/ng-template" id="myModalContent.html">
    <div class="modal-header">
        <h3 class="modal-title">Box Preview</h3>
    </div>
    <div class="modal-body">

        <div id="webGL-container" width="500px">
        </div>

    </div>
    <div class="modal-footer">
        <button class="btn btn-primary" data-ng-click="ok()">Close</button>
    </div>
</script>

更新 1:

在 Chrome 中查看网络选项卡时,我可以看到它正在下拉正确的图像,但只是没有在 Modal 中显示它。我尝试禁用缓存,但这没有效果。就像 three.js 将图像保存在某处......但我只是不知道如何清除它。

更新 2:

使用 Mr.doob 的建议 在这篇文章中列出 ,我能够让它重新加载而无需刷新页面一次,然后我收到以下错误:

RangeError: Maximum call stack size exceeded

当我将以下代码添加到实例化加载器的位置时:

THREE.ImageLoader.prototype._load = THREE.ImageLoader.prototype.load;
THREE.ImageLoader.prototype.load = function (url, onLoad, onProgress, onError) {
this._load(url + '?' + Math.random(), onLoad, onProgress, onError);
};

//Instantiate a Collada loader
var loader = new THREE.ColladaLoader();

以下是事件顺序:

1)第一次单击以在新会话中显示 3D 框时,将显示更改

2)更改纹理并上传,然后单击以在同一会话中显示 3D 框,它显示了更改。以前,它直到页面刷新后才会显示。

3)更改纹理并上传,然后单击以在同一会话中显示 3D 框。RangeError:超出最大调用堆栈大小

要清楚, 我在 Modal 中打开它 。并且看到了 Modals 在搜索时产生这些错误的问题,除了我在路由级别只保留一个控制器调用。

更新 3:

我整理了以下 plunk:

http://plnkr.co/edit/DGZA9LUBZWsLWrmXaaKD

它使用我的 Dropbox 公共文件夹来获取图像。我遇到了同样的问题,如果我覆盖纹理文件,则当我选择框预览时,除非我刷新页面,否则它不会更新。我需要这个来在单击时刷新框纹理。一些关键说明:

1) 我在控制台记录了术语“已加载”,以表明即使在关闭模式后,它仍会继续渲染。我不确定是否需要连续渲染/动画,因为我只需要使用 OrbitControls 旋转框。请让我知道是否有更好的方法,或者这是否导致了缓存问题/我们需要销毁场景/重新创建它以便更新框

2)使用 Mr.Doob 推荐的代码,我可以更新服务器上的图像并让它重新正确渲染一次,然后才会出现以下错误:

Uncaught RangeError: Maximum call stack size exceeded

这是代码:

THREE.ImageLoader.prototype._load = THREE.ImageLoader.prototype.load;
            THREE.ImageLoader.prototype.load = function (url, onLoad, onProgress, onError) {
               this._load(url + '?' + Math.random(), onLoad, onProgress, onError);
           };

我保留了这段代码,因为它在崩溃之前至少实现了一次我想要的功能。

3)由于我们使用的是公共 Dropbox,我不确定那些帮助我的人是否能够用更改覆盖 Texture_0.png 文件(我在 Paint 中打开它并在其上涂抹了一些东西并重新上传到 Dropbox),但您可以将 URL 更改为您自己的 Dropbox 进行测试,因为您将能够上传并看到除非您重新加载 Web,否则该框纹理不会刷新页面(plunker)。

2个回答

如果您在 three.js 背后编辑图像文件且不更改文件名,并且想要刷新材质纹理,则可以使用如下模式:

mesh.material.map.dispose(); // unrelated, but important!
mesh.material.map = THREE.ImageUtils.loadTexture( "filename.jpg" + "?" + Math.random() );

如果没有动画循环,则需要强制重新渲染,如下所示:

mesh.material.map.dispose(); // unrelated, but important!
mesh.material.map = THREE.ImageUtils.loadTexture( "filename.jpg" + "?" + Math.random(), undefined, render );

three.js r.72

WestLangley
2015-09-24

@weslangley 的回答对于大多数 three.js 用户来说在技术上是正确的,因为他们使用材料。在我非常特殊的情况下,我有一个独特的 Collada 文件,我必须编辑 ImageLoader.js 文件才能使其从服务器动态返回具有相同文件名(但不同图像内容)的图像文件。 我不建议触碰源代码 ,但就我而言,我在 ImageLoader.js 中的加载函数下添加了以下行:

url = url + '?' + Math.random();

对我来说,这有效,但对于 99.9% 的其他 three.js 用户来说,这可能不适用。

Kode
2015-09-30