Module nsi::output

source ·
Available on crate feature output only.
Expand description

Output driver callbacks.

This module declares several closure types. These can be passed via Callbacks to an OutputDriver node to stream pixels during and/or after a render, in-memory.

There are three types of closure:

As a user you can choose how to use this API.

  • To get a single buffer of pixel data when rendering is finished it is enough to implement an FnFinish closure.

  • To get the pixel buffer updated while the renderer is working implement an FnWrite closure.

The format of the Vec<f32> buffer is described by the PixelFormat parameter which is passed to both of these closures.

Example

// Setup a screen.
ctx.create("screen", nsi::SCREEN, None);
// We pretend we defined a camera node earlier.
ctx.connect("screen", None, "camera", "screens", None);
ctx.set_attribute(
    "screen",
    &[
        // The resolution becomes the width & height passed to
        // FnOpen, FnWrite and FnFinish type callbacks.
        nsi::integers!("resolution", &[1920, 1080]).array_len(2),
    ],
);

// Setup an RGBA output layer.
ctx.create("beauty", nsi::OUTPUT_LAYER, None);
ctx.set_attribute(
    "beauty",
    &[
        // The Ci variable comes from Open Shading Language.
        nsi::string!("variablename", "Ci"),
        // We want the data as raw, 32 bit float.
        nsi::string!("scalarformat", "float"),
        nsi::integer!("withalpha", 1),
    ],
);
ctx.connect("beauty", None, "screen", "outputlayers", None);

// Setup an output driver.
ctx.create("driver", nsi::OUTPUT_DRIVER, None);
ctx.connect("driver", None, "beauty", "outputdrivers", None);

// Our FnFinish callback. We will be called once.
let finish = nsi::output::FinishCallback::new(
    |// Passed from the output driver node, below.
     image_filename: String,
     // Passed from the screen node, above.
     width: usize,
     // Passed from the screen node, above.
     height: usize,
     pixel_format: nsi::output::PixelFormat,
     pixel_data: Vec<f32>| {
        // Call some function to write our image to an OpenEXR file.
        write_exr(
            (String::from(image_filename) + ".exr").as_str(),
            width,
            height,
            pixel_format.len(),
            &pixel_data,
        );
        nsi::output::Error::None
    },
);

ctx.set_attribute(
    "driver",
    &[
        // Important: FERRIS is the built-in output driver
        // that understands the closure parameters.
        nsi::string!("drivername", nsi::output::FERRIS),
        // This will end up in the `name` parameter passed
        // to finish().
        nsi::string!("imagefilename", "render"),
        nsi::callback!("callback.finish", finish),
    ],
);

ctx.render_control(nsi::Action::Start, None);

// The finish() closure will be called once, before the next call returns.
ctx.render_control(nsi::Action::Wait, None);

Color Profiles

The pixel color data that the renderer generates is linear and scene-referred. I.e. relative to whatever units you used to describe illuminants in your scene.

Using the "colorprofile" attribute of an OutputLayer you can ask the renderer to apply an Open Color IO (OCIO) profile/LUT before quantizing (see below).

Once OCIO has a Rust wrapper you can easily choose to do these color conversions yourself. In the meantime there is the colorspace crate which has some useful profiles built in, e.g. ACEScg.

ctx.create("beauty", nsi::OUTPUT_LAYER, None);
ctx.set_attribute(
    "beauty",
    &[
        // The Ci variable comes from Open Shading Language.
        nsi::string!("variablename", "Ci"),
        // We want the pixel data 'display-referred' in sRGB and quantized down to 0.0..255.0.
        nsi::string!("colorprofile", "/home/luts/linear_to_sRGB.spi1d"),
        nsi::string!("scalarformat", "uint8"),
    ],
);

Quantization

Using the "scalarformat" attribute of an OutputLayer you can ask the renderer to quantize data down to a suitable range. For example, setting this to "uint16" will get you valid u16 values from 0.0..65535.0, but stored in the f32s of the pixel_data buffer. The value of 1.0 will map to 65535.0 and everything above will be clipped. You can convert such a value straight via f32 as u16.

Unless you asked the renderer to also apply some color profile (see above) the data is linear. To look good on a screen it needs to be made display-referred.

See the output example on how to do this with a simple, display-referred sRGB curve.

Modules

Structs

Enums

  • An error type the callbacks return to communicate with the renderer.
  • The depth (number and type of channels) a pixel in a Layer is composed of.

Statics

  • This is the name of the crate’s built-in output driver that understands the “closure.*” attributes.

Traits