使用React Three Fiber实现多Pass渲染

Multi-pass rendering with React Three Fiber

Feb 26, 25

引言

Before you read

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

React Three Fiber (R3F) is a very convenient and powerful threejs react wrapper. It can be used to quickly build rendering scenes using the react framework. For more details about the framework, please refer to the official documentation.

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

R3F provides EffectComposer and common post-processing effects, such as Bloom, Dither, etc. However, this cannot meet the needs of highly customized scenes. At this time, you need to use the native interface of three.js. In the previous use cases, I found that there are relatively few documents and cases in this area, so I decided to write an article to share my experience in using R3F to achieve multi-pass rendering.

准备

Requirements

这篇文章假设你已经设置好了基本的 R3F 环境。如果你还没有设置好, 请查阅 官方文档
This article assumes that you have set up the basic R3F environment. If you haven't set it up yet, please refer to the official documentation.

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

# yarn yarn add postprocessing # npm npm install postprocessing

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

In addition to the basic R3F environment, the postprocessing library from pmndrs will also be used. You can install it with the following command:

# yarn yarn add postprocessing # npm npm install postprocessing

For more information about the postprocessing library, please refer to the official documentation.

禁用 R3F RenderLoop

Disable R3F RenderLoop

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

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

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

The default rendering loop of R3F does not support custom pipelines very well, so we need to disable it first and establish our own rendering loop to control the rendering process.

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

Note If you have set some effects through <EffectComposer />, doing this may cause the original effects to fail. It is recommended to re-establish the relevant effects in the subsequent custom rendering loop.

创建 EffectComposer

Create EffectComposer

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

First, we need to create a new EffectComposer instance to manage our rendering process. In my use case, the Canvas component will be reused in multiple places, so I put the EffectComposer in the scene component.

// 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 的输出渲染到屏幕上。

So far, we have created a basic EffectComposer instance, which contains a RenderPass for rendering the scene. However, the <Canvas /> is still blank because we have not yet rendered the output of the EffectComposer to the screen.

自定义渲染循环

Custom Rendering Loop

本文将介绍两种创建方式,分别对应 R3F 的两种 frameLoop 模式: demandauto
This article will introduce two ways to create a custom rendering loop, corresponding to the two frameLoop modes of R3F: demand and auto.

demand - 使用 react 渲染机制

demand - Use react rendering mechanism

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

这样在每次 react 更新dom时都会触发渲染,从而达到 Canvas 动态响应 react 的 state 更新。
This way, rendering will be triggered every time the dom is updated by react, so that the Canvas can dynamically respond to state updates from react.

auto - 使用 requestAnimationLoop

auto - Use 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) } }, []) }

这样我们就创建了一个基本的渲染循环,并根据浏览器建议的刷新频率逐重绘画面。
Now we have created a basic rendering loop, which will redraw the screen according to the refresh rate recommended by the browser.

参考

References