Three.js From Zero · Article s13-07
R3F Portal Scenes
R3F Portal Scenes is Article s13-07 of Three.js From Zero, a MasterAllArts free interactive lesson for artists learning creative 3D on the web.
Season 13 · Article 07 · R3F Mastery
A doorway you can see another world through. Two scenes, one render. MeshPortalMaterial from drei renders a separate sub-scene onto the surface of a mesh — perfect for room transitions, dimensional doorways, picture-in-picture.
Code-walkthrough article
Requires drei. Visual effect best appreciated in a real project — copy the snippet, drop in your scene.
The trick: render targets
Under the hood, drei's MeshPortalMaterial renders a separate Three.js scene to an off-screen render target, then uses that texture on your mesh's surface. The portal mesh effectively becomes a window — what's "inside" is the other scene from the other scene's camera.
Basic portal
import { MeshPortalMaterial, Environment } from '@react-three/drei';
function PortalScene() {
return (
<mesh>
<planeGeometry args={[2, 3]} />
<MeshPortalMaterial>
{/* This is the OTHER world, rendered onto the plane */}
<Environment preset="sunset" background />
<mesh>
<sphereGeometry />
<meshStandardMaterial color="orange" />
</mesh>
</MeshPortalMaterial>
</mesh>
);
}
Everything inside <MeshPortalMaterial> is its own scene with its own background, lighting, and camera. From the outside, the mesh looks like it has a sunset behind it and an orange sphere in front. From elsewhere in your main scene, the plane is opaque — you see the portal content.
Camera dolly-in transition
const [active, setActive] = useState(false);
<MeshPortalMaterial blend={active ? 0 : 1}>
{/* portal contents */}
</MeshPortalMaterial>
blend 0 = camera is fully inside the portal world (you're "through" the doorway). blend 1 = camera is in your main world, looking AT the portal. Animate this value with framer-motion or react-spring for a "step into the world" effect.
Multiple portals with shared rendering
function Gallery() {
return (
<>
{worlds.map((world, i) => (
<mesh key={world.id} position={[i * 3, 0, 0]}>
<circleGeometry args={[1, 32]} />
<MeshPortalMaterial worldUnits={false}>
<color attach="background" args={[world.bg]} />
<world.Scene />
</MeshPortalMaterial>
</mesh>
))}
</>
);
}
A row of portals, each showing a different world. The art-gallery / dimensional-rift pattern. Each renders to its own RT — cost scales linearly with portal count, so don't put 50 of them at once.
Performance considerations
A portal is a second render pass. If your portal scene is as complex as your main scene, you've doubled rendering cost. Tips:
- Use simpler geometry in portal scenes (fewer lights, no shadows).
- Render portals at lower resolution:
<MeshPortalMaterial resolution={512}>defaults to 1024. - Cull portals that are off-screen — drei does this automatically when
frames={1}is set.
Common first-time pitfalls
worldUnits appropriately. true = treat the portal plane as a real opening (camera moves through it geometrically). false = treat portal plane as a 2D window (more like a TV screen).Exercises
- The classic dimensional door. A doorway in a wall, looking out onto a sunny field. Approach the door — camera dolly forward — and animate blend to 0 to "enter."
- Mini-map portal. A circular portal at the top-right corner showing a top-down camera of the main scene. Two cameras, one render target.
- Aquarium. A glass tank (cube) with fish inside, rendered via portal so the fish are confined to the tank world. Looks impossible — that's why it works.
UP NEXT
S13-08 — R3F Post-Processing → Bloom, depth of field, vignette. The cinematic layer.