Three.js小白的学习之路。
之前博客写过一篇使用WebGLRenderTarget离屏渲染实现不同场景之间的过渡。本篇则主要是介绍一下WebGLRenderTarget以及一些其他使用,本篇参考了Three.js的官方中有关渲染目标的讲解。
渲染目标大体上指的是可以被渲染的纹理。当它被渲染之后,你可以像使用其他纹理一样使用它。意思就是可以将渲染目标输出的纹理当做一张图片纹理去使用(浅薄理解)。
官方示例
下面贴一下官网的一个例子:
const renderTargetWidth = 512;
const renderTargetHeight = 512;const scene = new Three.Scene();const camera = new Three.PerspectiveCamera(75,renderTargetWidth / renderTargetHeight,0.1,10
);
camera.position.set(0, 0, 2);
camera.lookAt(0, 0, 0);const rtScene = new Three.Scene();
rtScene.background = new Three.Color("red");const rtCamera = new Three.PerspectiveCamera(75,renderTargetWidth / renderTargetHeight,0.1,10
);
rtCamera.position.set(0, 0, 2);
rtCamera.lookAt(0, 0, 0);const renderTarget = new Three.WebGLRenderTarget(renderTargetWidth,renderTargetHeight
);const ambientLight = new Three.AmbientLight(0xffffff, 1);
scene.add(ambientLight);{const light = new Three.DirectionalLight(0xffffff, 1);light.position.set(-1, 2, 4);rtScene.add(light);
}const geometry = new Three.BoxGeometry(1, 1, 1);const makeInstance = (geo: Three.BufferGeometry, color: number, x: number) => {const material = new Three.MeshPhongMaterial({ color });const cube = new Three.Mesh(geo, material);rtScene.add(cube);cube.position.x = x;return cube;
};const rtCube = [makeInstance(geometry, 0x44aa88, 0),makeInstance(geometry, 0x8844aa, -2),makeInstance(geometry, 0xaa88aa, 2),
];const material = new Three.MeshPhongMaterial({map: renderTarget.texture,
});
const cube = new Three.Mesh(geometry, material);
scene.add(cube);const loop = (time: number) => {time *= 0.0001;rtCube.forEach((cube, ndx) => {const speed = 1 + ndx * 0.1;const rot = time * speed;cube.rotation.x = rot;cube.rotation.y = rot;});renderer.setRenderTarget(renderTarget);renderer.render(rtScene, rtCamera);renderer.setRenderTarget(null);renderer.render(scene, camera);requestAnimationFrame((time) => {loop(time);});
};
loop(0);
代码很简单,不难理解。先看一下运行结果:
可以看到,图中最外层的立方体的纹理是一个由三个旋转立方体+红色背景组成的纹理。
怎么实现的呢?这就是WebGLRenderTarget的妙用之处了。
首先创建了一个renderTargetScene(rtScene)和renderTargetCamera(rtCamera),作为渲染目标所需的场景和摄像机,在生成了三个cube加入到rtScene中。
在loop循环中,并没有直接讲场景渲染到canvas中,而是加了中间一层WebGLRenderTarget。可以理解为一个以前都是直接拿钞票付款,没有中间商,现在变成了要付款,得先掏出手机,因为钱都在手机里,然后扫码付款。
手机就是WebGLRenderTarget,他将目标换一种形式保存了下来,在这里面就可以简单理解为一个图像纹理。
既然是图像,那么自然可将其作为模型的纹理贴图进行使用,因此最外层的盒子的map属性就可设置为renderTarget.texture,从而实现上述效果。
使用场景
一个就是之前提到过的离屏渲染。
一个是后处理效果,如在使用辉光等后处理时,经常会导致场景变的模糊,加入一个WebGLRenderTarget可以明显改善,官方在实现相关的后处理特效时也使用了WebGLRenderTarget。
此外还可以作为3D场景中可能会用到的镜子反射(像龙骑里面一个人被困在镜子里面,只有一半身体可以看到那种)、监控画面等需要看到不是当前所能看到的场景等。
下面写一个简单的例子,车子在行驶过程中,后视镜突然变成一个别的东西(常见的鬼片里面会出现的那种,但我做的很简单很粗糙,凑活看一看):
const renderTargetWidth = 512;
const renderTargetHeight = 512;const scene = new Three.Scene();
const camera = new Three.PerspectiveCamera(60,window.innerWidth / window.innerHeight,0.1,10
);
camera.position.set(-2.5, 1.5, -2.5);
camera.lookAt(0, 0, 0);const rtScene = new Three.Scene();
rtScene.background = new Three.Color(0xff0000);
const rtCamera = new Three.PerspectiveCamera(60,renderTargetWidth / renderTargetHeight,0.1,10
);
rtCamera.position.set(-2.5, 1.5, -2.5);
rtCamera.lookAt(0, 0, 0);const geo = new Three.TorusGeometry(0.5, 0.3);
const material = new Three.MeshBasicMaterial({ color: 0x00ff00 });
const torus = new Three.Mesh(geo, material);
rtScene.add(torus);const renderTarget = new Three.WebGLRenderTarget(renderTargetWidth,renderTargetHeight
);const wheel: Three.Mesh[] = [];
let mirror: Three.Mesh;
new GLTFLoader().load("/car.glb", (glb) => {const mesh = glb.scene;scene.add(mesh);mirror = mesh.getObjectByName("mirror") as Three.Mesh;wheel.push(mesh.getObjectByName("wheel1") as Three.Mesh);wheel.push(mesh.getObjectByName("wheel2") as Three.Mesh);wheel.push(mesh.getObjectByName("wheel3") as Three.Mesh);wheel.push(mesh.getObjectByName("wheel4") as Three.Mesh);mirror.material = new Three.MeshBasicMaterial({map: renderTarget.texture,});
});const ambientLight = new Three.AmbientLight(0xffffff, 1);
scene.add(ambientLight);
rtScene.add(ambientLight.clone());const wheelRotate = () => {wheel.forEach((item) => {item.rotation.z -= 0.05;});
};const loop = () => {wheelRotate();torus.rotation.x += 0.01;renderer.setRenderTarget(renderTarget);renderer.render(rtScene, rtCamera);renderer.setRenderTarget(null);renderer.render(scene, camera);requestAnimationFrame(loop);
};loop();
部分代码省略了,效果如下:
总之,WebGLRenderTarget是一个比较悬乎的概念,使用中会遇到各种各样的问题,欢迎大家一起讨论学习!