Navigate

Search topics across all sections

GitHub
Production

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.

terminal
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

ToneMapping.tsxTSX
// 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

ColorSpace.tsxTSX
// 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

Shadows.tsxTSX
// 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

Antialiasing.tsxTSX
// 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.

1
Forgetting tone mapping on a production Canvas
Colors look washed out or unnaturally bright
Don't do this
ToneMappedCanvas.tsxTSX
<Canvas>
  {/* Default tone mapping is NoToneMapping */}
  <meshStandardMaterial color="#ff6b35" />
</Canvas>
Without tone mapping, HDR values clip harshly at white. ACESFilmicToneMapping compresses the dynamic range like a camera sensor, giving you rich highlights and deep shadows. It is the film industry standard for a reason.
2
Using LinearSRGBColorSpace for the output
Scene appears too dark or too bright on most monitors
Don't do this
ColorSpace.tsxTSX
<Canvas
  gl={{
    outputColorSpace: THREE.LinearSRGBColorSpace,
  }}
>
Monitors expect sRGB-encoded pixels. If you output linear values, the gamma curve makes mid-tones look wrong. Three.js defaults to SRGBColorSpace since r152, but explicitly setting it prevents surprises when upgrading.
3
Shadow acne from incorrect bias
Stripy artifacts appear on surfaces that should be smooth
Don't do this
ShadowBias.tsxTSX
<directionalLight
  castShadow
  // No bias set — shadow map fights with surface
/>
Shadow acne happens when the shadow map samples land exactly on the surface, causing self-shadowing. A small negative bias pushes the shadow slightly behind the surface. Too much bias causes 'peter panning' where shadows detach from objects. Start at -0.001 and adjust.

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.