tiramisu/camera

Camera configuration and post-processing effects.

Cameras define how your 3D scene is projected onto the 2D screen. This module provides both perspective (3D) and orthographic (2D) camera types, plus a full post-processing pipeline for visual effects.

Camera Types

// Perspective camera for 3D games
let assert Ok(cam) = camera.perspective(
  field_of_view: 75.0,
  near: 0.1,
  far: 1000.0,
)

// Orthographic camera for 2D games
let cam = camera.camera_2d(size: vec2.Vec2(800, 600))

Adding to Scene

scene.camera(
  id: "main-camera",
  camera: cam,
  transform: transform.at(position: vec3.Vec3(0.0, 5.0, 10.0)),
  active: True,
  viewport: None,
  postprocessing: None,
)

Post-Processing

Add visual effects like bloom, vignette, and film grain:

let pp = camera.new_postprocessing()
  |> camera.add_pass(camera.clear_pass(None))
  |> camera.add_pass(camera.render_pass())
  |> camera.add_pass(camera.bloom(strength: 1.0, threshold: 0.8, radius: 0.4))
  |> camera.add_pass(camera.fxaa())
  |> camera.add_pass(camera.output_pass())

scene.camera(
  id: "main-camera",
  camera: cam,
  // ...
  postprocessing: Some(pp),
)

Viewports

Create split-screen or picture-in-picture views:

let minimap = camera.ViewPort(
  position: vec2.Vec2(650, 10),
  size: vec2.Vec2(150, 100),
)

Types

Camera configuration (perspective or orthographic projection).

Use with scene.Camera nodes to define viewpoints in your scene.

pub opaque type Camera

Validation errors for camera creation.

pub type CameraError {
  InvalidFieldOfView(Float)
  InvalidAspectRatio(Float)
  InvalidNearPlane(Float)
  InvalidFarPlane(Float)
  NearFarConflict(near: Float, far: Float)
}

Constructors

  • InvalidFieldOfView(Float)

    Field of view must be between 0 and 180 degrees

  • InvalidAspectRatio(Float)

    Aspect ratio must be positive

  • InvalidNearPlane(Float)

    Near plane must be positive

  • InvalidFarPlane(Float)

    Far plane must be positive

  • NearFarConflict(near: Float, far: Float)

    Near plane must be less than far plane

Post-processing pass types.

Each pass represents a visual effect that will be applied to the scene. Passes are executed in the order they are added to the pipeline.

Pipeline Passes

Three.js postprocessing requires specific passes for proper rendering:

  • RenderPass: Renders the scene to the render target (usually first)
  • ClearPass: Clears render target with a color (for backgrounds)
  • OutputPass: Final tone mapping and output (usually last)

You must explicitly add these passes in the correct order.

pub type Pass {
  RenderPass
  ClearPass(color: option.Option(Int))
  OutputPass
  PixelatePass(
    pixel_size: Int,
    normal_edge_strength: Float,
    depth_edge_strength: Float,
  )
  BloomPass(strength: Float, threshold: Float, radius: Float)
  FilmPass(
    noise_intensity: Float,
    scanline_intensity: Float,
    scanline_count: Int,
    grayscale: Bool,
  )
  VignettePass(darkness: Float, offset: Float)
  FXAAPass
  GlitchPass(dt_size: Int)
  ColorCorrectionPass(
    brightness: Float,
    contrast: Float,
    saturation: Float,
  )
  CustomShaderPass(
    vertex_shader: String,
    fragment_shader: String,
    uniforms: List(#(String, UniformValue)),
  )
}

Constructors

  • RenderPass

    Render pass - renders the scene to the render target.

    This pass actually draws your 3D scene. It should typically be one of the first passes in your pipeline. If you need the scene background to work, add a ClearPass before this.

  • ClearPass(color: option.Option(Int))

    Clear pass - clears the render target with a color.

    Use this before RenderPass to make scene backgrounds work correctly. The color parameter overrides the scene background if provided.

    • None: Uses the scene’s background color
    • Some(color): Uses the specified hex color
  • OutputPass

    Output pass - applies tone mapping and outputs to screen.

    This should typically be the last pass in your pipeline. It applies final color corrections and tone mapping before displaying the result.

  • PixelatePass(
      pixel_size: Int,
      normal_edge_strength: Float,
      depth_edge_strength: Float,
    )

    Pixelation effect with optional edge detection.

    Creates a retro pixel-art aesthetic by reducing the resolution of the image. Edge detection can add outlines based on surface normals and depth.

  • BloomPass(strength: Float, threshold: Float, radius: Float)

    Bloom effect (glow for bright areas).

    Makes bright areas of the scene glow and bleed into surrounding pixels. Great for emissive materials, lights, and sci-fi aesthetics.

  • FilmPass(
      noise_intensity: Float,
      scanline_intensity: Float,
      scanline_count: Int,
      grayscale: Bool,
    )

    Film grain effect.

    Adds analog film texture with grain noise and optional scanlines. Can create a retro or cinematic look.

  • VignettePass(darkness: Float, offset: Float)

    Vignette effect (darkened edges).

    Darkens the edges of the screen, focusing attention on the center.

  • FXAAPass

    FXAA anti-aliasing.

    Fast approximate anti-aliasing that smooths jagged edges. Usually added as the last pass for a polished final output.

  • GlitchPass(dt_size: Int)

    Glitch effect.

    Creates digital corruption artifacts with RGB channel offsets. Great for cyberpunk or error state aesthetics.

  • ColorCorrectionPass(
      brightness: Float,
      contrast: Float,
      saturation: Float,
    )

    Color correction.

    Adjust brightness, contrast, and saturation of the final image.

  • CustomShaderPass(
      vertex_shader: String,
      fragment_shader: String,
      uniforms: List(#(String, UniformValue)),
    )

    Custom shader pass.

    Apply a custom GLSL shader for advanced effects.

Opaque post-processing composer type.

Contains a list of passes that will be applied in order to the rendered scene.

pub opaque type PostProcessing

Uniform values for custom shaders.

When creating custom shader passes, you can pass uniform values of different types.

pub type UniformValue {
  FloatUniform(Float)
  IntUniform(Int)
  Vec2Uniform(Float, Float)
  Vec3Uniform(Float, Float, Float)
  ColorUniform(Int)
}

Constructors

  • FloatUniform(Float)
  • IntUniform(Int)
  • Vec2Uniform(Float, Float)
  • Vec3Uniform(Float, Float, Float)
  • ColorUniform(Int)

Viewport configuration for split-screen or picture-in-picture rendering.

Coordinates are in pixels from the top-left of the canvas.

Example

import tiramisu/camera
import tiramisu/scene
import vec/vec2

// Create a viewport in the top-right corner
let minimap_viewport = camera.ViewPort(
  position: vec2.Vec2(650, 10),
  size: vec2.Vec2(150, 100),
)

scene.camera(
  id: "minimap",
  camera: minimap_cam,
  transform: transform.identity,
  active: True,
  viewport: option.Some(minimap_viewport),
  postprocessing: option.None,
)
pub type ViewPort {
  ViewPort(position: vec2.Vec2(Int), size: vec2.Vec2(Int))
}

Constructors

  • ViewPort(position: vec2.Vec2(Int), size: vec2.Vec2(Int))

    Arguments

    position

    Position from top-left corner in pixels (x, y)

    size

    Size in pixels (width, height)

Values

pub fn add_pass(pp: PostProcessing, pass: Pass) -> PostProcessing

Add a pass to the pipeline.

Passes are executed in the order they are added.

Example

camera.postprocessing_new()
|> camera.add_pass(camera.bloom(
  strength: 1.5,
  threshold: 0.85,
  radius: 0.4,
))
|> camera.postprocessing_add_pass(camera.fxaa())
pub fn bloom(
  strength strength: Float,
  threshold threshold: Float,
  radius radius: Float,
) -> Pass

Creates a bloom effect pass.

Makes bright areas glow and bleed into surrounding pixels.

Example

// Subtle bloom for realistic glow
camera.bloom(strength: 0.8, threshold: 0.85, radius: 0.4)

// Intense bloom for sci-fi effect
camera.bloom(strength: 2.0, threshold: 0.5, radius: 0.8)
pub fn camera_2d(size size: vec2.Vec2(Int)) -> Camera

Create a 2D camera centered at origin with world coordinates.

Useful for 2D games where (0,0) is the center of the screen.

Example

let cam = camera.camera_2d(size: vec2.Vec2(800, 600))
scene.Camera(
  id: "main_camera",
  camera: cam,
  transform: transform.at(position: vec3.Vec3(0.0, 0.0, 5.0)),
  active: True,
  viewport: option.None,
)
// (0, 0) is screen center, positive Y is up
pub fn camera_2d_screen_space(
  size size: vec2.Vec2(Int),
) -> Camera

Create a 2D camera with screen-space coordinates (top-left origin).

Useful for UI or pixel-perfect 2D games where (0,0) is top-left corner.

Example

let cam = camera.camera_2d_screen_space(size: vec2.Vec2(800, 600))
scene.Camera(
  id: "ui_camera",
  camera: cam,
  transform: transform.at(position: vec3.Vec3(0.0, 0.0, 5.0)),
  active: True,
  viewport: option.None,
)
// (0, 0) is top-left, positive Y is down (like CSS)
pub fn camera_2d_with_bounds(
  left: Float,
  right: Float,
  top: Float,
  bottom: Float,
) -> Camera

Create a 2D camera with custom bounds.

Example

let cam = camera.camera_2d_with_bounds(
  left: -100.0, right: 100.0,
  top: 75.0, bottom: -75.0,
)
scene.Camera(
  id: "game_camera",
  camera: cam,
  transform: transform.at(position: vec3.Vec3(0.0, 0.0, 5.0)),
  active: True,
  viewport: option.None,
)
pub fn clear_pass(color color: option.Option(Int)) -> Pass

Creates a clear pass for postprocessing.

Clears the render target with a color. Use before RenderPass to make scene backgrounds work correctly with postprocessing. Pass None to use the scene’s background color.

Example

// Use scene background
camera.clear_pass(option.None)

// Use custom color
camera.clear_pass(option.Some(0x000000))  // Black
pub fn color_correction(
  brightness brightness: Float,
  contrast contrast: Float,
  saturation saturation: Float,
) -> Pass

Creates a color correction pass.

Adjusts brightness, contrast, and saturation of the final image.

Example

// Brighten and increase saturation
camera.color_correction(
  brightness: 0.2,
  contrast: 0.1,
  saturation: 0.3,
)

// Desaturated look
camera.color_correction(
  brightness: 0.0,
  contrast: 0.2,
  saturation: -0.5,
)
pub fn custom_shader(
  vertex_shader vertex_shader: String,
  fragment_shader fragment_shader: String,
  uniforms uniforms: List(#(String, UniformValue)),
) -> Pass

Creates a custom shader pass for advanced effects.

Apply custom GLSL vertex and fragment shaders with uniforms.

Example

camera.custom_shader(
  vertex_shader: "
    varying vec2 vUv;
    void main() {
      vUv = uv;
      gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
    }
  ",
  fragment_shader: "
    uniform sampler2D tDiffuse;
    uniform float intensity;
    varying vec2 vUv;
    void main() {
      vec4 color = texture2D(tDiffuse, vUv);
      gl_FragColor = color * intensity;
    }
  ",
  uniforms: [
    #("intensity", camera.FloatUniform(1.5)),
  ],
)
pub fn film_grain(
  noise_intensity noise_intensity: Float,
  scanline_intensity scanline_intensity: Float,
  scanline_count scanline_count: Int,
  grayscale grayscale: Bool,
) -> Pass

Creates a film grain effect.

Adds analog film texture with grain noise and optional scanlines.

Example

// Subtle film grain
camera.film_grain(
  noise_intensity: 0.2,
  scanline_intensity: 0.0,
  scanline_count: 0,
  grayscale: False,
)

// Retro CRT monitor effect
camera.film_grain(
  noise_intensity: 0.4,
  scanline_intensity: 0.3,
  scanline_count: 512,
  grayscale: True,
)
pub fn fxaa() -> Pass

Create an FXAA anti-aliasing pass.

Fast approximate anti-aliasing that smooths jagged edges. Usually added as the last pass in the pipeline.

Example

camera.postprocessing_new()
|> camera.postprocessing_add_pass(camera.bloom(...))
|> camera.postprocessing_add_pass(camera.fxaa())  // Last pass
pub fn glitch(dt_size dt_size: Int) -> Pass

Creates a glitch effect pass.

Creates digital corruption artifacts with RGB channel offsets.

Example

camera.glitch(dt_size: 64)
pub fn new_postprocessing() -> PostProcessing

Create a new empty post-processing pipeline.

Start with this and add passes using postprocessing_add_pass.

Example

let pp = camera.new_postprocessing()
  |> camera.add_pass(camera.clear_pass(option.None))
  |> camera.add_pass(camera.render_pass())
  |> camera.add_pass(camera.bloom(...))
  |> camera.add_pass(camera.fxaa())
  |> camera.add_pass(camera.output_pass())
pub fn orthographic(
  left left: Float,
  right right: Float,
  top top: Float,
  bottom bottom: Float,
  near near: Float,
  far far: Float,
) -> Camera

Create an orthographic camera (for 2D games or isometric views).

No perspective distortion - objects are the same size regardless of distance.

Example

let cam = camera.orthographic(
  left: -400.0, right: 400.0,
  top: 300.0, bottom: -300.0,
  near: 0.1, far: 1000.0,
)
pub fn output_pass() -> Pass

Create an output pass.

Applies final tone mapping and outputs to the screen. This should typically be the last pass in your pipeline.

Example

camera.postprocessing_new()
|> camera.postprocessing_add_pass(camera.render_pass())
|> camera.postprocessing_add_pass(camera.bloom(...))
|> camera.postprocessing_add_pass(camera.output_pass())  // Last pass
pub fn perspective(
  field_of_view fov: Float,
  near near: Float,
  far far: Float,
) -> Result(Camera, CameraError)

Creates a perspective camera for 3D games.

Objects further away appear smaller, like in real life. The aspect ratio is automatically calculated from the viewport dimensions at render time.

pub fn pixelate(pixel_size pixel_size: Int) -> Pass

Creates a simple pixelation effect without edge detection.

Example

// Subtle pixelation
camera.pixelate(pixel_size: 2)

// Strong retro effect
camera.pixelate(pixel_size: 8)
pub fn pixelate_with_edges(
  pixel_size pixel_size: Int,
  normal_edge_strength normal_edge_strength: Float,
  depth_edge_strength depth_edge_strength: Float,
) -> Pass

Creates a pixelation effect with edge detection.

Edge detection adds outlines based on surface normals and depth changes.

Example

camera.pixelate_with_edges(
  pixel_size: 4,
  normal_edge_strength: 1.0,
  depth_edge_strength: 0.5,
)
pub fn render_pass() -> Pass

Create a render pass.

This pass renders your 3D scene to the render target. It should typically be one of the first passes in your pipeline (after ClearPass if you need background rendering).

Example

camera.postprocessing_new()
|> camera.postprocessing_add_pass(camera.render_pass())
|> camera.postprocessing_add_pass(camera.bloom(...))
pub fn vignette(
  darkness darkness: Float,
  offset offset: Float,
) -> Pass

Creates a vignette effect.

Darkens the edges of the screen, focusing attention on the center.

Example

// Subtle vignette
camera.vignette(darkness: 0.5, offset: 1.0)

// Dramatic vignette
camera.vignette(darkness: 1.5, offset: 0.8)
Search Document