Процедурная графика и шум

Многие природные текстуры не рисуют вручную, а вычисляют формулами — на основе плавного псевдослучайного шума.

Процедурная графика — генерация изображений и текстур алгоритмом, а не готовым файлом; основа — функции шума вроде шума Перлина.

Зачем это знать

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

Белый шум против гладкого

Обычный случайный (белый) шум — это «телевизионная рябь»: каждое значение независимо, картинка дёрганая. Природа выглядит иначе: соседние точки похожи. Гладкий шум получают интерполяцией между случайными значениями в узлах сетки.

import random

random.seed(42)
white = [round(random.random(), 2) for _ in range(8)]
print("Белый шум: ", white)

# гладкий: случайные значения в узлах + линейная интерполяция между ними
def smooth(values, between=2):
    out = []
    for i in range(len(values) - 1):
        a, b = values[i], values[i+1]
        for k in range(between):
            t = k / between
            out.append(round(a + (b - a) * t, 2))
    return out

nodes = [0.2, 0.8, 0.3, 0.9]
print("Узлы:      ", nodes)
print("Гладкий:   ", smooth(nodes, 3))

Вывод:

Белый шум:  [0.64, 0.03, 0.28, 0.22, 0.74, 0.68, 0.89, 0.09]
Узлы:       [0.2, 0.8, 0.3, 0.9]
Гладкий:    [0.2, 0.4, 0.6, 0.8, 0.63, 0.47, 0.3, 0.5, 0.7]

Белый шум прыгает хаотично, а гладкий плавно перетекает между узлами — он и похож на природу.

Октавы: фрактальный шум

Один слой шума — это крупные пятна. Природа же многомасштабна: крупные холмы, на них кочки, на них рябь. Складывая несколько слоёв (октав) шума с уменьшающейся амплитудой и растущей частотой, получают богатую фрактальную текстуру (fBm).

import math

def fbm(x, octaves=4):
    total = 0.0
    amp = 1.0
    freq = 1.0
    for _ in range(octaves):
        # псевдо-шум через синус (для демонстрации формы)
        total += amp * (0.5 + 0.5 * math.sin(freq * x))
        amp *= 0.5     # амплитуда падает
        freq *= 2.0    # частота растёт
    return round(total, 3)

for x in [0.0, 1.0, 2.0, 3.0]:
    print(f"x={x}: fbm={fbm(x)}")

Вывод:

x=0.0: fbm=0.938
x=1.0: fbm=1.553
x=2.0: fbm=1.309
x=3.0: fbm=0.815

Складывая слои разной частоты и убывающей амплитуды, получаем неровную, но органичную кривую — основу ландшафтов и облаков.

Как работает под капотом

Шум Перлина (и более новый simplex noise) в узлах сетки хранит не значения, а случайные градиенты, и интерполирует их сглаживающей функцией — оттого он плавный и без видимой сетки. В шейдерах шум считают прямо во фрагментном шейдере по координате, получая анимированные воду и огонь без единой текстуры. Анимацию дают, прибавляя время к координате шума.

Частые ошибки

  • Использовать белый шум там, где нужен гладкий — узор выйдет «грязным».
  • Линейная интерполяция вместо сглаживающей — видны artefacты сетки (резкие «грани»).
  • Брать слишком много октав — дорого и почти незаметно после 4–6 слоёв.

Итоги

  • Процедурная графика генерирует текстуры формулой, а не файлом.
  • Гладкий шум (Перлина) плавно интерполирует случайность — похож на природу.
  • Сложение октав (fBm) даёт многомасштабный фрактальный узор.
  • Шум в шейдере + время = анимированные вода, огонь, облака без текстур.
Проверьте себя
1. Чем гладкий шум Перлина отличается от белого шума?
AОн чёрно-белый
BСоседние значения плавно связаны, без резких скачков
CОн быстрее считается
DОн всегда одинаковый
2. Что даёт сложение нескольких октав шума (fBm)?
AЧистый цвет
BМногомасштабный фрактальный узор (крупные и мелкие детали вместе)
CУдаление шума
DТени
3. Как процедурный шум обычно анимируют (вода, огонь)?
AМеняют разрешение
BПрибавляют время к координате шума каждый кадр
CОтключают z-buffer
DУвеличивают FOV