В прошлой части мы представили схему перегрузки операторов в JS, которая похожа на использование JSX вместо React.createElement в React. Наш код можно оптимизировать, чтобы сделать его более кратким и интуитивно понятным.
Применение методов оптимизации React в трассировке лучей для Интернета (часть 1)
Однако это всего лишь синтаксический сахар, который не решает проблем с производительностью. Огромная вычислительная нагрузка алгоритма трассировки лучей требует других методов оптимизации. Сегодня мы поговорим об этих техниках.
Решение: разделение времени
Разбивка по времени, или асинхронный рендеринг, или параллельный режим, как бы это ни называлось, грубо описывает разделение долго выполняющейся задачи на мелкие части.Высвобождаются другие задачи (например, рендеринг), отложенные в потоке.
React и Vue продемонстрировали этот эффект. В нашем сценарии эту идею тоже можно использовать, и нет необходимости сначала реализовывать фреймворк параллелизма. Использование функции async/await и генератора может легко удовлетворить потребности.
Давайте сначала посмотрим, как разделить вычислительные задачи трассировки лучей на блоки.
Идея алгоритма трассировки лучей состоит в том, чтобы попиксельно вычислять цвет этой позиции пикселя. Итак, два цикла for, i = 0ширина плюс j = 0высота, каждый набор i, j соответствует положению пикселя.
Для каждого пикселя мы выпускаем 100 лучей из глаза (точки обзора), имитируя обратный путь пути света от объекта к глазу, чтобы сэмплировать источник света. Расчет каждого пикселя показан на следующем рисунке:
Каждый раз, когда свет, используемый для отбора проб, попадает на объект, он будет отражаться, преломляться, рассеиваться и т. д. в соответствии с материальными характеристиками самого объекта. Это эквивалентно использованию точки удара в качестве источника света и повторному излучению света в другие места. При столкновении свет постепенно теряет энергию (полностью теряет энергию, кажется черным; такие как тени, тени обычно находятся под углом включения предметов, свет сталкивается с высокой частотой в узких местах, каждый удар частично поглощается, частично отражается, частично Преломление, многократные попадания, он полностью поглощается).
Учитывая, что основная цель этой серии статей — взять в качестве примера трассировку лучей, поговорить о стратегиях оптимизации рендеринга. Поэтому более подробное описание алгоритма трассировки лучей выходит за рамки, заинтересованные студенты могут поискать материалы для чтения, такие как «Трассировка лучей в выходные».
После того, как мы внедрили алгоритм трассировки лучей, если мы хотим запустить его в браузере, нам нужно использовать меры оптимизации, представленные в этой серии статей.
А пока предположим, что мы реализовали алгоритм трассировки лучей. Его код JS выражается, как показано на следующем рисунке:
Первые два слоя циклов for используются для определения положения точек пикселей, третий слой циклов for определяет количество испускаемых выборочных лучей, добавьте небольшое случайное возмущение Math.random(), чтобы точки выборки излучаются в разных точках небольшого квадрата, можно обнаружить более широкую среду.
Функция color(ray, world) рекурсивно вычисляет отношение столкновения между лучами и различными объектами в мире и получает значение цвета.
Наконец, мы накапливаем значения цвета, полученные путем выборки лучей, а затем берем среднее значение. фактический цвет пикселя. Поскольку все это нормализуется до 0 во время вычисленийдиапазон значений 1, поэтому в конце масштаб до 0255 в диапазоне RGB. До этого я извлекал квадратный корень, это простая симуляция гамма-коррекции, здесь это не важно и не указано.
На данный момент мы знаем, как наш алгоритм трассировки лучей справляется с задачами. Как его можно разрезать на части?
На первом этапе значения цвета накапливаются и усредняются. Вместо того, чтобы запускать луч много раз за один проход, мы можем запустить его только один раз. Вы можете получить грубое изображение. Сохраните значения пикселей этого изображения. Затем инициируйте еще один обход, и каждый пиксель снова излучает свет, чтобы получить еще одно грубое изображение.После того, как два изображения сложены и усреднены, получается более подробное изображение.
Мы исключили третий уровень цикла for. Вместо этого, многократно выполняя функцию для получения нескольких элементов и комбинируя их, мы, наконец, можем получить такое же детальное изображение трассировки лучей.
Мы сделали. Но, как ни странно, разбиение подробного изображения на несколько грубых изображений не решает проблему полностью. Просто превратите десять минут в десять секунд. Для веб-страницы по-прежнему недопустимо тратить более десяти секунд.
Как еще мы можем разделить задачи? Останавливается ли он каждые n подсчитанных пикселей? Оно делает. Но как мы напишем код, двухслойный цикл for может очень хорошо выполнять позиционирование по ширине и высоте по оси xy. Мы не хотим навязывать требования к шардингу запутанным кодом.
Хороший способ — изменить нашу функцию на функцию-генератор, которую можно остановить несколько раз с помощью ключевого слова yiled.
Теперь вместо того, чтобы собирать контент внутри функции, мы выдаем его группами по 4 и собираем снаружи.
Внешне массив данных используется для накопления значений цвета, а innerCount — для накопления времени рендеринга, что удобно для усреднения, длительность — для отслеживания времени выполнения. , 0)) Освободите место для основного потока пользовательского интерфейса.
Таким образом, просто полагаясь на функцию генератора и async/await, мы реализовали простое разделение времени. Хотя время отрисовки картинки по-прежнему 10 секунд (в компе или мобильнике с плохой производительностью), хоть интерфейс можно двигать, и хоть дом можно непрерывно рендерить, чтобы считать секунды и время.
Расширенное решение: потоковый рендеринг
До сих пор мы разделяли изображение высокой четкости на суперпозицию нескольких размытых изображений. Генерация размытого изображения делится на несколько сегментов по 100 мс, чтобы другие задачи рендеринга (например, DOM) в основном потоке пользовательского интерфейса могли выполняться. Интерфейс больше не зависает.
Наша трассировка лучей доступна в браузере, но это не предел наших возможностей. Мы также можем пойти еще дальше и заставить изображения появляться быстрее.
Напомним, что при выполнении SSR с React, если отображаемый HTML слишком сложен, вам нужно дождаться завершения всего содержимого, прежде чем отправлять его в браузер. Пользователь всегда будет видеть белый экран. Как мы оптимизировали в то время?
Мы будем использовать renderToNodeStreaming для рендеринга в Node.js Stream и позволять браузеру получать строки HTML одну за другой для достижения прогрессивного рендеринга. В нашей сцене с трассировкой лучей эта стратегия оптимизации работает так же хорошо.
Потому что даже если все пиксели изображения не собраны, оно все равно доступно для рендеринга (рендеринга), а другие части можно оставить прозрачными (нам даже не нужно с этим разбираться, структура данных, сгенерированная cxt.createImageData, прозрачна по умолчанию).
Как показано выше, по сравнению с бессмысленной задержкой каждые 100 мс возможное отставание задач в потоке пользовательского интерфейса может быть освобождено; на этот раз мы напрямую инициируем рендеринг requestAnimationFrame и добавляем отрендеренное изображение холста в основной поток пользовательского интерфейса. Как только все пиксели собраны, мы делаем дополнительный общий рендер.
Таким образом реализован потоковый рендеринг, и нам не нужно ждать 10 секунд, чтобы увидеть полное изображение в первый раз. Мы можем видеть часть изображения в первую секунду. Как показано ниже, часть изображения видна уже через 1,6 секунды.
Нам удалось исправить первый рендер, но мы можем сделать лучше. В дополнение к первому рендеру мы также можем добавить некоторые оптимизации на этапе обновления.
Например, большая часть содержимого многих изображений — это простые фоны, а объектов всего несколько, и вычислять их поровну для нас — пустая трата времени. Мы должны направить драгоценные вычислительные ресурсы на более важные объекты, особенно на те, которые находятся в центре поля зрения. Расставьте приоритеты для ясности.
В следующий раз я расскажу, как взять на вооружение идею предстоящей стратегии приоритета расписания React и улучшить нашу трассировку лучей.