webgl 讀取canvas 像素

讀取canvas 的像素,應該是一件很簡單且基礎的事。就像在canvas 2d 裏,使用其上下文對象的getImageData() 方法就能夠輕鬆搞定。css

獲取webgl 類型的canvas 像素也不難,只是我百度了一下居然沒找到很詳細的那種文章,我只找到了一個webgl 原生的,three.js 的居然沒有看見。html

我基於原生webgl 的實現原理,找到了three.js 裏的相關方法,而後又找到了其官方提供的案例,在裏不斷壓縮代碼,將相應功能提取了出來。web

下面我們就詳細說一下實現方法,先說three.js 的。canvas

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

Image.png

接下來我要在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

Image.png

接着上面的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

Image.png

接下來,重頭戲開始,我要獲取右側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 ); 輸出的像素集合是這樣的:

Image.png

至少沒有報錯,吾心甚慰。只是看了一眼canvas 效果,心跳就漏了一拍。

image.png

右側的canvas 居然黑屏啦 /(ㄒoㄒ)/~~ 

莫慌莫慌,咱先把這個像素集合顯示出來看看是個神馬東東。接着上文,繼續碼字:

我要基於像素集合創建ImageData 對象,而後將其顯示到左邊的canvas2d 裏。(canvas2d:個人劍已經飢渴難耐!)

//基於像素集合和尺寸創建ImageData 對象
let imageData=new ImageData(new Uint8ClampedArray(pixels),width,height);
//將圖像數據顯示到二維canvas 中
ctx.putImageData(imageData,0,0);

見證真相:

image.png

canvas2d 裏的圖不就是咱們剛纔用webgl 畫的圖嗎?(canvasGl:這是風水輪流轉的節奏嗎?)

image.png

其實,如今咱們已經實現目的了。獲取webgl 類型的canvas 像素,就是上面的步驟。

但是我本着苟利國家生死以的目的,仍是要把canvasGl 黑屏的緣由說一下。

咱們剛纔一番操做,實際上並無把渲染器渲染後的數據放到canvas 畫布上,而只是把數據放進了渲染目標裏,而後就撒手無論了。

因此咱們接下來須要把場景渲染到canvas 畫布上。其實現原理是:將渲染器的渲染目標清除,而後再次渲染場景。

接着上文,繼續碼字:

//將渲染器的渲染目標清除
renderer.setRenderTarget(null);
//渲染場景
renderer.render( scene, camera );

效果以下:

image.png

花開並蒂,邀君共賞!

總體代碼以下:

<!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 讀取canvas 像素

首先咱們將代碼會到webgl 渲染出盒子的那一步:

image.png

在這裏我偷一個懶,渲圖仍是使用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);

效果以下:

image.png

這裏的關鍵點是在於繪圖緩衝區,渲染器渲染後的圖像數據會被放到這個緩衝區裏,咱們從中讀取便可。

完整代碼:

<!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>
相關文章
相關標籤/搜索