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
-
RenderPassRender 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 colorSome(color): Uses the specified hex color
-
OutputPassOutput 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.
-
FXAAPassFXAA 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
-
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)