Низкая частота кадров с событиями в R3F + просмотр DREI

Вопрос или проблема

У меня есть проблема с основными react-three-fiber событиями, в частности в контексте Drei Views (хотя это может быть связано или не связано, и я надеюсь, что просто пропустил простой шаг реализации).

Полные детали в конце.

Конкретная проблема в следующем: если я добавляю любую event функцию к моей модели, я сразу же получаю падение частоты кадров. Когда я говорю “любую”, я имею в виду, что это работает нормально:

    return (
        <points ref={ref}>
            <bufferGeometry
                width={1}
                height={1}
                widthSegments={1}
                heightSegments={1}
            >
                {/* bufferAttribues */}
            </bufferGeometry>
            <shaderMaterial
                // аргументы материала
            />
        </points>
     )

но если я изменю это на <points ref={ref} onClick={() => {}}>, то просто пролистывание (т.е. без нажатия) снижает частоту кадров с 60 до где-то между 30 и 50. Конечно, я хочу сделать что-то полезное, а не просто чтобы событие было пустым — я пришел к этому из-за своих попыток отладить, потому что мой оригинальный код был

    const shaderRef = useRef();

    const pointerMoveHandler = ((e) => {
        shaderRef.current.uniforms.uMouse.value = e.point;
    })

    return (
        <points ref={ref} onPointerMove={pointerMoveHandler}>
            <bufferGeometry
                width={1}
                height={1}
                widthSegments={1}
                heightSegments={1}
            >
                {/* bufferAttribues */}
            </bufferGeometry>
            <shaderMaterial
                ref={shaderRef}
                uniforms={{
                        uMouse: { value: new Vector3(0, 0, 0) },
                        // другие униформы
                    }}
                // другие аргументы материала
            />
        </points>
     )

где если я перемещал мышь над моделью, частота кадров падала до однозначных значений! Сумасшествие в том, что если я включаю OrbitControls, я могу увеличивать и уменьшать масштаб, и вращать вещи, пока не надоест, и всё это работает плавно при 60 FPS всё это время.

Что я пробовал до сих пор:

  • убедиться, что OrbitControls отключены, на случай, если черт его знает, кто захватывает события.
  • подтвердить, что модель не перерисовывается много раз:
<points ref={ref}>
    {console.log('points rendered')}

(это не так; она рендерится только один раз)

    return (
      <Bvh>
          <points ref={ref} onPointerMove={pointerMoveHandler}>
          // ...
          </points>
      </Bvh>
    )
  • Принудительно установить последнюю версию BVH (Drei в настоящее время на одну версию назад и есть предупреждение о депрецировании, кроме того, похоже, есть некоторые улучшения, так что … почему бы и нет?)
// package.json
"overrides": {
  "@react-three/drei": {
    "three-mesh-bvh": "~0.8.2"
  }
}

Что я делаю не так?

Основываясь на всех приведенных выше попытках, я сейчас пришёл к выводу, что что-то не так с тем, как Drei views обрабатывают распространение событий. Вот упрощенная версия всего стека (сокращённая, чтобы устранить use client директивы и dynamic импорты для работы в NextJS и т.д…):

<html>
  <body>
    <div>
      <main>
        <div ref={localRef} />
        <r3f.In>
          <View track={localRef}> // Drei view
            <Points /> // проблемный код выше
            <PerspectiveCamera />
            {orbit && <OrbitControls />}
            <Stats />
          </View>
        </r3f.In>
      </main>
      <div
        ref={ref}
        touchAction={'auto'}
      >
        <Canvas
          pointerEvents={'none'}
          eventSource={ref}
          eventPrefix={'client'}
        >
          <r3f.Out />
          <Preload all />
        </Canvas>
      </div>
    </div>
  </body>
</html>

Не строго минимальный, но я разместил codesandbox. Любопытно, что там не так плохо, когда я открываю предварительный просмотр в новой вкладке, как это при локальном запуске … но это всё равно довольно плохо 🙂

P.S., мой личный план, пока я надеюсь и жду ответа здесь, — это заглянуть в исходный код OrbitControls и попытаться найти что-то там.

Ответ или решение

Данный вопрос касается проблемы снижения частоты кадров в приложении, использующем библиотеки react-three-fiber и @react-three/drei, особенно в контексте обработки событий. Это распространенная проблема, с которой сталкиваются разработчики, использующие WebGL и связанные с ним инструменты, и задача заключается в оптимизации кода для обеспечения стабильной производительности.

Описание проблемы

Пользователь сообщал, что добавление обработчиков событий непосредственно к элементам react-three-fiber (например, компонентам points) вызывает падение частоты кадров. Ниже приводятся ключевые моменты:

  1. Падение FPS при добавлении событий: Частота кадров падает с 60 до 30-50 FPS, когда добавляется любой обработчик событий.
  2. Снижение FPS при перемещении мыши: Специфически, код, обрабатывающий события движения мыши, приводит к падению частоты кадров до однозначных значений.
  3. Без событий FPS высокая: Без обработчиков событий производительность остается на стабильном уровне.

Возможные причины и решения

1. Процесс обработки событий

События в WebGL и react-three-fiber могут затрагивать сами рендеры сцены, особенно когда обработчики выполняют сложные вычисления или изменения состояния. В случае вашего обработчика pointerMoveHandler, возможно, имеются неоптимизированные операции. Чтобы проверить это, можно использовать console.time() и console.timeEnd() для оценки времени выполнения обработчика.

2. Использование useRef и изменение состояния

Поскольку вы используете ссылку для изменения значений в шейдере (shaderRef), убедитесь, что изменение значений не вызывает ненужных перерисовок. Если это возможно, постарайтесь оптимизировать обработчик, чтобы он не вызывал обновления состояния более одного раза за событие:

const pointerMoveHandler = React.useCallback((e) => {
  if (shaderRef.current) {
    shaderRef.current.uniforms.uMouse.value = e.point;
  }
}, []);

Использование React.useCallback помогает избежать ненужных пересозданий обработчика.

3. Сокращение числа обновлений

Рассмотрите возможность использования requestAnimationFrame или установки частоты обновления обработчика, чтобы ограничить количество вызовов в секунду, например, обновление позиции курсора только раз в 16.67 мс (60 FPS):

let lastMouseMove = 0;
const pointerMoveHandler = (e) => {
  const now = performance.now();
  if (now - lastMouseMove > 16) {
    shaderRef.current.uniforms.uMouse.value = e.point;
    lastMouseMove = now;
  }
};

4. Упрощение геометрии

Попробуйте оптимизировать используемую геометрию, чтобы уменьшить нагрузку на GPU. Убедитесь, что нет неиспользуемых или избыточных вершин и что ваша геометрия действительно необходима.

5. Проверка конфликтов с OrbitControls

Несмотря на то, что вы уже отключили OrbitControls, если вы решите их использовать, убедитесь, что обработчики события не конфликтуют с ним. Возможно, использование pointerEvents с правильным значением поможет избежать проблем.

Заключение

Для решения вашей проблемы с производительностью важно тщательно анализировать и оптимизировать каждую часть вашего приложения. Проведение тестирования, используя инструменты для мониторинга производительности, такие как Chrome DevTools, может оказаться полезным для анализа узких мест. Оптимизация обработчиков событий и сокращение числа вызовов, а также использование React хуков могут значительно улучшить производительность вашего проекта.

Оцените материал
Добавить комментарий

Капча загружается...