RenderTarget 介绍

在渲染的时候调用 renderer.render(scene, camera, renderTarget, forceClear) 方法,render 方法有四个参数,我们平时使用只传前两个参数,第一个参数是要绘制的场景,第二个参数是指定相机,相机照射的区域会转换成 2D 绘制到屏幕,而我们今天要讲的就是使用第三个参数,不让渲染的内容直接绘制到屏幕,而是存放到 renderTarget 里。RenderTarget 是一个缓冲区,用来记录渲染后的像素,而不是直接绘制到屏幕上,因此我们可以在绘制屏幕之前做一些处理,以满足特殊的需求。

把 RenderTarget 作为贴图使用

首先创建两个场景:一个 RTScene 场景,用来渲染 RenderTarget 贴图、另一个 Scene 场景,用来渲染最终显示到屏幕上的,并调用 RTScene 场景作为贴图。

创建一个尺寸为 300×300 大小的 RenderTarget 存放 RTScene 的渲染结果

1
2
const RT_SIZE = 300;
const RenderTarget = new THREE.WebGLRenderTarget( RT_SIZE, RT_SIZE);

创建相机

1
2
3
4
5
const Camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 1, 1000 );
Camera.position.x = 2;
Camera.position.y = 2;
Camera.position.z = 2;
Camera.lookAt(0, 0, 0);

创建 RTScene,并向 RTScene 场景添加灯光、环面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 创建场景
const RTScene = new THREE.Scene();

// 向场景添加灯光
const RTLight = new THREE.DirectionalLight( 0xffffff, 1.5 );
RTLight.position.set( 0, 1, 1 ).normalize();
RTScene.add(RTLight);

// 向场景添加环面
const geometryTorus = new THREE.TorusGeometry( 10, 3, 16, 100 );
// 使用红色的材质
const materialTorus = new THREE.LineBasicMaterial( {color: 0xff0000} );
const Torus = new THREE.Mesh( geometryTorus, materialTorus );
Torus.scale.set( 0.05, 0.05, 0.05 );
RTScene.add(Torus);

创建 Scene,并向 Scene 场景添加灯光、立方体,立方体使用 RTScene 作为贴图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const Scene = new THREE.Scene();

const AmbientLight = new THREE.AmbientLight( 0xffffff, 1.5 );
Scene.add(AmbientLight);

const Light = new THREE.DirectionalLight( 0xffffff, 1.5 );
Light.position.set( 0, 1, 1 ).normalize();
Scene.add(Light);

// 向场景添加立方体
const geometryBox = new THREE.BoxBufferGeometry( 1, 1, 1 );
const materialBox = new THREE.MeshStandardMaterial( {
color: 0xff9300, // 橙色
map: RenderTarget.texture // 使用 RTScene 作为贴图,也就是会贴一张环面图
} );
const Box = new THREE.Mesh( geometryBox, materialBox );
Scene.add(Box);

创建 Renderer

1
2
3
4
5
6
7
const Renderer = new THREE.WebGLRenderer( {
antialias: true // 开启消除锯齿
} );
Renderer.setClearColor(0xffffff, 1.0); // 设置画布背景色
Renderer.setPixelRatio( window.devicePixelRatio ); // 设置屏幕像素比
Renderer.setSize( window.innerWidth, window.innerHeight );
document.body.appendChild( Renderer.domElement ); // 把 canvas 元素放到 body 内

场景设置完毕接下来我们就可以渲染场景了,注意这两个 render 的顺序,第一个的结果要给第二个使用,所以不能写反了

1
2
Renderer.render(RTScene, Camera, RenderTarget); // 离屏渲染并存放到 RenderTarget 里
Renderer.render(Scene, Camera); // 渲染显示到屏幕上的场景

如果场景有动画则需要使用 requestAnimationFrame 马不停蹄的进行渲染

1
2
3
4
5
6
7
8
9
10
11
12
animation();

function animation() {
Renderer.clear();

Box.rotation.y += 0.01; // 让立方体沿着 y 轴不停的旋转

Renderer.render(RTScene, Camera, RenderTarget);
Renderer.render(Scene, Camera);

requestAnimationFrame(animation); // 循环
}

实例 Demo

把 RenderTarget 画到 2D Canvas 上

我们可以使用 Renderer.readRenderTargetPixels(renderTarget, x, y, width, height, buffer) 方法读取离屏渲染的像素数据到一个 Uint8Array(length) 实例里,xy 可以限制读取的起点,widthheight 为读取的宽高。

新建一个画布,通过 canvas.getContext('2d') 方法获取 2D 的对象,此对象有个 ctx.putImageData(imagedata, dx, dy) 可以让画布绘制 ImageData(data, width, height) 实例类型的矩形像素,dataUint8ClampedArray(length) 实例,widthheight 为图片的宽和高(必须保证 Uint8ClampedArraylength = 4 * width * height 才不会报错)。

指定 canvas 元素,并获取 2D 对象

1
2
const preview = document.getElementById('preview');
const ctx = preview.getContext('2d');

创建 buffer 用来存放 RenderTarget 的像素数据,实例化 Uint8ClampedArray 以便创建 ImageData 时使用。在处理图片像素中与 Uint8Array 相比 Uint8ClampedArray 更安全,因为 Uint8ClampedArray 能保证写入的值在 0-255 之间,当写入的值小于 0 时会自动改为 0,当大于 255 时会自动改为 255。

1
2
const buffer = new Uint8Array(RT_SIZE * RT_SIZE * 4);
const clamped = new Uint8ClampedArray(buffer.buffer);
1
2
3
Renderer.readRenderTargetPixels(RenderTarget, 0, 0, RT_SIZE, RT_SIZE, buffer); // 读取像素到 buffer
const imageData = new ImageData(clamped, RT_SIZE, RT_SIZE); // 创建可供 canvas 使用的图像数据类型
ctx.putImageData(imageData, 0, 0); // 绘制到 canvas 中

实例 Demo

小结

至此结束,完整代码请查看例子,感谢阅读。