读取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
接下来我要在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 里的背景可不是一回事哦。
接着上面的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 里已经有了一个黄色的盒子
接下来,重头戏开始,我要获取右侧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 读取canvas 像素
首先我们将代码会到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>
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。