讀取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>