Three.js canvas.toDataURL有时为空白 [英] Three.js canvas.toDataURL sometimes blank

查看:226
本文介绍了Three.js canvas.toDataURL有时为空白的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试使用html2canvas.js渲染THREE.js场景+一些重叠的HTML元素。它在大多数情况下都有效,但并非始终如此。

I'm trying to render a THREE.js scene + some overlaid HTML elements using html2canvas.js. It works most times, but not all the time.

在失败的情况下,呈现HTML元素(背景,覆盖等) 。) 但没有别的。即使THREE.js场景中显然有数据,它的行为也好像完全是空的。我可以说,通常对于较大的模型,它会失败,但是只能在渲染的早期进行。在所有情况下,它最终都可以工作,但是较大的模型大约需要30秒。好像我必须给缓冲区一些时间才能稳定。

In the failure cases, the HTML elements are rendered (the background, overlays, etc.) but nothing else. The THREE.js scene acts as if it's completely empty, even though it visibly has data in it. I can say that it usually fails for larger models, but only early into the rendering. It does eventually work in all cases, but the larger models take about 30 seconds. It's as if I have to give the buffer some time to stabilize.

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

html2canvas handles the THREE.js canvas as you would expect--it simply uses drawImage to draw the THREE.js canvas onto the new canvas that is eventually returned by the library.

否则,我尝试确保像下面这样的小提琴,没​​有其他东西忙于画布了: http://jsfiddle.net/TheJim01/k6dto5sk / 63 / (下面的js代码)

Otherwise, I try to ensure that nothing else is busy with the canvas, like in this fiddle: http://jsfiddle.net/TheJim01/k6dto5sk/63/ (js code below)

正如您所看到的,我正在做很多工作来尝试阻止渲染循环,并仅执行当我想捕获场景时,再渲染一个。但是,即使所有这些预防措施似乎也无济于事。

As you can see, I'm doing quite a bit to try to block the render loop, and perform just one more render when I want to capture the scene. But even all of these precautions don't seem to help.

是否有更好的方法从THREE.js画布中获取图像?我可能可以手动完成该部分,然后将THREE.js画布换成获取的图像,时间足够长,以便html2canvas可以完成其工作,然后再将THREE.js画布换回。我宁愿不要那样做,所以如果用户创建大量快照(图像资源,无处不在的图像资源...),我就不会迷惑DOM。

Is there a better way to fetch the image from the THREE.js canvas? I can potentially do that part manually, then swap out the THREE.js canvas for a fetched image just long enough for html2canvas to do its thing, then swap the THREE.js canvas back in. I'd prefer not to do it that way, so I don't muddy up the DOM, should a user make lots of snapshots (image resources, image resources everywhere...).

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

Anyway, here's the code. Any ideas or suggestions are welcome. Thanks!

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是可能的-我曾考虑过类似的方法来获取缓冲区,但由于所需的异步代码数量而推迟。像这样:

Calling readPixels or toDataURL in the manner described is possible--I had considered a similar method to grab the buffer, but was put off by the amount of asynchronous code required. Something like this:

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


推荐答案

(为了将来的Google员工的利益):实例化时,需要将 preserveDrawingBuffer 设置为 true WebGLRenderer

(For the benefit of future Googlers): You need to set preserveDrawingBuffer to true when you instantiate WebGLRenderer:

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

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

(original code had this, but there was a typo). This is needed because by default, WebGL doesn't require browsers to save the depth/color buffers after each drawing frame, so if you want to use readPixels or toDataURL, you need to explicitly tell the implementation to save the drawing buffer using that flag.

但是,这样做会降低性能这个; 如果您遇到问题,该规范会提供一些指导

However, there's a performance penalty to doing this; the spec provides some guidance if this is a problem for you:


尽管有时希望保留绘图缓冲区,但在某些平台上,
可能会导致严重的性能损失。只要
可能,此标志应保持为false并使用其他技术。
可以使用诸如同步绘图缓冲区访问之类的技术(例如,在渲染到
绘图缓冲区的同一函数中调用
readPixels或toDataURL)来获取绘图缓冲区的内容。
如果作者需要通过一系列
的调用渲染到相同的图形缓冲区,则可以使用Framebuffer对象。

While it is sometimes desirable to preserve the drawing buffer, it can cause significant performance loss on some platforms. Whenever possible this flag should remain false and other techniques used. Techniques like synchronous drawing buffer access (e.g., calling readPixels or toDataURL in the same function that renders to the drawing buffer) can be used to get the contents of the drawing buffer. If the author needs to render to the same drawing buffer over a series of calls, a Framebuffer Object can be used.

我不确定在当前版本的three.js(r68)中这些建议是否可行,但是另一个解决方案(调用 readPixels 或在其他几个问题上讨论了 toDataURL 在画布副本上)(为什么我的画布在转换为图像后会变成空白?如何从Three.js画布中保存图像?)。

I'm not sure either of those suggestions are feasible in the current version of three.js (r68), but another alternative solution (to call readPixels or toDataURL on a copy of the canvas) is discussed on several other questions (Why does my canvas go blank after converting to image? , How do you save an image from a Three.js canvas?).

这篇关于Three.js canvas.toDataURL有时为空白的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆