Navigate
Search topics across all sections
Realistic Render
Your 3D objects look like plastic toys under fluorescent lighting. You know the geometry is right. The materials are fancy. But something feels off. The secret is not in the objects -- it is in the camera settings.
You spend hours modeling a beautiful metallic watch in Blender, export it as GLTF, and load it into R3F. The metal looks flat and grey. The glass face has no reflections. Your client asks why it looks nothing like the Blender render.
Visual: No tone mapping detected | Colors clipped at white | Shadow map: 512px (blurry) | Output: LinearSRGB (incorrect for display)
Real-world
Think of realistic rendering like a photography studio.
You have the same subject on the table. But a professional photographer adjusts: exposure (tone mapping), white balance (color management), lens quality (anti-aliasing), and film type (output encoding). These are not about the subject at all -- they are about how the camera sees the subject.
In Three.js, your "camera settings" live on the Canvas gl prop. Small tweaks there make the same scene look amateur or photorealistic.
Raw Scene
Flat, clipped, no depth
Tone Mapping
Compress dynamic range
Color Space
sRGB for monitors
Shadows
Grounding & depth
Photorealistic
Production quality
The Four Render Settings That Matter
Every production R3F app should configure these four properties on the Canvas. Together they transform a flat scene into something convincing.
Step 1: Tone Mapping -- compress the light
// ACESFilmic is the film-industry standard
<Canvas
gl={{
toneMapping: THREE.ACESFilmicToneMapping,
toneMappingExposure: 1.0,
}}
>HDR lighting values can exceed 1.0 (pure white). Without tone mapping, anything above 1.0 clips to white, losing all highlight detail. ACESFilmic compresses the range like real camera film, preserving highlights and deepening shadows.
Step 2: Color Space -- speak the monitor's language
// sRGB is what monitors expect
<Canvas
gl={{
outputColorSpace: THREE.SRGBColorSpace,
}}
>Monitors apply a gamma curve to displayed pixels. If you output linear values, mid-tones look wrong. SRGBColorSpace applies the inverse gamma so colors appear correct on screen. Three.js does this by default since r152, but setting it explicitly prevents surprises.
Step 3: Shadows -- ground your objects
// Enable shadows on Canvas, light, and meshes
<Canvas shadows>
<directionalLight
castShadow
shadow-mapSize-width={1024}
shadow-mapSize-height={1024}
shadow-bias={-0.001}
/>
<mesh castShadow>...</mesh>
<mesh receiveShadow>...</mesh>
</Canvas>Without shadows, objects float in space. Enable shadows on the Canvas, set castShadow on the light and objects, and receiveShadow on the floor. Increase shadow-mapSize for sharper edges, and use shadow-bias to fix acne artifacts.
Step 4: Anti-aliasing -- smooth the jaggies
// Enable MSAA via the gl prop
<Canvas
gl={{ antialias: true }}
dpr={[1, 2]}
>Aliased edges (jagged stairs on diagonal lines) scream "computer graphics." Setting antialias to true enables multisample anti-aliasing (MSAA). Combine with dpr to cap the pixel ratio at 2 -- this gives clean edges without the GPU cost of rendering at 3x or 4x resolution.
What you just learned
ACESFilmicToneMapping compresses HDR values like camera film, preventing harsh white clips.
SRGBColorSpace matches how monitors display colors. Linear output looks wrong on screen.
Shadows need three things: Canvas shadows prop, castShadow on light/objects, receiveShadow on floors.
Shadow bias (-0.001) prevents shadow acne. Shadow map size (1024+) controls sharpness.
Anti-aliasing + dpr={[1, 2]} gives clean edges without excessive GPU cost on high-DPI screens.
Question
You set ACESFilmicToneMapping and your scene looks great. But then you add a bright point light and the highlights turn orange instead of white. What is happening, and which setting would you adjust?
Think about it...
You have two identical scenes. Scene A uses ACESFilmicToneMapping with exposure 1.0. Scene B uses NoToneMapping. You add a very bright point light (intensity: 100) to both. What happens differently in each scene?
Hint: Think about what happens when a pixel value exceeds 1.0 (pure white). How does each tone mapping handle overflow?
Try These Challenges
Put what you learned into practice. Try each challenge in the demo above using the Leva controls, then check the solution.
Try This!
Beginner
Switch toneMapping to None — washed out!
Try This!
Beginner
Set exposure to 3 — overexposed!
Try This!
Beginner
Toggle shadows off — flatter look
These are the patterns that trip up developers most often. Switch between Wrong and Fixed to compare the code side by side.
<Canvas>
{/* Default tone mapping is NoToneMapping */}
<meshStandardMaterial color="#ff6b35" />
</Canvas><Canvas
gl={{
outputColorSpace: THREE.LinearSRGBColorSpace,
}}
><directionalLight
castShadow
// No bias set — shadow map fights with surface
/>Best Practices
Start with ACES
ACESFilmicToneMapping is the right choice for 90% of scenes. It handles bright highlights gracefully and produces cinema-quality color. Only switch to Reinhard or Cineon if you need a specific aesthetic.
Use Environment Maps
Metallic and glossy materials need something to reflect. drei's Environment component with a preset like "studio" or "city" gives you instant reflections. Without it, metallic objects look flat grey.
Shadow Map Size Matters
The default shadow map is 512x512 pixels, which produces blurry shadows. Bump it to 1024 or 2048 for sharper results. Going above 2048 rarely helps and wastes GPU memory.
Cap Pixel Ratio
Use dpr={[1, 2]} on Canvas. High-DPI screens (3x, 4x) render 9 to 16 times more pixels for a minimal visual improvement. Combined with antialias, dpr 2 looks sharp enough on any display.