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
.