Вопрос или проблема
У меня есть проблема с основными 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
) вызывает падение частоты кадров. Ниже приводятся ключевые моменты:
- Падение FPS при добавлении событий: Частота кадров падает с 60 до 30-50 FPS, когда добавляется любой обработчик событий.
- Снижение FPS при перемещении мыши: Специфически, код, обрабатывающий события движения мыши, приводит к падению частоты кадров до однозначных значений.
- Без событий 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
хуков могут значительно улучшить производительность вашего проекта.