读取canvas 的像素,应该是一件很简单且基础的事。就像在canvas 2d 里,使用其上下文对象的getImageData() 方法就能够轻松搞定。css
获取webgl 类型的canvas 像素也不难,只是我百度了一下居然没找到很详细的那种文章,我只找到了一个webgl 原生的,three.js 的居然没有看见。html
我基于原生webgl 的实现原理,找到了three.js 里的相关方法,而后又找到了其官方提供的案例,在里不断压缩代码,将相应功能提取了出来。web
下面我们就详细说一下实现方法,先说three.js 的。canvas
先搭个架子webgl
<!DOCTYPE html> <html lang="en"> <head> <title>开课吧</title> <meta charset="utf-8"> <style> #canvas2d{background: #88ee88} #canvasGl{background: #93abff} </style> </head> <body> <canvas id="canvas2d" width="100" height="100"></canvas> <canvas id="canvasGl" width="100" height="100"></canvas> <script type="module"> </script> </body> </html>
两个可爱的canvas 已经有了,第一个是用于2d 的canvas,第二个是用于webgl 的canvasui
接下来我要在webgl 类型的canvas 里画一个盒子,第一个二维的canvas 先无论它。
先在js 里搭建three.js 环境spa
<script type="module"> import * as THREE from '../build/three.module.js'; //webgl 类型的canvas 元素 let canvasGl=document.getElementById('canvasGl'); //二维的canvas 元素,及其上下文对象 let canvas2d=document.getElementById('canvas2d'); let ctx=canvas2d.getContext('2d'); //canvas 的尺寸,之后会经常使用到 let {width,height}=canvasGl; //渲染器 let renderer = new THREE.WebGLRenderer({canvas:canvasGl}); //渲染场景 let scene = new THREE.Scene(); //场景的背景色 scene.background=new THREE.Color(1,0.5,0); //透视相机 let camera = new THREE.PerspectiveCamera( 45, 1, 1, 10000 ); camera.position.z = 2; </script>
效果以下:
我使用scene.background 为场景添加了一个橘黄色的背景,这个背景和css 里的背景可不是一回事哦。3d
接着上面的js 代码,接续码code
//创建盒子对象 let mat = new THREE.MeshBasicMaterial({color:new THREE.Color(255,255,0)}); let geo = new THREE.BoxBufferGeometry(1,1,1); let mesh=new THREE.Mesh( geo , mat ); scene.add( mesh ); //渲染场景 renderer.render( scene, camera );
右侧的canvas 里已经有了一个黄色的盒子htm
接下来,重头戏开始,我要获取右侧webgl 类型的canvas 里的像素。由于这个canvas 的上下文对象是webgl 类型,因此我得按webgl 的规则走。
canvas 2d 的上下文对象是CanvasRenderingContext2D, webgl 类型的canvas 的上下文对象是WebGLRenderingContext,他们虽然都是用canvas.getContext() 方法造出来的,但它们彻底不像一对兄弟。
先说一下实现原理:首先我要有一个渲染目标,你们能够将其当成一个容器。接下来我要跟渲染器说,你在渲染完场景后,要将数据放到这个容器里。在场景渲染完成后,咱们就能够在这个容器里读取数据了。
具体的实现步骤是这样的:
在renderer.render( scene, camera ); 的先后加点料
//创建渲染目标对象 let renderTarget=new THREE.WebGLRenderTarget( width, height ); //让渲染器在渲染完场景后,将数据放到渲染目标里 renderer.setRenderTarget( renderTarget ); //渲染场景 renderer.render( scene, camera ); //创建像素集合,用于装像素 let pixels = new Uint8Array( width*height*4); //从渲染目标里读取像素数据,而后将其装到事先创建好的像素集合里 renderer.readRenderTargetPixels( renderTarget , 0, 0, width, height, pixels ); console.log(pixels );
我console.log(pixels ); 输出的像素集合是这样的:
至少没有报错,吾心甚慰。只是看了一眼canvas 效果,心跳就漏了一拍。
右侧的canvas 居然黑屏啦 /(ㄒoㄒ)/~~
莫慌莫慌,咱先把这个像素集合显示出来看看是个神马东东。接着上文,继续码字:
我要基于像素集合创建ImageData 对象,而后将其显示到左边的canvas2d 里。(canvas2d:个人剑已经饥渴难耐!)
//基于像素集合和尺寸创建ImageData 对象 let imageData=new ImageData(new Uint8ClampedArray(pixels),width,height); //将图像数据显示到二维canvas 中 ctx.putImageData(imageData,0,0);
见证真相:
canvas2d 里的图不就是咱们刚才用webgl 画的图吗?(canvasGl:这是风水轮流转的节奏吗?)
其实,如今咱们已经实现目的了。获取webgl 类型的canvas 像素,就是上面的步骤。
但是我本着苟利国家生死以的目的,仍是要把canvasGl 黑屏的缘由说一下。
咱们刚才一番操做,实际上并无把渲染器渲染后的数据放到canvas 画布上,而只是把数据放进了渲染目标里,而后就撒手无论了。
因此咱们接下来须要把场景渲染到canvas 画布上。其实现原理是:将渲染器的渲染目标清除,而后再次渲染场景。
接着上文,继续码字:
//将渲染器的渲染目标清除 renderer.setRenderTarget(null); //渲染场景 renderer.render( scene, camera );
效果以下:
花开并蒂,邀君共赏!
总体代码以下:
<!DOCTYPE html> <html lang="en"> <head> <title>开课吧</title> <meta charset="utf-8"> <style> #canvas2d{background: #88ee88} #canvasGl{background: #93abff} </style> </head> <body> <canvas id="canvas2d" width="100" height="100"></canvas> <canvas id="canvasGl" width="100" height="100"></canvas> <script type="module"> import * as THREE from '../build/three.module.js'; //webgl 类型的canvas 元素 let canvasGl=document.getElementById('canvasGl'); //二维的canvas 元素,及其上下文对象 let canvas2d=document.getElementById('canvas2d'); let ctx=canvas2d.getContext('2d'); //canvas 的尺寸,之后会经常使用到 let {width,height}=canvasGl; //渲染器 let renderer = new THREE.WebGLRenderer({canvas:canvasGl}); //渲染场景 let scene = new THREE.Scene(); //场景的背景色 scene.background=new THREE.Color(1,0.5,0); //透视相机 let camera = new THREE.PerspectiveCamera( 45, 1, 1, 10000 ); camera.position.z = 2; //创建盒子对象 let mat = new THREE.MeshBasicMaterial({color:new THREE.Color(255,255,0)}); let geo = new THREE.BoxBufferGeometry(1,1,1); let mesh=new THREE.Mesh( geo , mat ); scene.add( mesh ); //创建渲染目标对象 let renderTarget=new THREE.WebGLRenderTarget( width, height ); //让渲染器在渲染完场景后,要将数据放到渲染目标里 renderer.setRenderTarget( renderTarget ); //渲染场景 renderer.render( scene, camera ); //创建像素集合,用于装像素 let pixels = new Uint8Array( width*height*4); //从渲染目标里读取像素数据,而后将其装到事先创建好的像素集合里 renderer.readRenderTargetPixels( renderTarget , 0, 0, width, height, pixels ); console.log(pixels ); //基于像素集合和尺寸创建ImageData 对象 let imageData=new ImageData(new Uint8ClampedArray(pixels),width,height); //将图像数据显示到二维canvas 中 ctx.putImageData(imageData,0,0); //将渲染器的渲染目标清除 renderer.setRenderTarget(null); //渲染场景 renderer.render( scene, camera ); </script> </body> </html>
首先咱们将代码会到webgl 渲染出盒子的那一步:
在这里我偷一个懒,渲图仍是使用three.js,但获取像素我会使用原生webgl
渲染器渲染完场景后,咱们只须要如此操做便可
//获取webgl 上下文对象,绘图缓冲区会被保留 let gl=canvasGl.getContext('webgl',{preserveDrawingBuffer:true}); //创建像素集合 let pixels = new Uint8Array( width*height*4); //从缓冲区读取像素数据,而后将其装到事先创建好的像素集合里 gl.readPixels(0, 0, width, height, gl.RGBA, gl.UNSIGNED_BYTE, pixels); //基于像素集合和尺寸创建ImageData 对象 let imageData=new ImageData(new Uint8ClampedArray(pixels),width,height); //将图像数据显示到二维canvas 中 ctx.putImageData(imageData,0,0);
效果以下:
这里的关键点是在于绘图缓冲区,渲染器渲染后的图像数据会被放到这个缓冲区里,咱们从中读取便可。
完整代码:
<!DOCTYPE html> <html lang="en"> <head> <title>webgl获取像素-原生</title> <meta charset="utf-8"> <style> #canvas2d{background: #88ee88} #canvasGl{background: #93abff} </style> </head> <body> <canvas id="canvas2d" width="100" height="100"></canvas> <canvas id="canvasGl" width="100" height="100"></canvas> <script type="module"> import * as THREE from './build/three.module.js'; //webgl 类型的canvas 元素 let canvasGl=document.getElementById('canvasGl'); //二维的canvas 元素,及其上下文对象 let canvas2d=document.getElementById('canvas2d'); let ctx=canvas2d.getContext('2d'); //canvas 的尺寸,之后会经常使用到 let {width,height}=canvasGl; //渲染器 let renderer = new THREE.WebGLRenderer({canvas:canvasGl}); //渲染场景 let scene = new THREE.Scene(); //场景的背景色 scene.background=new THREE.Color(1,0.5,0); //透视相机 let camera = new THREE.PerspectiveCamera( 45, 1, 1, 10000 ); camera.position.z = 2; //创建盒子对象 let mat = new THREE.MeshBasicMaterial({color:new THREE.Color(255,255,0)}); let geo = new THREE.BoxBufferGeometry(1,1,1); let mesh=new THREE.Mesh( geo , mat ); scene.add( mesh ); //渲染场景 renderer.render( scene, camera ); //获取webgl 上下文对象,绘图缓冲区会被保留 let gl=canvasGl.getContext('webgl',{preserveDrawingBuffer:false}); //创建像素集合 let pixels = new Uint8Array( width*height*4); //从缓冲区读取像素数据,而后将其装到事先创建好的像素集合里 gl.readPixels(0, 0, width, height, gl.RGBA, gl.UNSIGNED_BYTE, pixels); //基于像素集合和尺寸创建ImageData 对象 let imageData=new ImageData(new Uint8ClampedArray(pixels),width,height); //将图像数据显示到二维canvas 中 ctx.putImageData(imageData,0,0); </script> </body> </html>