3 Film
Imagine you’re taking a photo with your phone. Light from your environment come into the camera, across a bunch of lens, and finally hit the sensor - that’s what we’re going to do in this chapter, where the rendering result of our renderer will be stored!
3.1 Film Abstraction
The film is where we’ll store the intermidiate state when rendering, and it will be converted to the final image to be saved as output.
So the film should has the same size as the output we expect. There should also be a method to convert it into the result image. And by that way, we should be allowed to access each pixel by a 2D index, as well as updating the pixels by its index.
An important part of this film abstraction is the ability to be splitted into a lot of tiles, and we can then process and render into the film tile by tile, concurrently. To simplify, we will constraint that the tile size should fit the film evenly, otherwise you could just throw remainging pixels away, or do whatever you choose to do. We should also avoid copying tile data everywhere when we need to process the film in tiled-based way, so a view-based tile representation is also needed to avoid too much memory allocation and copy.
Add a figure here to show how the film could be tiled.
3.2 Implementation Choice
3.2.1 2D Array - ndarray
We’ll leave the implmentation to others, and use the ndarray
crate for this film struct. There’re mainly two way to create a new type based on another type:
- Wrapper Pattern
- Type Alias
The one I choose here is type alias. In this way, we can use methods provided by the ndarray
crate naturally without the ugly .0
infix, or spend much time on delegating methods to the inner type. However, there’re also a lot problems with this approach. First the type inference rule won’t use this alias for the type. Second, it’s a problem that people can pass a wrong instance with the same type to our methods - we lost a great benefit of type-driven design! See Exercise 3.4.1.
3.2.2 Output Image
3.3 Implementation
Just like how we’ve implemented additional methods for the Color
type, we can do the same thing for this Film
type. We’ve also given an alias name to the Color
type for convienience:
// color.rs
pub type Color = palette::LinSrgb<f32>;
// film.rs
pub type Film = Array2<Color>;
pub type OutputImage = ImageBuffer<Rgb<u8>, Vec<u8>>;
pub trait FilmExt {
fn to_output(&self) -> OutputImage;
}
impl FilmExt for Film {
fn to_output(&self) -> OutputImage {
1 OutputImage::from_fn(
self.dim().0 as u32,
self.dim().1 as u32,
|x, y| self[(x as usize, y as usize)].to_output_color(),
)
}
}
- 1
-
from_fn
is a method provided by theimage
crate, which can be used to create an image from a function. Here’s we’re copying data from the array to the image, pixel by pixel. You can also tryfrom_par_fn
, since we’ll userayon
for parallel rendering, and theimage
crate has this method to improve this performance.
Write a unit test for it like what we’ve done in the last chapter. A tip: use a simple film, like
3.4 Questions, Challenges & Future Possibilities
3.4.1 Wrapper Pattern for Film Implementation
As we’ve mentioned previously, the type alias approach leaves some problems. Try to re-implement it using the wrapper pattern. Provide some beatiful methods to access the methods originally provided by ndarray
.
Wrapper pattern is widely used in Rust
community. Think about is there any convient way to delegate methods to the inner type? Is there tools helping you to eliminate the ugly .0
or the boilerplate? Try to search on crates.io
or make your own one!