2

读取canvas 的像素,应该是一件很简单且基础的事。就像在canvas 2d 里,使用其上下文对象的getImageData() 方法就可以轻松搞定。

获取webgl 类型的canvas 像素也不难,只是我百度了一下竟然没找到很详细的那种文章,我只找到了一个webgl 原生的,three.js 的竟然没有看见。

我基于原生webgl 的实现原理,找到了three.js 里的相关方法,然后又找到了其官方提供的案例,在里不断压缩代码,将相应功能提取了出来。

下面咱们就详细说一下实现方法,先说three.js 的。

three.js 读取canvas 像素

先搭个架子

<!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 的canvas

Image.png

接下来我要在webgl 类型的canvas 里画一个盒子,第一个二维的canvas 先不管它。
先在js 里搭建three.js 环境

<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 里的背景可不是一回事哦。

Image.png

接着上面的js 代码,接续码

//建立盒子对象
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 里已经有了一个黄色的盒子

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>


已注销
148 声望9 粉丝