Рейтрейсинг против растеризации
Растеризация спрашивает «куда попадёт треугольник», а трассировка лучей — «что увидит луч из камеры».
Рейтрейсинг (трассировка лучей) — метод рендеринга, при котором из камеры через каждый пиксель пускают луч и ищут, с чем он пересечётся, прослеживая отражения и тени.
Зачем это знать
Два подхода решают одну задачу — нарисовать сцену — но с противоположных сторон. Растеризация проецирует геометрию на экран (быстро, основа игр). Рейтрейсинг моделирует физику света (точные отражения, преломления, мягкие тени, но дорого). Современные движки их сочетают, поэтому полезно понимать оба.
Две противоположные стратегии
| Растеризация | Рейтрейсинг | |
| Вопрос | куда попадёт треугольник? | что увидит луч? |
| Направление | геометрия → экран | экран → геометрия |
| Отражения | трюки (карты, экраны) | естественны |
| Скорость | очень быстро | дорого |
Растеризация: Рейтрейсинг:
треугольник камера
| проецируем | луч
v на пиксели v пускаем в сцену
[экран] [сцена] -> что пересёк луч?
Пересечение луча с объектом
Сердце рейтрейсинга — найти, где луч встречает поверхность. Покажем простейший случай: пересечение луча со сферой. Луч идёт из точки origin в направлении dir; решаем, попал ли он в сферу.
import math
def ray_sphere(origin, dir, center, radius):
# вектор от камеры к центру сферы
oc = tuple(o - c for o, c in zip(origin, center))
a = sum(d*d for d in dir)
b = 2 * sum(o*d for o, d in zip(oc, dir))
c = sum(o*o for o in oc) - radius*radius
disc = b*b - 4*a*c
if disc < 0:
return None # промах
t = (-b - math.sqrt(disc)) / (2*a) # ближайшее пересечение
return round(t, 3)
cam = (0, 0, 0)
forward = (0, 0, -1)
print("Луч в сферу z=-5:", ray_sphere(cam, forward, (0, 0, -5), 1))
print("Луч мимо (сдвиг по y):", ray_sphere(cam, (0, 1, 0), (0, 0, -5), 1))
Вывод:
Луч в сферу z=-5: 4.0 Луч мимо (сдвиг по y): None
Луч вперёд попал в сферу на расстоянии 4.0 (до ближней стенки сферы радиуса 1 на z=-5); луч вверх прошёл мимо. На таких пересечениях строится вся трассировка.
Почему рейтрейсинг дорог
Один первичный луч на пиксель — это только начало. Для отражений и теней из точки пересечения пускают вторичные лучи, из них — ещё, и каждый проверяют против всей геометрии. Число лучей и проверок взрывается.
pixels = 1920 * 1080
rays_per_pixel = 1 + 2 + 4 # первичный + тени + отражения (грубо)
total = pixels * rays_per_pixel
print("Пикселей:", pixels)
print("Лучей всего:", total)
print("И каждый луч проверяется против тысяч треугольников...")
Вывод:
Пикселей: 2073600 Лучей всего: 14515200 И каждый луч проверяется против тысяч треугольников...
Как работает под капотом
Чтобы не проверять каждый луч против каждого треугольника, сцену укладывают в ускоряющую структуру (BVH — иерархия ограничивающих объёмов): луч быстро отсекает целые группы геометрии. Современные GPU имеют аппаратные блоки RT-ядра для пересечений. На практике делают гибрид: основная картинка растеризуется, а рейтрейсингом добавляют отражения, тени и глобальное освещение. Шум от малого числа лучей убирают денойзером.
Частые ошибки
- Считать рейтрейсинг «всегда лучше» — для большинства задач растеризация быстрее и достаточна.
- Забыть про ускоряющую структуру (BVH) — наивный перебор немыслимо медленный.
- Ждать чистой картинки от малого числа лучей — без денойзера будет шум.
Итоги
- Растеризация: геометрия → экран, быстро; рейтрейсинг: луч из камеры → сцена, физично.
- Ядро рейтрейсинга — пересечение луча с поверхностью.
- Дороговизна — от взрыва числа вторичных лучей и проверок.
- На практике — гибрид: растеризация + рейтрейс-эффекты, ускорение через BVH и денойзинг.