【问题标题】:Three.js canvas.toDataURL sometimes blankThree.js canvas.toDataURL 有时为空
【发布时间】:2014-12-13 10:35:04
【问题描述】:

我正在尝试使用 html2canvas.js 渲染一个 THREE.js 场景 + 一些覆盖的 HTML 元素。它大部分时间都有效,但并非一直有效。

在失败的情况下,HTML 元素被渲染(背景、覆盖等),但没有其他内容。 THREE.js 场景表现得好像完全是空的,尽管它里面显然有数据。我可以说它对于较大的模型通常会失败,但只是在渲染的早期。它确实最终适用于所有情况,但较大的模型大约需要 30 秒。就好像我必须给缓冲区一些时间来稳定。

html2canvas 像您期望的那样处理 THREE.js 画布 - 它只是使用 drawImage 将 THREE.js 画布绘制到最终由库返回的新画布上。

否则,我会尽量确保没有其他东西在画布上,就像在这个小提琴中一样:http://jsfiddle.net/TheJim01/k6dto5sk/63/(下面的 js 代码)

如您所见,我做了很多尝试来阻止渲染循环,并在我想捕捉场景时再执行一次渲染。但即使是所有这些预防措施似乎也无济于事。

有没有更好的方法从 THREE.js 画布中获取图像?我可以手动完成该部分,然后将 THREE.js 画布换成一个足够长的图像,以便 html2canvas 完成它的工作,然后将 THREE.js 画布换回。我不想那样做,所以我不会弄乱 DOM,如果用户制作大量快照(图像资源,无处不在的图像资源......)。

无论如何,这是代码。欢迎任何想法或建议。谢谢!

var hostDiv, scene, renderer, camera, root, controls, light, shape, theta, aniLoopId, animating;
function snap() {
    animating = false;
    cancelAnimationFrame(aniLoopId);
    renderer.render(scene, camera);

    // html2canvas version:
    /*
    var element = document.getElementById('scenePlusOverlays');
    // the input buttons represent my overlays
    html2canvas( element, function(canvas) {
        // I'd convert the returned canvas to a PNG
        animating = true;
        animate();
    });
    */

    // This is basically what html2canvas does with the THREE.js canvas.
    var c = document.getElementById('rendererCanvas');
    var toC = document.createElement('canvas');
    toC.width = c.width;
    toC.height = c.height;
    var toCtx = toC.getContext('2d');
    toCtx.drawImage(c, 0, 0);
    console.log(toC.toDataURL('image/png'));
    animating = true;
    animate();
}
function addGeometry() {
    //var geo = new THREE.BoxGeometry(1, 1, 1);
    var geo = new THREE.SphereGeometry(5, 32, 32);
    var beo = new THREE.BufferGeometry().fromGeometry(geo);
    geo.dispose();
    geo = null;
    var mat = new THREE.MeshPhongMaterial({color:'red'});
    var msh;

    var count = 10;
    count /= 2;
    var i = 20;
    var topLayer = new THREE.Object3D();
    var zLayer, xLayer, yLayer;
    for(var Z = -count; Z < count; Z++){
        zLayer = new THREE.Object3D();
        for(var X = -count; X < count; X++){
            xLayer = new THREE.Object3D();
            for(var Y = -count; Y < count; Y++){
                yLayer = new THREE.Object3D();
                msh = new THREE.Mesh(beo, mat);
                yLayer.add(msh);
                msh.position.set((X*i)+(i/2), (Y*i)+(i/2), (Z*i)+(i/2));
                xLayer.add(yLayer);
            }
            zLayer.add(xLayer);
        }
        topLayer.add(zLayer);
    }
    scene.add(topLayer);
}
var WIDTH = '500';//window.innerWidth,
    HEIGHT = '500';//window.innerHeight,
    FOV = 35,
    NEAR = 0.1,
    FAR = 10000;

function init() {
    hostDiv = document.getElementById('hostDiv');
    document.body.insertBefore(hostDiv, document.body.firstElementChild);

    renderer = new THREE.WebGLRenderer({ antialias: true, preserverDrawingBuffer: true });
    renderer.setSize(WIDTH, HEIGHT);
    renderer.domElement.setAttribute('id', 'rendererCanvas');
    hostDiv.appendChild(renderer.domElement);

    camera = new THREE.PerspectiveCamera(FOV, WIDTH / HEIGHT, NEAR, FAR);
    camera.position.z = 500;

    controls = new THREE.TrackballControls(camera, renderer.domElement);

    light = new THREE.PointLight(0xffffff, 1, Infinity);
    light.position.copy(camera.position);

    scene = new THREE.Scene();
    scene.add(camera);
    scene.add(light);

    animating = true;
    animate();
}

function animate() {
    if(animating){
        light.position.copy(camera.position);
        aniLoopId = requestAnimationFrame(animate);    
    }
    renderer.render(scene, camera);
    controls.update();
}

编辑: 以所描述的方式调用 readPixels 或 toDataURL 是可能的——我曾考虑过一种类似的方法来获取缓冲区,但由于所需的异步代码量而推迟了。像这样的:

var global_callback = null;
function snapshot_method() {
   global_callback = function(returned_image) {
      // do something with the image
   }
}
// ...
function render() {
   renderer.render();
   if(global_callback !== null) {
      global_callback(renderer.domElement.toDataURL());
      global_callback = null;
   }
} 

【问题讨论】:

  • 我认为你有一个错字...preserverDrawingBuffer 应该是preserveDrawingBuffer。这能解决问题吗?
  • 你说得对。我的模板代码中肯定已经有很多年了……我进行了一些快速的压力测试,似乎是拼写错误导致了问题。我很惊讶它甚至在某些时候有效!如果您将评论添加为答案,我会接受。

标签: canvas three.js html5-canvas html2canvas


【解决方案1】:

(为了未来的谷歌人的利益):你需要在实例化WebGLRenderer时将preserveDrawingBuffer设置为true

renderer = new THREE.WebGLRenderer({ antialias: true, preserveDrawingBuffer: true });

(原始代码有这个,但有一个错字)。这是必需的,因为默认情况下,WebGL 不要求浏览器在每个绘图帧之后保存深度/颜色缓冲区,因此如果要使用 readPixelstoDataURL,则需要明确告诉实现保存绘图使用该标志的缓冲区。

但是,这样做会降低性能; the spec provides some guidance if this is a problem for you:

虽然有时需要保留绘图缓冲区,但它可以 在某些平台上导致显着的性能损失。每当 可能此标志应保持为假并使用其他技术。 同步绘图缓冲区访问等技术(例如,调用 readPixels 或 toDataURL 在呈现给 绘图缓冲区)可用于获取绘图缓冲区的内容。 如果作者需要通过一系列渲染到同一个绘图缓冲区 调用时,可以使用 Framebuffer 对象。

我不确定这些建议中的任何一个在当前版本的 three.js (r68) 中是否可行,但另一个替代解决方案(在画布的副本上调用 readPixelstoDataURL)在其他几个问题(Why does my canvas go blank after converting to image?How do you save an image from a Three.js canvas?)。

【讨论】:

猜你喜欢
  • 2018-08-21
  • 2014-03-27
  • 2015-02-23
  • 2012-09-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-06-20
  • 2011-02-24
相关资源
最近更新 更多