Behind the Sim #1 — електромагнітні хвилі: як ми впровадили рівняння Максвелла в браузер

Behind the Sim — серія про історію окремих симуляцій: фізичні рішення, глухі кути, дизайнерські рішення й моменти, коли все раптово запрацювало. Перший випуск: EM Waves — розв'язувач методом скінченних різниць у часовій області в реальному часі для рівнянь Максвелла, що працює з інтерактивною частотою кадрів у звичайній вкладці браузера.

Відправна точка: що таке електромагнітна хвиля?

Джеймс Клерк Максвелл об'єднав електрику й магнетизм у 1865 році чотирма рівняннями. Ключове розуміння для цієї симуляції — хвильове рівняння, що з них випливає: змінне електричне поле індукує магнітне поле, і навпаки. Два поля переслідують одне одного крізь простір зі швидкістю світла.

У 2D ми працюємо з поперечно-магнітним (TM) режимом: електричне поле має лише z-компоненту Ez, а магнітне поле має x- і y-компоненти Hx та Hy. Це зводить рівняння ротора Максвелла до трьох рівнянь оновлення — достатньо простих для обчислення на кожній комірці сітки щокадру.

FDTD: ґратка Йі

Метод скінченних різниць у часовій області (FDTD), запроваджений Кейном Йі в 1966 році, дискретизує рівняння Максвелла на зміщеній просторово-часовій сітці. Компоненти електричного й магнітного полів зміщені на половину просторового й часового кроку, що автоматично задовольняє закон Гаусса (∇·B = 0) без жодного кроку корекції.

// Рівняння оновлення FDTD режиму TM (2D, рівномірна сітка)
// E і H чергуються: спочатку оновлюється H, потім E

// Оновити Hx і Hy на кроці часу n+½
Hx[i][j] -= (Cz / dy) * (Ez[i][j+1] - Ez[i][j])
Hy[i][j] += (Cz / dx) * (Ez[i+1][j] - Ez[i][j])

// Оновити Ez на кроці часу n+1
Ez[i][j] += Cz * (
    (Hy[i][j] - Hy[i-1][j]) / dx -
    (Hx[i][j] - Hx[i][j-1]) / dy
)

// Cz = dt / (ε₀ · μ₀) — число Куранта

Умова стабільності Куранта вимагає dt ≤ dx / (c · √2) для рівномірної 2D сітки. Ми встановлюємо dt = 0.9 · dx / (c · √2) — 90 % від межі стабільності для невеликого запасу безпеки. Із сіткою 200×200 і dx = 0.01 м крок часу становить близько 21 пс на оновлення.

Хронологія побудови

День 1 — голий FDTD на звичайному canvas

Почав із масиву JavaScript 100×100, чистий CPU, одне точкове джерело. Хвиля поширювалася правильно — концентричні кільця, що розходяться назовні. Але вона ідеально відбивалася від країв, тож весь простір заповнився стоячими хвилями за секунди. Явно потрібні поглинальні межі.

День 2 — ідеально узгоджені шари (PML)

PML — стандартна поглинальна межа FDTD: втратна «губчаста» область навколо домену, де поля затухають без відбиття. Її реалізація подвоїла складність коду, але майже ідеально усунула відбиття. Домен тепер здавався «відкритим» — хвилі виходять чисто.

День 3 — перехід на WebGL

Реалізація на CPU працювала на ~30 FPS на сітці 100×100. Розширення до 200×200 (мінімальна сітка для корисної візуалізації) знизило це до 8 FPS. Рівняння оновлення FDTD бентежно паралельні — кожну комірку можна оновлювати незалежно. Перемістив цикл оновлення у фрагментний шейдер WebGL, що записує в пару текстур із плаваючою комою (схема «пінг-понг»). Стрибнув до 60 FPS на 400×400.

День 4 — рендеринг і джерела

Відобразив поле Ez на синьо-червону колірну шкалу (від від'ємного до додатного), додав синусоїдальне точкове джерело, модульоване гаусовою обвідною, щоб уникнути початкового «клацання», і реалізував апертуру подвійної щілини для класично відомого візерунка дифракції. Додав дипольне джерело, джерело плоскої хвилі та рухому діелектричну область, яку користувач може розмістити на полотні.

День 5 — накладання векторного поля

Накладання Hx/Hy як стрілок за допомогою інстансованих лінійних сегментів у WebGL. Стрілки підвибираються до сітки 20×20, масштабуються за величиною поля та повертаються за напрямком поля. Поєднання колірної карти Ez та стрілок поля H дає відчутне розуміння того, як два поля перебувають у квадратурі.

Найважча частина: текстури з плаваючою комою

WebGL1 не підтримує читання з текстури з плаваючою комою, у яку ви щойно записали — розширення «render to float texture» (OES_texture_float) існує, але не є універсально доступним для запису. Рішення — буфер «пінг-понг»: два фреймбуфери, що обмінюються ролями щокадру. Шейдер читає з буфера A і пише в буфер B; наступного кадру він читає з B і пише в A.

На iOS Safari розширення взагалі не було доступне, що вимагало запасного варіанту з 16-бітними напівплаваючими текстурами зі зменшеним динамічним діапазоном. Значення поля нормалізуються перед зберіганням і масштабуються назад при читанні — невелика втрата точності, але непомітна за колірної роздільної здатності дисплея.

Текстура діелектричної проникності

Щоб дозволити користувачам розміщувати матеріали (скло, метал тощо) у домені, коефіцієнти оновлення FDTD повинні змінюватися просторово. Ми запікаємо проникність εr у окрему текстуру R8 і вибираємо її під час виконання шейдера оновлення. Натискання кнопки на панелі інструментів встановлює «режим пензля» — подальші події миші малюють значення проникності в текстуру матеріалу через невеликий вторинний фреймбуфер.

// У GLSL-шейдері оновлення — читання текстури матеріалу
float eps_r = texture2D(u_material, vTexCoord).r * 10.0 + 1.0;
// Діапазон: [1.0, 11.0] — від повітря до склоподібного діелектрика

float Cz_local = dt / (eps_r * eps0 * mu0 * dx * dx);
// Та сама формула оновлення, але Cz змінюється залежно від матеріалу

Використання в класі: що працює, а що ні

Ми отримали відгуки від кількох вчителів фізики після запуску симуляції. Функції, що виявилися найціннішими в класі:

Що не спрацювало: початковий інструмент вільного малювання діелектрика заплутував учнів, оскільки «малювання скла» виглядало точнісінько як «малювання повітря», доки хвиля не вдаряла. Ми додали постійне колірне накладання для розміщених матеріалів (бурштиновий відтінок = діелектрик, сірий = провідник), що дуже допомогло.

Спробуйте самі: відкрийте симуляцію EM Waves, натисніть пресет «Double Slit» і перетягніть повзунок відстані між щілинами. Ви побачите, як візерунок дифракції змінюється в реальному часі — та сама фізика, що й в експерименті Томаса Юнга 1801 року, що працює у вашому браузері.

Наступні кроки для симуляції

2D FDTD режиму TM — потужний навчальний інструмент, але є природні розширення, які я хочу побудувати: