使用React Three Fiber实现多Pass渲染

February 26

引言

React Three Fiber (R3F) 是一个非常方便且强大的 threejs react 封装。 使用它可以利用 React 框架搭建渲染场景。 关于框架的详细内容可以查阅 官方文档

R3F 提供了 EffectComposer 和常用的后处理, 如 Bloom, Dither 等等。但这不能满足需要自定义 Pipeline 的复杂渲染需求, 此时需要使用 three.js的原生接口。在之前的使用场景中我发现这方面的文档和案例都比较少, 所以我决定写一篇文章来分享一下我在使用 R3F 实现多Pass渲染的经验。

准备

这篇文章假设你已经设置好了基本的 R3F 环境。如果你还没有设置好, 请查阅 官方文档

除去基本的 R3F 环境外, 还将使用 pmndrspostprocessing 库。你可以通过以下命令安装:

# yarn yarn add postprocessing # npm npm install postprocessing

更多关于 postprocessing 库的信息可以查阅 官方文档

禁用 R3F RenderLoop

R3F 默认的渲染循环对自定义 Pipeline 的支持不是很好, 因此首先我们需要禁用它, 建立我们自己的渲染循环来控制渲染流程。 做完这步后, 你的场景将不会被渲染, Canvas 将会显示一个空白画布。

<Canvas frameLoop="never"> {/* Scene */} </Canvas>

注意 如果你已经通过 <EffectComposer /> 设置了一些效果, 这么做可能会导致原有的效果失效。 建议在后续自定义渲染循环中重新建立相关效果。

创建 EffectComposer

首先我们需要创建一个新的 EffectComposer 实例, 用于管理我们的渲染流程。在我的使用场景里, Canvas 组件会在多处复用, 因此我将 EffectComposer 放在场景组件内。

// make sure to import the necessary modules from right package import { EffectComposer, RenderPass, ShaderPass } from 'postprocessing' function Scene() { const composerRef = useRef<EffectComposer>() const { gl, scene, camera } = useThree() function initComposer() { // 1. Create a new EffectComposer instance const composer = new EffectComposer(gl) // 2. Usually we want to leverage the existing scene setup inside r3f, // then apply post processing effects on top of that. composer.addPass(new RenderPass(scene, camera)) // 3. here you can add more passes, // e.g. composer.addPass(new ShaderPass(…)) // 4. Save the composer instance to a ref composerRef.current = composer; }; function disposeComposer() { if (composerRef.current) { composerRef.current.dispose() const composer = composerRef.current; for (var i = composer.passes.length - 1; i >= 0; i--) { composer.passes[i].dispose(); } composerRef.current = undefined; } }; useEffect(() => { // 1. initialize and setup the composer on scene && camera change initComposer(); // 2. dispose the composer when the component unmounts return disposeComposer }, [gl, scene, camera]) }

到此为止, 我们已经创建了一个基本的 EffectComposer 实例, 它包含了一个 RenderPass 用于渲染场景。 但 Canvas 依旧是空白的, 因为我们还没有将 EffectComposer 的输出渲染到屏幕上。

自定义渲染循环

本文将介绍两种创建方式, 分别对应 R3F 的两种 frameLoop 模式: demandauto

demand - 使用 react 渲染机制

function Scene() { const composerRef = useRef<EffectComposer>(); composerRef.current?.render() {...} }

这样在每次 react 更新dom时都会触发渲染, 从而达到 Canvas 动态响应 react 的 state 更新。

auto - 使用 requestAnimationLoop

function Scene() { const composerRef = useRef<EffectComposer>(); const renderLoopIdRef = useRef<number>(); useEffect(() = { // 1. cancel exisiting render loop if (renderLoopIdRef.current) { cancelAnimationFrame(renderLoopIdRef.current) } function renderLoop() { composerRef.current?.render(); renderLoopIdRef.current = requestAnimationLoop(renderLoop) } // 2. start a new render loop renderLoopIdRef.current = requestAnimationLoop(renderLoop) return () => { cancelAnimationFrame(renderLoopIdRef.current) } }, []) }

这样我们就创建了一个基本的渲染循环, 并根据浏览器建议的刷新频率逐重绘画面。

参考

© 2020 - 2025 Ruiyao Luo

25/12/10 11:30

PROD

#68e6fa8