I have a WebGL widget (using R3F) of which I want the user to be able to take a screenshot, which is then saved and can be displayed elsewhere on the page.
I’m doing this by grabbing the RootState of the current scene, rendering it to a brand new WebGLRenderTarget
and then render the scene to it. This mostly works alright (for all geometry and lights in the room), however, none of the Effects in the scene are applied. I don’t know how to grab these effects from the RootState and render them (in subsequent next passes?) onto the new render target I created.
I have a minimum working example here: https://codesandbox.io/p/sandbox/effect-screenshot-clpfrv
The relevant screenshot rendering is Widget::getCurrentImage()
.
import React from "react";
import { Canvas, RootState } from "@react-three/fiber";
import { EffectComposer, Bloom } from "@react-three/postprocessing";
import { OrbitControls } from "@react-three/drei";
import { Color, WebGLRenderTarget } from "three";
interface IWidgetProps {}
interface IWidgetState {
rootState: RootState | null;
}
class Widget extends React.Component<IWidgetProps, IWidgetState> {
constructor(props: IWidgetProps) {
super(props);
this.state = {
rootState: null,
};
}
public getCurrentImage(): string | null {
const WIDTH = 512;
const HEIGHT = 512;
const gl = this.state.rootState?.gl;
const camera = this.state.rootState?.camera;
const scene = this.state.rootState?.scene;
if (gl && scene && camera) {
const renderTarget = new WebGLRenderTarget(WIDTH, HEIGHT);
gl.setRenderTarget(renderTarget);
gl.render(scene, camera);
const imageData = new ImageData(WIDTH, HEIGHT);
gl.readRenderTargetPixels(
renderTarget,
0,
0,
WIDTH,
HEIGHT,
imageData.data
);
gl.setRenderTarget(null);
const canvas = document.createElement("canvas");
canvas.width = WIDTH;
canvas.height = HEIGHT;
const ctx = canvas.getContext("2d");
ctx?.putImageData(imageData, 0, 0);
return canvas.toDataURL("image/png");
}
return null;
}
render() {
return (
<Canvas
className="canvas"
// Preserving drawing buffer is necessary in order to be able to make a screenshot
// using "toDataURL()".
onCreated={(renderState: RootState) =>
this.setState({
...this.state,
rootState: renderState,
})
}
style={{
width: "80vw",
height: "80vw",
background: "black",
}}
>
<OrbitControls />
<ambientLight intensity={0.95} />
<pointLight position={[3, 2, 0]} intensity={0.5} />
<mesh rotation={[0, 10, 0]}>
<boxGeometry attach="geometry" args={[1, 1, 1]} />
<meshStandardMaterial
attach="material"
color={new Color(5, 3, 1.5)}
/>
</mesh>
<EffectComposer>
<Bloom
mipmapBlur={true}
luminanceThreshold={1}
levels={8}
intensity={1}
/>
</EffectComposer>
</Canvas>
);
}
}
export default Widget;
The Widget here will render the following:
Whereas the saved-out state looks like that but without the applied effect (Bloom in this case):