Готовый перевод Ogre3D Basic Tutorials / Базовые туториалы Ogre3D: Basic Tutorial 4: Frame Listeners and Unbuffered Input

Внимание! Этот перевод, возможно, ещё не готов, так как модераторы установили для него статус «перевод редактируется»

ВВЕДЕНИЕ В РУКОВОДСТВО

В этом руководстве мы познакомимся с одной из наиболее полезных конструкций Ogre - FrameListener.

К концу этого руководство, вы поймёте объекты FrameListener, как их использовать там, где требуется обновление в каждом кадре, и как использовать небуферизованный ввод системы OIS

ДЛЯ НАЧАЛА

Вот код, с которого мы начнем:

Мы переписали функцию frameRenderingQueued и определили новую приватную функцию processUnbufferedInput.

Давайте проведем остаток туториала, заставляя их делать что-нибудь интересное.

КАДРОВЫЕ КОНТРОЛЛЕРЫ

ВВЕДЕНИЕ

В Ogre мы можем зарегистрировать класс который получает уведомления перед тем и после того как содержимое сцены будет отрендерено на экране.
Этот класс называется FrameListener

Интерфейс FrameListener определяет три функции которые могут использоваться для того чтобы получать уведомления о событиях:

frameStarted: вызывается прямо перед тем, как кадр будет отрендерен.

frameRenderingQueued: Вызывается после того, как все части рендринга получат свои команды, но до того, как окно рендеринга запросит сброс их буферов.

frameEnded: Вызывается сразу после того, как произошел рендеринг кадра.

Цикл продолжается до тех пор, пока любой FrameListener возвратит ложное значение из frameStarted, frameRenderingQueued or frameEnded.

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

Если вернется значение false, то программа закроется.

Объект FrameEvent содержит две переменные, но только в FrameListener полезна переменная timeSinceLastFrame.

Переменная отслеживает сколько времени заняло, с тех пор как события frameStarted или frameEnded в последний раз выводились.

Отметим, что в методе frameStarted, FrameEvent::timeSinceLastFrame будет содержать количество времени с последнего воспроизведения кадра в событии frameStarted (не событие frameEnded).

Одним из важных принципов, который нужно реализовать, касающийся кадровых контроллеров из Ogre состоит в том, что порядок, в котором они вызываются полностью зависит от Ogre.

Вы не можете определить какой кадровый контроллер будет вызываться первым, вторым, третьим... и так далее.

Если вам нужно убедиться, что кадровые контроллеры вызываются в правильном порядке, тогда вам нужно зарегистровать только один контроллер и сделать так, чтобы он вызывал все объекты в определенном порядке.

Итак, какой же из трех методов FrameListener нужно выбрать?

Конечно это зависит от того, что вам нужно сделать, но если вы только хотите обновлять объекты каждый кадр, то разместите его в событие frameRenderingQueued, потому, что это событие вызывается прямо перед обработкой в GPU и становится занятой, при сбросе буферов рендеринга.

Или, если процитировать API документы:

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

Для завершения это может занять некоторое время, и когда это происходит CPU может сделать полезные вещи.

Как только случится зарос на "сброс буферов", поток запрашивающий это будет заблокирован, до тех пор пока GPU не будет готов, что может заставить процессор(CPU) работать впустую.

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

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

Для обновления каждого кадра используйте функцию frameRenderingQueued, если вы хотите производительности.

РЕГИСТРАЦИЯ КАДРОВОГО КОНТРОЛЛЕРА

Наш класс BasicTutorial4 уже является FrameListener - сюрприз.

Это происходит благодаря BaseApplication, который наследует FrameListener:

BaseApplication содержит функцию frameRenderingQueued, мы не использовали эту функцию в предыдущем туториале.

На самом деле, мы так же не использовали функцию createFrameListener.

Ну и теперь мы объясним, что эта функция все таки делает.

Для того, чтобы сделать полностью функциональный FrameListener, вам нужно зарегистрировать его в Ogre::Root.

Вам нужно сделать это из-за того, что Ogre::Root должен знать какой FrameListener вызывать, когда происходит кадровое событие.

Для добавления или удаления кадрового контроллера мы можем использовать две функции: Ogre::Root::addFrameListener и Ogre::Root::removeFrameListener.

Метод addFrameListener добавляет FrameListener, а метод removeFrameListener удаляет FrameListener (то есть FrameListener больше не будет получать обновлений)

Обратите внимание, что методы add|removeFrameListener берут только указатели на FrameListener (то есть FrameListeners не имеют имен, которые вы можете использовать, чтобы их убрать).

BaseApplication использует следующий код в createFrameListener для регистрации самого себя с Ogre::Root как кадрового контроллера:

После того, как это будет сделано, мы сможем получать кадровое собитий из Ogre::Root, с помощью функций FrameListener frameStarted, frameRenderingQueued и frameEnded.

ИТАК, КАК ЭТО РАБОТАЕТ?

Давайте заглянем в Ogre::Root::renderOneFrame:

Здесь вы можете видеть, что Ogre::Root, когда осуществляет рендеринг кадра, создает событие FrameStarted до обновления всех объектов рендеринга.

А затем выводит событие FrameEnded, когда завершено обновление.

Чтобы увидеть где Ogre::Root выводит событие FrameRenderingQueued, мы взглянем в отрывок из Ogre::Root::_updateAllRenderTargets:

Там вы можете увидеть, что он создает событие FrameRenderingQueued после обновления частей рендринга, но до того, как поменяет местами буферы объектов рендеринга.

Пока, это все, что вам нужно знать о внутренней работе кадровых контроллеров.

Перед тем как продолжить, убедитесь, что можете скомпилировать приложение.

УСТАНОВКА СЦЕНЫ

ВВЕДЕНИЕ

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

Мы поместим на сцену один объект (ниндзя) и один точечный источник света.

При нажатии на левую кнопку мыши, свет будет включаться и выключаться.

Вы сможете двигать Node(к которому привязан ниндзя) используя клавиши IKJL: "I" двигает вперед, "K" назад, "J" влево, правый shift + "J" поворачивают влево, "K" двигает вправо, правый shift + "K" поворачивают вправо.

Клавиши "U" и "O" будут двигают узел с ниндзей вверх и вниз.

КОД

Найдите метод BasicTutorial4::createScene. Первое, с чего мы начнем - сделаем окружающий свет очень слабым.

Мы бы хотели, чтобы объекты все еще были видны, когда свет выключен, но мы также хотим, чтобы включение/выключение света было заметным.

Теперь добавим объект ниндзя на нашу сцену в начало координат:

Ну а сейчас создадим белый источник света и поместим его на небольшое(относительно) расстояние от ниндзя.

С функцией createScene мы закончили.

Давайте займемся функцией frameRenderingQueued.

КАДРОВЫЙ КОНТРОЛЛЕР

Нам нужно добавить код в нашу контроллер кадров функцию frameRenderingQueued:

Программа просто вызовет функции BaseApplication::frameRenderingQueued и (еще не записанную) processUnbufferedInput, а потом вернется в цикл рендеринга.

Для того, чтобы продолжить рендеринг, метод frameStarted должен вернуть положительное булево значение(true).

Если как-нибудь возвратится ложное значение(false), то Ogre завершит цикл рендринга и приложение закроется.

Давайте запишем что-нибудь в функцию processUnbufferedInput.

ОБРАБОТКА ВХОДНЫХ ДАННЫХ

ПЕРЕМЕННЫЕ

Мы определили несколько переменных в классе TutorialFrameListener.

Давайте пройдемся по ним, до того как продолжить.

mRotate и mMove - это константы вращения и движения, соответственно.

Если вы хотите увеличить(уменьшить) скорость передвижения или вращения, измените значение переменных в соответствующую сторону.

Две другие переменные(mToggle и mMouseDown) контролируют входные данные.

Мы будем использовать "небуферизованные" входные данные мыши и клавиатуры (буферизованный входные данные мы рассмотрим в следующем уроке)

Это означает, что мы будем вызывать методы во время работы нашего кадрового контроллера, чтобы запросить состояние клавиатуры и мыши.

Мы столкнулись с интересной проблемой, когда пытались использовать клавиатуру для изменения состояния какого-либо объекта на экране.

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

Видим ли мы, что та же клавиша нажата и выполняется то же действие?

В некоторых случаях(таких как передвижение с помощью arrow keys(клавиши стрелки)), это то, что мы хочем сделать.

Однако, давайте скажем, что мы хотим, чтобы клавиша "T" переключала свет из положения включено в выключено и обратно.

В первом кадре клавиша T нажимается, свет переключается, а в следующим кадре кадре клавиша T все еще нажата, поэтому свет переключается снова... и снова, и снова, пока клавишу не отпустят.

Что бы избежать этой проблемы, нам нужно отслеживать состояние клавиши между кадрами.

Для решения этой проблемы, мы представим два отдельных метода.

Переменная mMouseDown отслеживает была ли нажата мышь в предыдущем кадре (итак, если mMouseDown принимает значение true,то пока кнопка мыши не будет отпущена, мы не будем выполнять те же действия).

Переменная mToggle определяет время, до конца которого мы не можем выполнять действие снова.

То есть, когда кнопка нажата, mToggle задается определенное количество времени, до истечения которого никакого другого действия выполнить нельзя.

Статичные локальные переменные в основном для удобства.

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

Открытая система ввода (OIS) принимает ввод от трех классов: клавиатура, мышь и джойстик.

В этом туториале мы изучим только как манипулировать мышью и клавиатурой.

Если вы заинтересованны в использование джойстика(или геймпада) с помощью Ogre, можете заглянуть в класс Joystick.

Прежде чем продолжить, давайте взглянем на BaseApplication::frameRenderingQueued.

Текущее состояние клавиатуры и мыши должно быть известно каждый фрейм. Этого можно добиться с помощью метода, получающего состояние объектов Mouse и Keyboard.

Это происходит, когда эти две линии находятся в BaseApplication::frameRenderingQueued:

Не добавляйте это в нашу функцию!

Первое, что мы хотим сделать - это переключение света нажатием кнопки мыши.

Мы можем узнать нажата ли кнопка мыши, вызвав метод getMouseButton с нужной нам кнопкой в параметре.

Обычно "0" это левая кнопка мыши, "1" правая, и "2" средняя.

В некоторых системах клавиша "1" - это средняя кнопка, а "2" правая.

Попробуйте разные конфигурации, если кнопки мыши работают не так, как ожидалось.

Добавьте это в функцию BasicTutorial4::processUnbufferedInput:

Переменная currMouse будет возвращать значение "true", если кнопка мыши нажата.

Теперь мы будем переключать свет в независимости от того, возвращает ли currMouse значение true. Но мы будем брать во внимание, была ли кнопка нажата в предыдущем фрейме (мы ведь хотим переключить свет один раз за одно нажатие).

Заметим также, что именно метод setVisible класса Light определяет будет ли объект излучать свет.

Теперь нам нужно установить значение переменной mMouseDown независимой от того, что содержит переменная currMouse.

В следующем фрейме мы заранее узнаем, была ли кнопка мыши нажата.

Скомпилируйте и запустите приложение.

Теперь свет включается и выключается при нажатии на левую кнопку мыши.

Так как мы вызвали метод frameRenderingQueued, мы до сих пор можем двигать камеру используя клавиши WASD.

Такой способ хранения предыдущее состояние кнопки мыши работает хорошо, поскольку мы знаем, что мышь уже была задействована.

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

Обойти это можно следующим способом. Для этого нужно отслеживать время с момента, когда кнопка была нажата в последний раз и позволять совершать только одно действие по прошествии времени.
Мы отслеживаем это время в переменной mToggle .

Если mToggle больше 0, то мы не выполняем какие-либо действия. Если mToggle меньше 0, тогда мы можем выполнить действие.

Мы будем использовать этот метод для двух клавиш.

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

Теперь, когда мы определились с переменной mToggle, мы можем использовать ее.

По сути mToggle это пол секундная задержка, до конца которой никакие изменения не происходят.

На практике эта задержка дольше, но так нам и нужно.

Давайте добавим альтернативный способ переключения света:

Скомпилируйте и запустите приложение. Теперь мы можем включать и выключать свет, нажимая на клавишу "1".

Следующее, что нам нужно сделать, это реализовать перемещение нода с привязанному к нему ниндзя, путем нажатия клавиш IJKL.

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

Это делает наш код в разы проще.

Для начала давайте создадим Vector3 и определимся, куда мы будем двигать объект:

Теперь мы хотим, чтобы при зажатой клавише "I" объект двигался вперед (а именно по отрицательной оси z. Помните, отрицательная ось z уходит прямо в экран?) :

Делаем почти тоже самое для клавиши "K", но движение будет происходить по положительной части оси z:

Движения влево и вправо происходит по оси x (положительная и отрицательная части). Поворот влево/вправо (при зажатом shift) так же происходит по оси x:

Ну а в движение вверх/вниз, регулируемое клавишами "U" и "O", будет задействована ось y:

Теперь в нашей переменной transVector находится перемещение, которое мы хотели бы применить на ноде камеры.

Первая ловушка с которой мы можем столкнуться, это то, что если вы поворачиваете SceneNode, то наши x, y и z координаты при перемещении будут другие.

Чтобы исправить это нам нужно применить все повороты, которые мы сделали со SceneNode, на наш нод для движения.

На деле это проще, чем звучит.

Для вращения Ogre не использует матрицы преобразования, как некоторое графические движки.

Вместо этого, для всех операций связанных с поворотом и вращениями, он использует Кватернионы .

В математике Кватернионы имеют отношение к четырехмерной линейной алгебре. Сложно правда?

К счастью вам не нужно понимать устройство Кватернионов для работы с ними.

Проще говоря, чтобы использовать Кватернионы, для поворота вектора, все что вам нужно сделать, это перемножить две части.

В таком случае, нам нужно применить все вращения, совершенные со SceneNode, на вектор перемещения.

Мы можем получить Кватернионы, представляющие эти вращения, вызовом SceneNode::getOrientation(), потом мы уже можем применить их к перемещаемому ноду (умножить).

Вторая ловушка, которую нужно остерегаться, это то, что нам нужно уменьшить величину нашего передвижения на величину времени прошедшего с последнего фрейма.

Иначе скорость перемещения будет зависеть от частоты кадров в секунду.

Определенно не то, чего мы хотели.

Вот вызов функции, который нам нужен для перемещения нода с камерой без возникновения данных проблем:

Сейчас мы узнали кое-что новое.

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

Обычно, когда вы двигаете объект, вам не нужно указывать этот параметр.

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

Когда мы нажимаем клавишу "I" (для движения вперед), мы вычитаем из направления z, то есть движемся в направлении отрицательной оси z.

Если бы в предыдущей строке кода мы не прописали пространство TS_LOCAL, то тогда бы мы двигали ниндзя по отрицательной оси z.

Однако, поскольку мы пытаемся заставить ниндзя двигаться вперед при нажатии на клавишу "I", нам нужно двигать его по направлению, к которому нод повернут лицом.

Таким образом, нам нужно использовать "локальное" пространство преобразования.

Существует и другой способ реализации этого (хотя и менее простой).

Чтобы получить тот же результат, мы могли бы взять направление нода и Кватерниона и умножить их на направление вектора.

Это было бы вполне допустимо:

Так же это перемещает нод ниндзя в локальном пространстве.

В нашем случае, нет никаких причин делать так.

Ogre поддерживает три пространства преобразования: TS_LOCAL, TS_PARENT и TS_WORLD.

Может случиться так, что вам нужно будет осуществить перемещение или вращение в другом векторном пространстве.

В таком случае это можно попытаться реализовать, как в предыдущем коде.

Возьмите кватернион представляющий векторное пространство (или положение любого объекта, который вы хотите использовать) и умножьте его на вектор перемещения, чтобы получить правильный вектор перемещения, ну и потом переместите объект в пространство TS_WORLD.

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

Скомпилируйте программу и опробуйте ее.

Данный туториал не является полным руководством по вращению и Кватернионам (здесь достаточна материала для усвоения).

В следующем туториале мы будем использовать буферизованный способ ввода, вместо проверки состояния кнопки каждый фрейм.

Внимание! Этот перевод, возможно, ещё не готов.
Его статус: перевод редактируется

Переведено на Нотабеноиде
http://notabenoid.org/book/19615/64009

Переводчики: proVIDec, wuffur, alexx_x

Настройки

Готово:

100.00% КП = 1.3