Three.js:如何将一个场景制作成2D快照并保存为JPG图片?

41

我有一个如下的three.js场景:

            var scene = new THREE.Scene();
            var camera = new THREE.PerspectiveCamera(75, window.innerWidth/window.innerHeight, 0.1, 1000);

            var renderer = new THREE.WebGLRenderer();
            renderer.setSize(window.innerWidth, window.innerHeight);
            document.body.appendChild(renderer.domElement);

            var geometry = new THREE.BoxGeometry(1,1,1);
            var material = new THREE.MeshBasicMaterial({color: 0x00ff00});
            var cube = new THREE.Mesh(geometry, material);
            scene.add(cube);

            camera.position.z = 5;

            var render = function () {
                requestAnimationFrame(render);

                cube.rotation.x += 0.1;
                cube.rotation.y += 0.1;

                renderer.render(scene, camera);
            };

            render();

是否可能从场景中制作2D快照或截图,并将其导出为JPG图像?


以下是有关此事的一些资源:http://stackoverflow.com/questions/16431318/webgl-single-frame-screenshot-of-webgl 和 https://dev59.com/t2Up5IYBdhLWcg3wEEI5 - gaitat
2
使用 preserveDrawingBuffer 标志初始化 WebGL 上下文,并使用 yourCanvas.toDataURL() - LJᛃ
@LJ_1102,你能否提供一个例子? - Michael
重复问题包括 https://dev59.com/GZPea4cB1Zd3GeqP_B9b,https://dev59.com/t2Up5IYBdhLWcg3wEEI5,https://dev59.com/44Tba4cB1Zd3GeqP5F-5。 - gman
1个回答

71

要将帧保存为jpg图像,您需要完成几个步骤。

首先,按照以下方式初始化WebGL上下文:

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

preserveDrawingBuffer标志将帮助您获取当前帧的base64编码。代码示例如下:

var strMime = "image/jpeg";
imgData = renderer.domElement.toDataURL(strMime);

其次,您可能希望使用.jpg扩展名保存文件, 但并非所有浏览器都允许您指定文件名。 我发现最好的解决方案在此SO线程中。

因此,我们的脚本将检查浏览器是否允许创建新的anchor元素,并设置其download属性进行点击(这将按指定的文件名保存文件),否则它将仅下载文件,但用户必须将其重命名为.jpg扩展名才能打开它。

Codepen链接

var camera, scene, renderer;
var mesh;
var strDownloadMime = "image/octet-stream";

init();
animate();

function init() {

    var saveLink = document.createElement('div');
    saveLink.style.position = 'absolute';
    saveLink.style.top = '10px';
    saveLink.style.width = '100%';
    saveLink.style.background = '#FFFFFF';
    saveLink.style.textAlign = 'center';
    saveLink.innerHTML =
        '<a href="#" id="saveLink">Save Frame</a>';
    document.body.appendChild(saveLink);
    document.getElementById("saveLink").addEventListener('click', saveAsImage);
    renderer = new THREE.WebGLRenderer({
        preserveDrawingBuffer: true
    });
    renderer.setSize(window.innerWidth, window.innerHeight);
    document.body.appendChild(renderer.domElement);

    camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 1, 1000);
    camera.position.z = 400;

    scene = new THREE.Scene();

    var geometry = new THREE.BoxGeometry(200, 200, 200);


    var material = new THREE.MeshBasicMaterial({
        color: 0x00ff00
    });

    mesh = new THREE.Mesh(geometry, material);
    scene.add(mesh);

    window.addEventListener('resize', onWindowResize, false);
}

function onWindowResize() {
    camera.aspect = window.innerWidth / window.innerHeight;
    camera.updateProjectionMatrix();

    renderer.setSize(window.innerWidth, window.innerHeight);
}

function animate() {
    requestAnimationFrame(animate);

    mesh.rotation.x += 0.005;
    mesh.rotation.y += 0.01;

    renderer.render(scene, camera);
}

function saveAsImage() {
    var imgData, imgNode;

    try {
        var strMime = "image/jpeg";
        var strDownloadMime = "image/octet-stream";

        imgData = renderer.domElement.toDataURL(strMime);

        saveFile(imgData.replace(strMime, strDownloadMime), "test.jpg");

    } catch (e) {
        console.log(e);
        return;
    }

}

var saveFile = function (strData, filename) {
    var link = document.createElement('a');
    if (typeof link.download === 'string') {
        document.body.appendChild(link); //Firefox requires the link to be in the body
        link.download = filename;
        link.href = strData;
        link.click();
        document.body.removeChild(link); //remove the link when done
    } else {
        location.replace(uri);
    }
}
html, body {
    padding:0px;
    margin:0px;
}
canvas {
    width: 100%;
    height: 100%
}
<script src="http://cdnjs.cloudflare.com/ajax/libs/three.js/r69/three.min.js"></script>
<script src="http://threejs.org/examples/js/libs/stats.min.js"></script>

2022年12月编辑

正如Mohammad Tbeishat在评论中指出的那样,现在有一个更预格式化的API可用:canvas.toBlob,您可以参考以下链接:

https://r105.threejsfundamentals.org/threejs/lessons/threejs-tips.html


1
@confile: 如果浏览器支持,则文件将以给定的文件名保存,例如 example.jpg,但如果浏览器不支持,则文件将以一些随机自动生成的文件名保存,例如 ac2wz43,没有 .jpg 扩展名,因此用户必须手动将其重命名为带有 .jpg 扩展名的名称才能打开它。您可以在此处检查 download 属性的兼容性 http://caniuse.com/#feat=download - Shiva
@confile:我不确定如何在客户端完全优化图像,但你可以搜索其他可能能够做到这一点的JavaScript库。 - Shiva
2
@Shiva - 你的回答很棒,非常感谢!但愿我能给予+5而不是+1! - Matteo
2
您不需要设置 preserveDrawingBuffer: true。您只需要在同一事件中渲染和捕获即可。您可以通过将标志添加到渲染循环或在调用 toDataURL 之前调用 renderer.render 来实现。 - gman
1
根据文档:“旧的canvas.toDataURL和新的更好的canvas.toBlob” https://r105.threejsfundamentals.org/threejs/lessons/threejs-tips.html - Mohammad Tbeishat
显示剩余5条评论

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