Содержание

WebGL2Fundamentals.org

Fix, Fork, Contribute

WebGL2 - Анимация

Этот пост является продолжением серии постов о WebGL. Первый начался с основ. и предыдущий был о 3D камерах. Если вы не читали их, пожалуйста, просмотрите их сначала.

Как мы анимируем что-то в WebGL?

На самом деле это не специфично для WebGL, но вообще, если вы хотите анимировать что-то в JavaScript, вам нужно изменить что-то со временем и нарисовать снова.

Мы можем взять один из наших предыдущих примеров и анимировать его следующим образом.

*var fieldOfViewRadians = degToRad(60);
*var rotationSpeed = 1.2;

*requestAnimationFrame(drawScene);

// Рисуем сцену.
function drawScene() {
*  // Каждый кадр увеличиваем вращение немного.
*  rotation[1] += rotationSpeed / 60.0;

  ...
*  // Вызываем drawScene снова в следующем кадре
*  requestAnimationFrame(drawScene);
}

И вот это

Есть тонкая проблема, однако. Код выше имеет rotationSpeed / 60.0. Мы разделили на 60.0, потому что предположили, что браузер будет отвечать на requestAnimationFrame 60 раз в секунду, что довольно распространено.

Это на самом деле не валидное предположение, однако. Может быть, пользователь на маломощном устройстве, как старый смартфон. Или может быть, пользователь запускает какую-то тяжелую программу в фоне. Есть все виды причин, по которым браузер может не отображать кадры со скоростью 60 кадров в секунду. Может быть, это 2020 год, и все машины работают на 240 кадрах в секунду сейчас. Может быть, пользователь - геймер и имеет CRT монитор, работающий на 90 кадрах в секунду.

Вы можете увидеть проблему в этом примере

В примере выше мы хотим вращать все ‘F’ с одинаковой скоростью. ‘F’ в середине работает на полной скорости и не зависит от частоты кадров. Тот, что слева и справа, симулируют, если бы браузер работал только на 1/8 максимальной скорости для текущей машины. Тот, что слева, НЕ зависит от частоты кадров. Тот, что справа, ЗАВИСИТ от частоты кадров.

Обратите внимание, что поскольку тот, что слева, не учитывает, что частота кадров может быть медленной, он не поспевает. Тот, что справа, однако, даже though он работает на 1/8 частоты кадров, он поспевает за тем, что в середине, работающим на полной скорости.

Способ сделать анимацию независимой от частоты кадров - это вычислить, сколько времени потребовалось между кадрами, и использовать это для вычисления, сколько анимировать в этом кадре.

Сначала нам нужно получить время. К счастью, requestAnimationFrame передает нам время с момента загрузки страницы, когда он вызывает нас.

Я нахожу это легче всего, если мы получаем время в секундах, но поскольку requestAnimationFrame передает нам время в миллисекундах (тысячных долях секунды), нам нужно умножить на 0.001, чтобы получить секунды.

Итак, мы можем затем вычислить дельта-время так

*var then = 0;

requestAnimationFrame(drawScene);

// Рисуем сцену.
*function drawScene(now) {
*  // Преобразуем время в секунды
*  now *= 0.001;
*  // Вычитаем предыдущее время из текущего времени
*  var deltaTime = now - then;
*  // Запоминаем текущее время для следующего кадра.
*  then = now;

   ...

Как только у нас есть deltaTime в секундах, тогда все наши вычисления могут быть в том, сколько единиц в секунду мы хотим, чтобы что-то происходило. В этом случае rotationSpeed равен 1.2, что означает, что мы хотим вращать 1.2 радиана в секунду. Это примерно 1/5 оборота, или другими словами, потребуется около 5 секунд, чтобы обернуться полностью, независимо от частоты кадров.

*    rotation[1] += rotationSpeed * deltaTime;

Вот это работает.

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

Далее как применять текстуры.

Не используйте setInterval или setTimeout!

Если вы программировали анимацию в JavaScript в прошлом, вы могли использовать либо setInterval, либо setTimeout, чтобы ваша функция рисования вызывалась.

Проблемы с использованием setInterval или setTimeout для анимации двукратны. Во-первых, и setInterval, и setTimeout не имеют отношения к тому, что браузер что-то отображает. Они не синхронизированы с тем, когда браузер собирается нарисовать новый кадр, и поэтому могут быть не синхронизированы с машиной пользователя. Если вы используете setInterval или setTimeout и предполагаете 60 кадров в секунду, а машина пользователя на самом деле работает на какой-то другой частоте кадров, вы будете не синхронизированы с их машиной.

Другая проблема в том, что браузер не имеет представления, почему вы используете setInterval или setTimeout. Так, например, даже когда ваша страница не видна, как когда она не является передней вкладкой, браузер все еще должен выполнять ваш код. Может быть, вы используете setTimeout или setInterval для проверки новой почты или твитов. Нет способа для браузера знать. Это нормально, если вы просто проверяете каждые несколько секунд новые сообщения, но это не нормально, если вы пытаетесь нарисовать 1000 объектов в WebGL. Вы будете эффективно DOS'ить машину пользователя своей невидимой вкладкой, рисуя вещи, которые они даже не могут видеть.

requestAnimationFrame решает обе эти проблемы. Он вызывает вас как раз в правильное время, чтобы синхронизировать вашу анимацию с экраном, и он также вызывает вас только если ваша вкладка видна.

Есть предложения или замечания? Создайте issue на GitHub.
comments powered by Disqus