Rust Ray Tracing: рефакторинг, архитектура и параллелизм
Продолжил работу над Ray Tracer на Rust. Результаты первой части описывал ранее.
По итогам второй части туториала сделал рендер картинки за час на 16 ядрах:
Благодаря третьей части туториала качество рендера улучшилось. Добавил оптимизацию параллелизма и рендер картинки выше сократилось до 25 минут. Изменения качества особенно заметно в Корнеллской коробке на малом количестве лучей (все параметры рендера идентичны):
Итог третьей части с стеклянными и зеркальными поверхностями:
Запомнившиеся моменты
- Рефакторинг ссылок. Избавился от лишнего
BoxвArcдля текстур и материалов, чтобы удобно делиться ими между потоками. - Трейты Send/Sync. Перенёс
Send + Syncв сами трейты, а не раскидывал их через generic-параметры. - Унифицированный конструктор через трейты. Ввел трейт
IntoSharedMaterial, позволяющий писать единый конструктор вместо дублированияfrom_shared_texture,from_texture:
pub trait IntoSharedMaterial {
fn into_arc(self) -> Arc<dyn Material>;
}
impl IntoSharedMaterial for Arc<dyn Material> {
fn into_arc(self) -> Arc<dyn Material> {
self
}
}
impl<T: Material + 'static> IntoSharedMaterial for T {
fn into_arc(self) -> Arc<dyn Material> {
Arc::new(self)
}
}
- Трансформации объектов. Создал трейт
Transformableдля поворота и перемещения объектов через преобразование типов. Аналогично сделаны итераторы,map,filterи т.д.
pub struct RotateY<T: Hit> { // Все объекты характеризуются трейтом Hit в проекте
object: T,
}
impl<T: Hit> Hit for RotateY<T> { ... }
pub trait Transformable: Hit + Sized {
fn rotate_y(self, angle: f32) -> RotateY<Self> {
RotateY::new(self, angle)
}
fn translate(self, offset: Coords) -> Translate<Self> {
Translate::new(self, offset)
}
}
impl<T: Hit> Transformable for T {}
// В вызывающем коде: object.rotate_y(45.).translate(2.)
- Option оператор
?. Упростил обработку опциональных значений. - Сборка объектов через
FromIterator. Реализовал конструктор с помощью трейтаFromIteratorиfold. - Функциональный стиль. Использовал итераторы,
map,collectиflat_mapдля лаконичного и декларативного кода. - Параллелизм и каналы. Организовал worker pool с использованием каналов для снижения времени рендера.
- Многопоточные генераторы чисел. Распихал по проекту
thread_local!для генераторов случайных чисел. - Ссылки без лишних аллокаций. Применял
Arcи&'a dynтам, где это возможно. Без трейтаcloneв проекте не обошлось, к сожалению.
Не туториалом единым
Рекомендую глянуть статью Rust в режиме «жесть», где Алексей описывает код рейтресера с минимизацией взаимодействия с std.