webgl & Three.js离屏渲染实践

6,185 阅读3分钟

1、离屏渲染的概念:

使用过three.js的朋友都非常熟悉WebGLRenderer,WebGLRender负责将你绚丽多彩的三维实体渲染到场景中。今天介绍的一种新的渲染形态->离屏渲染WebGLRenderTarget, 官方解释是一块存储渲染数据的缓冲区,一般常用于在渲染到屏幕之前做_后处理_。

2、离屏渲染在本文的应用:

在本文中,我们将应用离屏渲染技术,将webgl实时渲染的场景,输出为一张静态图片,并可以下载到本地。项目源码地址: github.com/liyang00770…

3、核心代码解析:

a、首先在initThree里面搭建一个基本场景,加载F16战斗机的模型和天空盒子,使用透视相机并绑定轨道控制器,同时在相机上加两束光,让F16战斗机产生高光反射效果(如果这一段完全不知道什么意思,可以停止阅读了,推荐去看下其他基础的文章。。。)。这时拖拽屏幕,可以全视角观察模型。如下方代码所示,主requestAnimationFrame中,renderer实时渲染模型到屏幕上

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

b、主角登场:离屏渲染!我们目前的诉求是,能够拿到webgl场景中的渲染数据,并且输出为一张png图片,达到类似于截屏的效果;但是因为我们是直接拿的渲染数据,所以不会存在网页截屏那种会丢失清晰度的问题,百分百复刻渲染场景!废话不多说,直接上代码。

1、创建一个WebGLRenderTarget,离屏渲染的像素数据,将会存在这片缓冲区域

let rt = new THREE.WebGLRenderTarget(el.clientWidth * 4, el.clientHeight * 4, {  encoding: THREE.sRGBEncoding})

2、在渲染之前,做一次抗锯齿处理。

const renderPass = new RenderPass( scene, camera );
const pixelRatio = renderer.getPixelRatio();
const pass = new SMAAPass( el.clientWidth * pixelRatio, el.clientHeight * pixelRatio );
fxaaPass.material.uniforms[ 'resolution' ].value.x = 1 / ( el.offsetWidth * pixelRatio );
fxaaPass.material.uniforms[ 'resolution' ].value.y = 1 / ( el.offsetHeight * pixelRatio );
composer1 = new EffectComposer( renderer, rt );composer1.addPass( renderPass );composer1.addPass( pass );
composer1.renderToScreen = falsecomposer1.render()

3、动态创建一个canvas画布,将WebGLRenderTarget中缓存的数据,渲染到画布上,随后就可以使用canvas的toDataURL()输出base64格式的图片数据,或者使用canvas.toBlob(),结合file-saver库,将图片下载到本地

const canvas2d = document.createElement('canvas')
document.body.appendChild(canvas2d);
const width = el.clientWidth * 4      
const height = el.clientHeight * 4     
const preview = canvas2d;    
preview.width = width    
preview.height = height   
const ctx = preview.getContext('2d');   
const buffer = new Uint8Array(width * height * 4);   
const clamped = new Uint8ClampedArray(buffer.buffer);     
const reversed = []    
composer1.renderer.readRenderTargetPixels(rt, 0, 0, width, height, buffer); // 读取像素到 buffer      
const imageData = new ImageData(clamped, width, height); // 创建可供 canvas 使用的图像数据类型      
ctx.putImageData(imageData, 0, 0);      // 图片输出前上下镜像,three的坑      
for (let i = height - 1; i >= 0; i--) {        
    reversed.push(...clamped.slice(width * 4 * i, width * 4 * (i + 1)))
}      
const imageDataFliped = new ImageData(new Uint8ClampedArray(reversed), width, height)      
ctx.putImageData(imageDataFliped, 0, 0);      
this.base64 = preview.toDataURL();      
if (type === 'download') {        
    canvas2d.toBlob((blob) => {          
        FileSaver.saveAs(blob, 'pretty image.png')        
    })      
}      
document.body.removeChild(canvas2d);


最终效果就是封面的图片。

4、结语:

本文介绍了一种利用webgl离屏渲染机制,实现webgl截屏输出图片数据并保存图片的效果。在一些DOM和webgl互动的场景中,可以用到。

5、广告:

字节跳动VR团队,正在火热招聘中,目前业务覆盖VR看房,装修等场景。

业务场景覆盖VR拍摄,房屋建模,跨端VR展示,全流程覆盖。

正在快速商业化中。欢迎各路大神,和有志于从事VR&webgl工作的同学,加入我们团队。

个人邮箱:liyang.peace@bytedance.com,微信liyang920308