Вода, которая отражает небо

Отражение в спокойной воде — самая дешёвая и самая эффектная часть пейзажа. Нужна одна строка индексов и немного шума.

Фьорд: два тёмных склона сходятся к горизонту и почти без искажений отражаются в воде
Фьорд, seed 82. Склоны отражаются почти без искажений: рябь слабая, вода тёмная.

Берём линию горизонта и зеркалим всё, что выше неё. Строка, которая лежит на расстоянии d под горизонтом, берёт пиксели из строки на расстоянии d над ним. В NumPy это одна операция индексирования, без циклов.

rows = np.arange(hy, H)
src = np.clip(2 * hy - rows, 0, hy - 1)
water = img[src]

Идеальное зеркало выглядит фальшиво. Настоящая вода размывает отражение тем сильнее, чем ближе она к зрителю. Поэтому каждую строку сдвигаем вбок на величину из шума, и амплитуда сдвига растёт к нижнему краю кадра: у горизонта 6 пикселей, внизу около 60.

rip = fbm(H - hy, W, base_y=30, base_x=3) - 0.5
dist = (rows - hy)[:, None] / (H - hy)
shift = (rip * (6 + 60 * dist)).astype(int)

Шум для ряби вытянут по горизонтали: по вертикали тридцать узлов сетки, по горизонтали три. Так волны получаются длинными и плоскими, как на озере в безветрие. Если поменять пропорцию, вода станет похожа на рифлёное стекло.

Последний шаг — цвет. Отражение всегда темнее неба, поэтому смешиваем его с цветом глубины, и к переднему плану доля этого цвета растёт. На закате добавляется дорожка бликов под солнцем: тот же шум, но с порогом и только в узкой полосе под диском.

Закат над заливом: солнце у горизонта, по тёмной воде идёт дорожка бликов
Закат над заливом, seed 3. Блики — это шум с порогом 0,62, умноженный на гауссову полосу под солнцем.

Чем спокойнее вода, тем меньше нужен шум. На Ладоге рябь почти нулевая, и вся работа ложится на дымку у горизонта: отражение леса сливается с ним самим, и линия берега пропадает.

Ладога: полоса леса на горизонте и её отражение в светлой неподвижной воде
Ладога, seed 41. Почти без ряби и с плотной дымкой у горизонта.