云图三维 | Three.js 截屏等知识点

1,418 阅读4分钟

云图三维 连接你·创造的世界 致力于打造国内第一家集查看、建模、装配和渲染于一体的“云端CAD”协作设计平台。

应读者的要求,希望我们成立一个专业的、面向成渝地区的前端开发人员的webgl、Threejs行业QQ交流群,便于大家讨论问题。群里有研究webgl、Threejs大佬哦,欢迎大家加入!——点击链接加入群聊【three.js/webgl重庆联盟群】:jq.qq.com/?_wv=1027&k…

截屏

在浏览器中,有两个有效的功能可以截取屏幕。旧的 canvas.toDataURL 和新的的 canvas.toBlob

很容易想到的一种截取屏幕截图方式

   <canvas id="c"></canvas>
   <button id="screenshot" type="button">Save...</button>
   const elem = document.querySelector('#screenshot');
   elem.addEventListener('click', () => {
       canvas.toBlob((blob) => {
           saveBlob(blob, `screencapture-${canvas.width}x${canvas.height}.png`);
       });
   });
    
   const saveBlob = (function() {
       const a = document.createElement('a');
       document.body.appendChild(a);
       a.style.display = 'none';
       return function saveData(blob, fileName) {
           const url = window.URL.createObjectURL(blob);
           a.href = url;
           a.download = fileName;
           a.click();
       };
   }());

实际测试下来是一张全黑的截图

image.png

问题在于,出于性能和兼容性原因,默认情况下,浏览器会在您绘制完 WebGL 画布的绘图缓冲区后清除它。

解决方案是在捕获之前调用渲染代码。

在代码中需要调整一些东西。先分离出渲染代码。

   const state = {
   time: 0,
   };
    
   function render(time) {
   time *= 0.001;
   function render() {
       if (resizeRendererToDisplaySize(renderer)) {
           const canvas = renderer.domElement;
           camera.aspect = canvas.clientWidth / canvas.clientHeight;
           camera.updateProjectionMatrix();
       }

       cubes.forEach((cube, ndx) => {
           const speed = 1 + ndx * .1;
           const rot = time * speed;
           const rot = state.time * speed;
           cube.rotation.x = rot;
           cube.rotation.y = rot;
       });

       renderer.render(scene, camera);

       requestAnimationFrame(render);
   }
    
   function animate(time) {
       state.time = time * 0.001;

       render();

       requestAnimationFrame(animate);
   }
   requestAnimationFrame(animate);

现在render只关心实际渲染,我们可以在捕获画布之前调用它。

   const elem = document.querySelector('#screenshot');
   elem.addEventListener('click', () => {
       render();
       canvas.toBlob((blob) => {
           saveBlob(blob, `screencapture-${canvas.width}x${canvas.height}.png`);
       });
   });

效果就正常了

详情案例请参阅threejsfundamentals.org/threejs/thr…

防止画布被清除

假设您想让用户使用动画对象进行绘画。你需要通过preserveDrawingBuffer: true创建时WebGLRenderer。这可以防止浏览器清除画布,除此之外three.js 不应该清除画布。

   const canvas = document.querySelector('#c');
   const renderer = new THREE.WebGLRenderer({canvas});
   const renderer = new THREE.WebGLRenderer({
       canvas,
       preserveDrawingBuffer: true,
       alpha: true,
   });
   renderer.autoClearColor = false;

具体事例可参考:threejsfundamentals.org/threejs/thr…

如果认真地制作绘图程序,这将不是一个解决方案,因为浏览器仍会在我们更改分辨率时清除画布。我们正在根据其显示尺寸更改分辨率。当窗口改变大小时,它的显示大小也会改变。这包括当用户下载文件时,即使在另一个选项卡中,浏览器会添加一个状态栏。它还包括用户转动手机和浏览器从纵向切换到横向的时间。

使画布透明

默认情况下,Three.js 使画布不透明。如果您希望画布在alpha:true创建时透明WebGLRenderer

   const canvas = document.querySelector('#c');
   const renderer = new THREE.WebGLRenderer({canvas});
   const renderer = new THREE.WebGLRenderer({
       canvas,
       alpha: true,
   });

可以设置premultipliedAlpha,预乘 alpha

   const canvas = document.querySelector('#c');
   const renderer = new THREE.WebGLRenderer({
       canvas,
       alpha: true,
       premultipliedAlpha: false,
   });

Three.js 默认为画布 using premultipliedAlpha: true但默认为材质输出premultipliedAlpha: false

如果您想更好地了解何时以及何时不使用预乘 alpha,

用透明画布设置一个简单的例子。

   function makeInstance(geometry, color, x) {
   const material = new THREE.MeshPhongMaterial({color});
   const material = new THREE.MeshPhongMaterial({
       color,
       opacity: 0.5,
   });
    
   ...

添加一些 HTML 内容

   <body>
       <canvas id="c"></canvas>
       <div id="content">
           <div>
               <h1>Cubes-R-Us!</h1>
               <p>We make the best cubes!</p>
           </div>
       </div>
   </body>

以及一些 CSS 将画布放在前面

   body {
       margin: 0;
   }
   #C {
       width: 100%;
       height: 100%;
       display: block;
       position: fixed;
       left: 0;
       top: 0;
       z-index: 2;
       pointer-events: none;
   }
   #内容 {
       font-size: 7vw;
       font-family: sans-serif;
       text-align: center;
       width: 100%;
       height: 100%;
       display: flex;
       justify-content: center;
       align-items: center;
   }

效果图如下

image.png

使您的背景成为three.js 动画

一个常见的问题是如何让一个three.js动画作为网页的背景。

有2个明显的方法。

  • 将画布 CSSposition设置fixed为 in
   #C {
       position: fixed;
       left: 0;
       top: 0;
       ...
   }

基本上可以在前面的示例中看到这个确切的解决方案。只需设置z-index为 -1,立方体就会出现在文本后面。

这个解决方案的一个小缺点是你的 JavaScript 必须与页面集成,如果你有一个复杂的页面,那么你需要确保你的 Three.js 可视化中的 JavaScript 没有与在页面中做其他事情的 JavaScript 冲突。

  • 使用 iframe

例如,在您的网页中插入一个 iframe

   <iframe id="background" src="threejs-responsive.html">
   <div>
       Your content goes here.
   </div>

然后设置 iframe 的样式以填充窗口并位于背景中,这与我们上面用于画布的代码基本相同,但我们还需要设置border为 ,none因为 iframe 默认具有边框。

   #背景 {
   position: fixed;
       width: 100%;
       height: 100%;
       left: 0;
       top: 0;
       z-index: -1;
       border: none;
       pointer-events: none;
   }

效果图如下

image.png

翻译来源:threejsfundamentals.org/threejs/les…

写在最后

本文介绍了Three.js的截屏相关的内容,截屏在日常工作中是非常常见的需求,希望对你有帮助。

本文发布自 云图三维大前端团队,文章未经授权禁止任何形式的转载。