Готовый перевод Adobe Flash 11 Stage3D (Molehill) game programming (Chapter 6 - 10) / Программирование игр в Adobe Flash 11 Stage3D (Molehill) (Глава 6 - 10): Текстуры: Делаем вещи привлекательными

Ваш игровой движок сейчас может похвастаться шейдерами, мешами , движением, GUI и всей инициализацией, требуемой для быстрого рендеринга Stage3D. Самое время для перехода на следующий уровень и эксперимента с продвинутыми текстурными эффектами.
Пока всё идёт хорошо. Мы достигли многого в нашем квесте по созданию 3D видеоигры во Flash. Теперь, позаботившись об основах, мы можем сосредоточиться на вещах, которые действительно выглядят хорошо. От блестящих эффектов, таких как металлическое оружие, до эффектов прозрачности, таким как пар, дым или взрывы, Stage3D API может рендерить текстуры всех видов, различными способами.
В этой главе мы начнём изучать, как обрабатывать bitmap-данные, как преобразовывать текстуры в специализированные форматы, которые полезны для эффектов, а также существенно эффективнее на ограниченных аппаратных средствах. В этой главе мы изучим :

Использование множества текстур и нескольких мешей;
Рендеринг текстур со специальными режимами перехода прозрачности;
Рендеринг спецэффектов, таких как взрывы.
Меняются ли задние грани меша, которые отсекаются;
Учёт z-буфера при рендеринге;
Обнаружение нажатий клавиш, для пользовательского контроля действий.

Время для проекта: создаём «настоящую» игру.
Мы двигаемся дальше, от простой технической демки к настоящей игре. Теперь, когда мы отважились погрузиться в глубины Molehil, это хороший момент для вас начать мозговой штурм какого вида игру вы хотите сделать с помощью Stage3D API.
У вас, вероятно, осталось что-нибудь в памяти с самого начала, но если нет, сейчас, чтобы иметь свет в конце туннеля, вы также можете придумать план.

Важная задача данного этапа – сделать вашу первую 3D игру настолько простой, насколько это возможно. Любой разработчик-ветеран знает, что даже вещи, которые кажутся простыми, могут оказаться гораздо более сложными, чем ожидалось. Помня об этом, выберете жанр игры, который, как вам кажется, потребует минимум работы; что-нибудь в аркадном стиле, упрошённое и лёгкое в создании. Ваши будущие игры могут с каждым разом расти в размерах и сложности, так что пока начнём с визуализации наиболее базовой игры, которую вы захотите сделать.

На этом этапе успех определяется просто завершением вашей игры, которую вы сможете показать вашим друзьям. Будет наивно пытаться написать многопользовательскую ролевую онлайн игру (MMORPG), например. Однажды вы достигнете этапа, имея завершённую, играбельную 3D игру за поясом и сможете быстро и просто перейти к улучшенным играм.
Простой с виду пример игры, который будет создан в оставшейся части этой книги, это аркадный шутер. Он был вдохновлён “Galaxian 1942”, “R-Type”, “Giga Wing”, “Ikaruga”, «Тигр Хели Рейдн», «Геометрические Войны» и даже «Космические захватчики». Многие люди называют эту разновидность игр играми «bullet hell».

Аркада "перестреляй их всех" (SHMUP - shoot-em-up) идеально подходит для наших будущих экспериментов. Кроме того для неё не требуется сложных технических решений, таких как физический движок, продвинутый искусственный интеллект или работа по сети. Зато для подобных игр характерно много действия, куча взрывов, и изобилие приятных глазу вещей.
Начиная с текущего момента, в процессе создания учебного проекта Stage3D, который мы будем обновлять от главы к главе, выберите текстуры и сетки (полигональные сетки), которые вы хотите поместить в свою игру.
Использование текстур в Stage3D
Самым простым способом использования текстуры является следующий:
1. Добавить изображение в SFW
2. Создать в коде объект Texture
3. Загрузить текстуру в оперативную видео память (VRAM)
4. Сгенерировать миникарты и так же их загрузить
5. Создать программу вершин на AGAL, которая использует координаты текстуры
6. Создать программу фрагментов на AGAL, которая использует саму текстуру
7. Научить Flash использовать эту текстуру при рендринге

Для того, чтобы поместить изображение во Flash вы будете либо используете flex-команду [embed], либо добавите его в вашу библиотеку в Flash IDE. Затем вы создаёте объект Texture путём создания экземпляра нового класса этого типа функцией context3D.createTexture().
Для того, чтобы отправить bitmap-данные изображения в вашу видеократу для использования во врем я рендеринга, вы должны загрузить его и, желательно, создать mip-уровни (его маленькие версии, используемые для перспективной коррекции) и загружаете их тоже, используя вашу функцию Texture.uploadFromBitmapData().

Теперь вы должны скомпилировать AGAL программы, которые используют эти данные. В вашей фрагментной программе вы используете команду подобную “tex ft0, v1, fs0<2d,linear, repeat,miplinear>” чтобы сделать выборку текстуры для достижения надлежащего цвета.
Наконец, в вашем цикле рендеринга вы используете функцию context3D.setTextureAt(), чтобы выбрать эту текстуру первой в рендеринге ваших мешей функцией context3D.drawTriangles().

Кратность двойке.
В предыдущих главах мы дали толкование использования текстур и дали как можно более простое объяснение как это делается. Также мы использовали только отдельные текстуры в нашем демо, и они всегда были размером 512 х 512. Почему мы выбрали этот странно звучащий размер для нашего изображения?
Хотя не все видеокарты требуют, чтобы текстуры были не больше определённого размера, математика, участвующая при рендеринге текстур в 3D, работает быстрее, когда вы используете текстуры определённых размеров. На некоторых машинах текстуры даже не будут работать, если они не в определённом диапазоне размеров. Поэтому необходимо, чтобы все текстуры, которые вы используете в вашей игре, имели размер кратный двойке.

Что это значит? Это значит, что текстуры должны иметь ширину и высоту, которая укладывается в эти значения: 2, 4, 16, 32, 64, 128, 256, 512, 1024 и т.д.
Кроме того, во многих случаях ваши текстуры лучше должны быть квадратными (т.е. высота должна быть равна ширине), чем прямоугольными. Иногда вы можете выбрать текстуру и 64 х 256, но в основном, вы вынуждены использовать квадратные, кратные степени двух текстуры, такие как изображения размером 256 х 256 пикселей.
Чем больше ваши текстуры, тем больше видеопамяти они будут занимать, и на определённом железе они, соответственно, будут рендериться немного медленнее. Поэтому всегда будет мудрым выбором использовать текстуры, которые будут большими ровно настолько, чтобы выглядеть хорошо.

Тогда, создавая, например, игральные кости, если вы знаете, что они не будут занимать весь экран, будет очень глупо использовать гигантские 1024х1024 текстуры для них. Крошечные объекты в вашем мире, такие как эти, будут выглядеть также точно с размером текстур 64х64 или 128х128. Важные объекты и текстуры, используемые для стен и пола, которые можно рассматривать с близкого расстояния, идеальные кандидаты для больших текстур; 256х256 или 512х512 являются нормой.
Не будет ошибкой использовать малое количество по-настоящему больших текстур, но как и все вещи в программировании игр, это будет компактнее и быстрее запускаться. Если каждая текстура, используемая вашей игрой, 1024х1024, ваш SWF-файл потребует действительно много времени на загрузку и может даже не загрузить текстуры в RAM.

Поэтому, создавая текстуры, работайте в высоком разрешении (4096х4096), но затем сохраняйте их как 128х128 для использования в вашей игре. Во время тестирования, если вы обнаружили, что требуемая текстура выглядит размыто, попробуйте 26х256 или 512х512. Так как источник изображения сохранён в формате высокой чёткости, у вас есть дополнительные пиксели для работы и тогда вы измените размер ваших картинок так, чтобы конечное изображение не было размыто.
Всегда благоразумно работать в высоком разрешении , но экспортировать в низкое разрешение и повышать точность только когда вы заметили, что это необходимо. Это поможет сохранить ваши файлы крошечными и запускаться вашей игре сглаженной.

Распространенная ошибка – включать текстуры больше, чем требуется: разумеется, вы хотите, чтобы ваша игра, выглядела так хорошо, как только возможно. Поэтому, хорошая метрика, которая поможет вам избежать этого, это оценить максимальное количество реального места вашего внутриигрового экрана, которое ваш меш будет использовать. Если требуемая текстура предназначена, чтобы покрыть крошечный внутриигровой объект, такой как скрепка или выключатель, тогда никогда не нужно брать более чем 50 пикселей на покрытие, нет причин использовать текстуру, которая имеет размер 1024х1024; 64х64 в этом примере хватит.

UV-координаты.
Как вы знаете, текстуры – это картинки, которые оборачиваются вокруг 3D-меша подобно обёрточной бумаге подарка на день рождения. Как она оборачивается, контролируется UV-координатами, которые загружаются как вершинный буфер рядом с x, y, z-координатами для каждой вершины. UV-координаты для любой данной вершины определяются двумя числами (u и v) и обычно в диапазоне от 0 до 1. Координата U – горизонтальная ось в текстуре, а V представляет вертикальную ось. Поэтому нижняя часть текстуры имеет V равные 1 и правый нижний угол будет иметь U равное 1.

Например, при простом повороте квадратного меша, мы использовали в первых нескольких главах верхний левый угол квадрата (первую вершину), имевший UV-координату, определённую как 0, 0. Это значит, что пиксели, которые рисуются на экране вашим фрагментным шейдером, приходят из левого верхнего угла вашего текстурного изображения. Нижний правый угол этой битмап-картинки будет доступен при получении вершины с UV-координатами 1, 1.

Вы можете использовать числа выше, чем 1. Например, если вы хотите повторить текстуру 16 раз по горизонтали вдоль поверхности полигона, вы можете получить UV-координаты вершины (16, 1), так что в такой особенной вершине текстура наложится 16 раз в U и раз на оси V. Вы также можете использовать числа, которые меньше, чем 1. Например, если вы начнёте с 0.25, тогда вы пройдёте четверть пути вдоль текстуры. Это происходит во многих мешах, когда вы хотите поверхность только с небольшой частью общей текстуры.

Прозрачные текстуры.
Вы не должны использовать .jpg картинки в качестве вашего источника изображений. Вы можете использовать .png-файл, выполненный с различной прозрачностью. Это может быть действительно великолепно для таких эффектов как взрывы, искры и дым, а также прозрачных окон. Кроме того, вы можете симулировать сложную геометрию (таких как изгородь, решётка или листья и ветви дерева) , рисуя сложные формы на вашей текстуре, но используя простой четырёхугольник (квадратный полигон).

Предыдущая текстура полностью прозрачная в областях, не покрытых зелёным листком. Это .png-файл, который сохранён со включённой альфа-информацией (информацией о прозрачности). Вместо моделирования мегасложного дерева, где каждый лист включает сотни полигонов, вы можете симулировать сложную геометрию используя текстуру, такую как этот простой квадрат. Группы этих простых «спрайтов-рекламных щитов» могут быть использованы во множестве эффектов, давая впечатление сцены с миллионами полигонов, где на самом деле используется только несколько дюжин.

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

Для того, чтобы сделать это, вы должны будете написать короткую AGAL вершинную программу, которая утверждает вершинные константы, которые вы установите для каждого кадра. Перед выводом UV-координат вы должны будете использовать опкод ADD, чтобы сместить их в одном или обоих измерениях, так что каждый кадр часть текстуры, которая назначается вашей фрагментной программе берётся из различных частей вашей текстуры.
В этом примере с текущей водой, всё, что мы должны будем сделать – это добавить небольшой код в наш цикл рендеринга. Во-первых, мы увеличиваем переменную немного в каждом кадре и отправляем это значение в вершинную программу с помощью метода setProgramConstantsFromVector().
Например, предположим, что во время инициализации вы создали объект Vector со списком чисел, вызвавший uvOffsets, во время вашего цикла рендеринга, вы могли сделать что-нибудь подобное, прокручивая текстуру по горизонтали понемногу в каждом кадре.

Время для действий: Обновляем UV-координаты каждый кадр.
//увеличиваем первое число в нашем списке
//каждая фрагментная программа добавит в UV-координаты
uvOffsets[0] +=0.01;
//отправляем этот вектор в AGAL переменную vc4
context3D.setProgramConstantsFromVector (
Context3DProgramType.VERTEX,
4, uvOffsets, true);
Затем, в вашей вершинной программе AGAL вы добавите этот вектор к сохранённым UV-координатам, которые загружаются вместе с вашим мешем. Допустим, что они сохранены в AGAL регистре va1 и посылаются в вашей фрагментной программе в v1, мы сначала добавим значение, которое мы установили ранее (в vc4) к тем координатам по умолчанию, так что v1 теперь содержит новые координаты.
add v1, va1, vc4

Что же произойдет?
В каждом кадре, сразу перед тем как мы отрендерим наш меш, мы обновляем значение, которое было отправлено в Stage3D для использования в нашей нашей вершинной программе как vc4. Это значение добавляется к существующим UV-данным нашего меша, так что для каждого кадра U (горизонтальная) координата сдвигается немного в сторону. Эта техника может быть использована для сдвига текстур на постоянную величину, таких как когда прокручиваются текстуры подобные воде понемногу в каждом кадре или когда анимируются текстуры или используются карты текстур, чтобы выбрать тот «кадр» (или часть текстур) который мы хотим использовать.

Карты текстур.

Замечательная технология, чтобы увеличить вашу игровую частоту кадров и позволить большему числу мешей быть отренедеренными, используя сам шейдер это упаковывать различные текстуры в большой битмап, называемый текстурным атласом. Например, представьте, что вам нужно 16 различных видов драгоценных камней для сокровищ как игровых элементов в вашей игре. Вместо создания 16 крошечных текстур вы можете, используя Photoshop или любой другой графический редактор, упаковать их в одну большую текстуру и затем моделировать различные 3D-меши таким способом, когда разные модели используют разные UV-координаты в самой текстуре.
Это замечательно для частоты кадров, потому что вы можете группировать целые связки различных текстурных моделей в один гигантский вызов drawTriangles. Кроме того, есть небольшие накладные расходы каждый раз, когда вы сменяете текстуры или шейдеры во время рендеринга, и если вы можете избежать рендеринга большего числа мешей между вызовами context3D.setTextureAt(), вы можете сохранить время подобных результатов с лучшим fps.

Использование атласов текстур, определённо, лучшая практика для рендеринга шрифтов в вашем 3D мире. Почти каждая игра, которая нуждается в рендеринге букв в 3D (когда не выходит просто наложить на вашу Stage3D) используются текстурные атласы битмап-шрифтов.
Как вы можете видеть, предыдущие текстуры – это более эффективный способ текстурирования четырёхугольников для использования как текста внутри вашего 3D мира по сравнению с альтернативным, который создаёт и загружает сотню или более крошечных текстур, одна для каждой буквы. Так как медленнее переключить активные текстуры, чем использовать такую же текстуру для множества мешей, использование карт текстур, повысит производительность.

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

Одним способом сделать это будет просто загрузить серию текстур и пройтись циклом по ним, основываясь на времени прохождения. Например, в вашем цикле рендеринга вы могли сменять текстуры, отправляя context3D.setTextureAt() каждые несколько кадров. Это удобно для небольших анимаций, таких как зацикленная водная рябь, экранная статика или даже для простых взрывов.
Вы можете также создавать карты текстур, которые охватывают различные кадры видео, так что вся анимация сохранена на одной текстуре. Затем просто используйте технику UV-анимации выше, чтобы обновлять UV-координаты четырёхугольника с течением времени. В качестве примера возьмём эту текстуру:

Используя текстуру подобно этой, вы будете сначала создавать простой квадратный меш (четыре вершины квадрата) с UV-координатами, которые покрывают весь квадрат только первый кадр анимации. Затем, используя пример AGAL выше, мы просто отправляем новое смещение в вызов SetProgramConstantsFromVector(), чтобы сдвинуть текстурные координаты на следующий кадр. В примере картинки выше каждый кадр - 1/13 смещения от каждого другого. Так, кадр 1 будет использовать U 0.0 , затем каждый следующий кадр будет увеличивать U-координату на 0.077.

Манипуляция текстурными данными.
Для анимированных текстур, которые получаются из видео или даже Flash анимации, может потребоваться слишком много видеопамяти, чтобы сохранить каждый кадр досрочно. В этом случае вы должны будете загружать пиксели каждый кадр путём копирования битмап-данных, которые вы хотите для вашей текстуры, и повторно вызывая uploadFromBitmapData() всякий раз, когда кадр изменился.
На самом деле это плохо для FPS, особенно если вам нужно генерировать mip-уровни каждый кадр, так что лучше избежать этого, если только ваш проект абсолютно не нуждается в этом. Когда используется умеренно, эта технология открывает все разновидности возможностей для «динамических текстур».

Так как вы можете загружать ваши текстуры сразу, как только захотите, бесконечное множество крутых эффектов становится доступным для вас. Например, вы можете сгенерировать динамические текстуры используя функции шума Перлина Flash, фрактальные и битмап-фильтры. В AS3 коде у вас под рукой есть всё, что может генерировать битмап-данные, которые могут быть использованы как источник пикселей для вашей текстуры.
Вы можете не только копировать битмап-данные из каждого кадра FLV-видео, но также можете захватывать снимки с веб-камеры и помещать их в ваш 3D-мир! Возможности бесконечны.

Установки рендеринга.
Точно также текстуры могут быть установлены, чтобы рендериться различными способами, полигоны также следуют правилам по умолчанию, которые начинают нарушаться!Flash по умолчанию рендерит меши самым быстрым и наиболее эффективным из возможных способов. Это, как правило, то, что вам нужно, но, естественно, игра не будет классной, если она не содержит превосходных спецэффектов. Вы можете изменить «установки рендеринга» в Stage3D API, чтобы установить продвинутое ручное поведение, которое откроет бесконечное число возможностей для визуализации.

Отбор поверхностей.
Отбор поверхностей – это процесс в котором меши рендерятся только с одной стороны полигона, являющейся видимой. Это помогает ускорить визуализацию т.к., например, в кубе с непрозрачными сторонами вы никогда не сможете увидеть внутренние стенки. Когда отбор поверхностей включён, что является состоянием по умолчанию, эти внутренние поверхности (задние поверхности) никогда не рендерятся. Как правило, это работает великолепно, но есть случаи, когда вы хотите, чтобы обе стороны каждого полигона были отрисованы.

Время для действий: рендеринг задних поверхностей меша.
Например, если вы используете прозрачные текстуры на кубе, как в этом примере, вы, скорее всего, захотите увидеть внутренние стенки позади тех, что ближе к камере. Для того, чтобы сделать это, вы просто должны инструктировать Flash рендерить обе стороны меша следующим образом:
//не выбираем поверхности: рисуем обе стороны
//каждого полигона, включая задние стороны
Conext3D.setCulling(Contex3DTriangleFace.NONE);
Что же произойдёт?

Мы инструктируем Stage3d API остановить отбор (пропустить рисование) задних поверхностей, какой бы меш мы не рендерили следующим. Это значит, что «внутренние» стороны будут рисоваться вместе с теми, которые кажутся «внешними».
Различные параметры доступа для функции setCulling следующие:
Contex3DTriangleFace.NONE (не отбираем ничего)
Contex3DTriangleFace.BACK (по умолчанию, задние стенки отбираются)
Contex3DTriangleFace.FRONT( противоположность нормальному поведению, передние стенки не будут отрисованы)
Contex3DTriangleFace.BOTH (который бесполезен на практике, поскольку обе стороны каждой поверхности в вашем полигоне будут отобраны, в результате ничего не отобразится).
Эти виды настроек, называются «установками рендеринга». Как только вы примерно установите их, они останутся в эффекте до тех пор, пока вы не установите их как-то иначе. Только вы должны установить их однажды заранее, если вы рендерите несколько мешей одним способом.

Проверка глубины.
Когда Flash рендерит вашу сцену, имеет значение как далеко каждый полигон располагается от камеры. Например, стена, которая близко к камере, не будет интуитивно скрывать ничего за собой. Для того, чтобы определить, что перед, а что за, каждый раз пиксели рендерятся в Stage3D, rgb-данные сохраняются в соответствии с глубиной компонентов. Данные, которые хранятся в глубине каждого пикселя на экране, называются буфером глубины (z-буфером).
По умолчанию в Stage3D рендерятся только пиксели, которые ближе всего к камере, что имеет смысл, как было показано в предыдущем примере со стеной. Обычно, есть множество случаев, когда вы не хотите сохранять информацию глубины для детальных мешей (например, когда используете спецэффекты, такие как взрывы, прозрачные области подобно оконному стеклу) или когда вы не хотите проверять буфер глубины при визуализации (для рентгеновского зрения, где вы можете видеть меш, даже если он полностью скрыт за стеной).

Время для действий: Создание меша не влияет на z-буфер.
Для того, чтобы изменить то, что Flash делает с проверкой глубины, вы можете инструктировать его либо не писать в z-буфер, либо не проверять его, или комбинировать и то и другое, используя функцию setDepthTest().
context3D. setDepthTest(false, Context3DCompareMode.LESS);
Что же произойдет?

Эта функция принимает два параметра. Первый это булевое (true или false) значение, которое является true, если вы хотите следующий меш, который вы рендерите, записать в буфер глубины. Второй параметр может быть одним из следующих:
Context3DCompareMode.ALWAYS
Context3DCompareMode.EQUAL
Context3DCompareMode.GREATER
Context3DCompareMode.GREATER_EQUAL
Context3DCompareMode.LESS
Context3DCompareMode.LESS_EQUAL
Context3DCompareMode.NEVER
Context3DCompareMode.NOT_EQUAL

Запрашиваемый пиксель будет рендериться когда сравнение между ним и целевым пикселем даёт TRUE, основываясь на предыдущем критерии. По умолчанию в Stage3D рендерятся только пиксели, которые имеют значение глубины меньше (LESS ) чем те, что уже на экране, ближе к камере.

Предыдущий пример удобен, если вы собираетесь рендерить прозрачный меш подобно окну, которое вы не хотите рисовать в z-буфере (так, что другие объекты всегда видимы за ним), но вы хотите, чтобы оно скрывало обстановку в комнате, которая находится перед ним.
Как и с другими состояниями рендеринга, они будут в эффекте до тех пор, пока вы не установите их как-нибудь иначе. Вы должны вызвать их только однажды, и эти критерии будут использоваться до тех пор, пока вы не проинструктируете Flash по-другому.

Режимы наложения.
Stage3D API предоставляет мощный массив режимов наложения, которые действуют как текстуры при рендеринге. Например, вы можете инструктировать его рисовать текстуру, используя прозрачность или смешать цвета в вашей текстуре с теми, что позади неё, так что они подсветят или затемнят экран. Этими видами режимов наложения – сложения, смешивания, вычитания – в современных играх рисуются системы частиц, такие как дым, огонь, взрывы, искры и т.п.

Перед тем, как вы нарисуете меш, вы можете использовать функцию Context3D.setBlandFactors(), чтобы инструктировать Flash как вы хотите рисовать текстуру.
Чтобы установить режим наложения, вы отправляете два коэффициента наложения в функцию. Первый коэффициент это исходный коэффициент, и второй – это целевой коэффициент. Результирующий пиксель на экране вычисляется следующим образом:
framebuffercolor = (fragmentcolor * srcBlend) + (framebuffercolor * destBlend).

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

Время для действий: рендеринг непрозрачного меша.
//это Molehill по умолчанию: хорошо для непрозрачных текстур
//все цвета на экране получены из фрагментных цветов
context3D.setBlendFactors(
Context3DBlendFactor.ONE, Context3DBlendFactor.ZERO);
Что же произойдёт?

Мы инструктируем Stage3D взять цвет из результата вашего фрагментного шейдера и пропустить цвет из буфера кадра (который позади меша). Это будет выглядеть наподобие как на следующем скриншоте:
Время для действий: рендеринг меша с прозрачными областями.
//Эту настройку следует использовать для .png текстур,
//которые имеют прозрачные области
//подходит для листвы/изгородей/шрифтов
context3D.setBlendFactors(
Context3DBlendFactor.SOURCE_ALPHA,
Context3DBlendFactor.ONE_MINUS_ SOURCE_ALPHA);

Что же произойдёт?
Мы просто инструктируем Stage3D, чтобы следующий рендерящийся меш взял всю цветовую информацию из выходных данных нашего фрагментного шейдера, где альфа (прозрачность) в нашей текстуре это чистый белый, и накладывается на цвет из буфера кадра позади меша в количестве, пропорциональном содержанию чёрных пикселей в текстурном альфа-канале. В примере с нашей картинкой листа зелёные биты листвы имеют чисто-белую альфу, и прозрачные области в текстуре будет чисто чёрными, если мы будем смотреть только альфа-данные, зашифрованные в изображении.

Время для действий: рендеринг меша так, чтобы он освещал сцену.
//эти настройки заставят его освещать только сцену
//хорошо для частиц, сохранённых как .jpg
//(только белые части добавляются на экран – чёрные не рисуются)
context3D.setBlendFactors(
Context3DBlendFactor.SOURCE_COLOR,
Context3DBlendFactor.ONE);

Что же произойдёт?
Мы просто инструктируем Stage3D, что мы хотим, чтобы следующий меш, рендерясь, добавлял цвет пикселя нашей текстуры на сцену, так что каждый пиксель рендерясь, будет приближать изображение к чисто-белому.
Используйте творчески режимы наложения, необходимые для системы частиц и других спецэффектов. Вот ещё один, последний, пример из нескольких для множества возможных режимов наложения.

Следующие три картинки изображают режимы наложения DESTINATION_COLOR, ZERO, ONE, ONE и SOURCE_COLOR, ONE соответственно:
В качестве параметров для функции context3D.setBlendFactors() у вас есть следующие варианты, каждый из которых имеет типы Context3DBlendFactor:
Context3DBlendFactor.DESTINATION_ALPHA
Context3DBlendFactor.DESTINATION_COLOR
Context3DBlendFactor.ONE
Context3DBlendFactor.ONE_MINUS_DESTINATION_ALPHA
Context3DBlendFactor.ONE_MINUS_DESTINATION_COLOR
Context3DBlendFactor.ONE_MINUS_SOURCE_ALPHA
Context3DBlendFactor.ONE_MINUS_SOURCE_COLOR
Context3DBlendFactor. SOURCE_ALPHA
Context3DBlendFactor. SOURCE_COLOR
Context3DBlendFactor. ZERO

Режимы наложения, отбор поверхностей, проверка глубины - это все состояния рендеринга, значения которых вы только однажды должны установить, и поведение которых будет продолжаться до тех пор, пока вы не установите их как-нибудь иначе.
Иногда это может быть источником незнакомых ошибок: если вы обнаружите, что после экспериментов с режимами наложения, другие модели теперь нечаянно рендерятся прозрачными, значит, вы могли забыть переустановить задний режим наложения по умолчанию (ONE, ZERO). Чтобы вернуть им «полностью непрозрачный» режим наложения, который активируется при первой инициализации Stage3D, вы должны использовать этот код:
context3D.setBlendFactors(
Context3DBlendFactor.ONE, Context3DBlendFactor.ZERO);

Увеличиваем производительность.
Хотя Stage3D API невероятно быстр и может обрабатывать сотни тысяч полигонов в режиме реального времени, вы должны избегать кусков больше, чем ваша видеокарта способна разжевать. Это особенно важно при ориентации на маломощные системы, такие как дешевые нетбуки или мобильные устройства.
Некоторые общие методы для получения наилучшей производительности (и в результате получения высокой частоты кадров) это следовать вечному правилу поцелуя (будь проще, глупый!)

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

Избегаем перерисовки.
Перерисовка означает ситуацию, когда тот же пиксель на экране повторно рисуется во время построения кадра. Каждый слой это пустая трата времени. Иногда, конечно, это время стоит того, чтобы достичь превосходных визуальных эффектов, но по возможности нужно свести к минимуму перерисовки и использовать непрозрачные полигоны (и режимы наложения ONE,ZERO) как можно чаще.

Глубинный буфер (или z-буфер) это мощный помощник в избегании перерисовок. Когда ваша видеокарта рендерит сцену, r,g,b цвета каждого пикселя сохраняются в видеопамяти вместе с другим значением: текущая z-глубина того пикселя. Затем, при последующих вызовах, рендерится дополнительная геометрия до пикселя, отрендеренного в том же месте, z-буфер проверяется, и если предыдущий полигон «спереди», он скоро будет отображён, ваша видеокарта может игнорировать его целиком, экономя множество времени.

Т.к. z-буфферы позволяют отменять перерисовку полигонов, рисуйте непрозрачную геометрию, которая ближе всего к камере, первой, всякий раз когда это возможно. Есть ваша сцена состоит из сотен моделей, вызываете renderTriangles для геометрии от самой близкой к самой далекой, если это возможно. Таким образом, если вы рисуете непрозрачную стену прямо перед игроком, любые попытки рендеринга геометрии, закрытой этой стеной, обрабатываются почти мгновенно, т.к. все её пиксели могут быть сразу отменены. В конце концов, их не было бы видно!

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

Поэтому, по возможности, группируйте геометрию по используемым текстурам и шейдерам. Если 4 модели в вашей игре используют одну текстуру, режим наложения и вертексные и фрагментные AGAL программы, то вам достаточно всего лишь указать Flash использовать их на старте, позже все 4 модели будут рисоваться без каких-либо изменений состояния.

Используйте простые шейдеры
Кроме того, чем больше строк AGAL кода используют шейдера, тем медленнее они будут работать. Супер-сложные шейдеры снижают частоту кадров игры. Как и со всем в программировании игр, вам будет необходимо экспериментировать и найти "золотую середину" в балансе красоты и производительности.

Рисуйте меньше моделей
Одна 10000 вертексная модель рисуется быстрее, чем 1000 10 вертексных моделей. Комбинируйте модели, так чтобы вызывать drawTriangles() реже. Кроме того, рисуйте только действительно видимые модели. Это называется построением PVS(potentially visible set - потенциально видимое множество). Вы можете найти супер-продвинутые методы оптимизации поиском таких алгоритмов как: Quad-деревья, BSP деревья, и порталов. Хотя построение PVS выходит за рамки руководства для начинающих, несложно представить простейший алгоритм PVS, следующим образом.

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

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

Добавляем текстурные эффекты в наше демо.
Теперь, когда мы преодолели много важных проблем рендеринга текстур, режимов наложения и буфера глубины, давайте модифицирируем наше текущее игровое демо.
Вы можете увидеть конечное демо Главы 6 здесь:
http://www.mcfunkypants.com/molehill/chapter_6_demo/
Исходный код, который мы собираемся написать, доступен на веб-странице книги:
http://www.packtpub.com/adobe-flash11-stage3d-molehill-game-
programming-beginners-guide/book

Начнём с демо-проекта, который вы создали в прошлой главе, сделаем несколько изменений:
1. Создадим несколько новых мешей для вашей игры (сферы, кубы, рассеянные полигоны и т.д.)
2. Нарисуем немного новых текстур (прозрачные .png, немного .jpg-частиц и т.д.)
3. Встроим этот новый контент в ваш SWF и пропарсим его в игре при инициализации.
4. Добавим события клавиатуры, так мы сможем переключать модели, текстуры и режимы наложения.
5. Обновим текстовое GUI, чтобы отразить новое состояние.
6. Улучшим ваш цикл рендеринга, чтобы считывать изменения.

В следующем разделе «Время для действий» мы реализуем все предыдущие улучшения.
Сначала ваяем несколько новых мешей (возможных объектов, которые вы желаете видеть в вашей новой игре) и текстур для них. Эта книга о программировании, так что создание рисунков будет читателю весёлой задачей. В следующем примере кода, вместо этого, включим дюжину похожих линий для каждой нашей текстуры и мешей, мы просто покажем единственный пример. Если вы запутались, тогда просто откройте полный исходный код из .zip-файла по предыдущей ссылке.

Как только вы создадите несколько интересных мешей в .OBJ формате, а также пару новых текстур в .PNG и .JPG файлах, вы готовы начать кодинг. Подробнее предыдущие шаги потребуют, чтобы вы добавили следующий код в исходный файл Stage3dGame.as.

Время действовать - встраиваем новую графику
Первым делом, чтобы встроить весь новый контент вам нужно изменить верхушку файла, добавив новые команды встраивания. Если вы используете Flex или чистый AS3 редактор такой как FlashDevelop, это должно выглядеть следующим образом:
[Embed (source = "art/leaf.png")] private var myTextureBitmap1:Class;
private var myTextureData1:Bitmap = new myTextureBitmap1();
// etc...

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

Обратите внимание на имена классов, которые мы собираемся использовать для каждого файла графики. Чтобы добавить графику в .fla, достаточно перетянуть файл в облать библиотеки и кликнуть правой кнопкой на добавленный файл, чтобы отредактировать его свойства согласно предыдущему примеру. В исходном коде вместо предыдущих вставок в стиле Flex, вы получите код следующего стиля:
private var myTextureData1:Bitmap = // leaf.png new Bitmap(new myTextureBitmapData1(256,256));
// etc...
Теперь, сделайте тоже самое для новой геометрии, так чтобы она была встроена в SWF. Это можно сделать следующим образом:
// The mesh data [Embed (source = "cluster.obj", mimeType = "application/octet-stream")] private var myObjData1:Class; private var myMesh1:Stage3dObjParser;
// etc...

Итак, что именно мы сделали только что?
Мы встроили новые графические ресурсы в проект. Помните, что вместо одной новой текстуры и геометрии, вы можете встроить другие. Предположим что мы встроили 5 текстур (myTextureData1,2,3,4,5) плюс одну для земли, которую мы встроили в предыдущей главе, а также 5 новых геометрий (myMesh1,2,3,4,5).

Время действовать - добавим нужные нам переменные
Вам понадобится еще несколько переменных чтобы сохранить Stage3D классы, для нового контента, а также для переключения с одного режима в другой для отображения GUI:
// Stage3d текстуры использующие перечисленные выше
private var myTexture1:Texture;
private var myTexture2:Texture;
private var myTexture3:Texture;
private var myTexture4:Texture;
private var myTexture5:Texture;
private var terrainTexture:Texture;
// Указывает на текущую геометрию
private var myMesh:Stage3dObjParser;
// Доступные режимы наложения/текстуры/геометрии
private var blendNum:int = -1;
private var blendNumMax:int = 4;
private var texNum:int = -1;
private var texNumMax:int = 4;
private var meshNum:int = -1;
private var meshNumMax:int = 4;
//Используются GUI
private var label1:TextField;
private var label2:TextField;
private var label3:TextField;

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

Время для действий: улучшаем GUI.
Для этого демо мы изменили в этом месте GUI , и будем изменять дерево меток каждый раз, когда нажимается клавиша.
//дескриптор текстового формата, используется всеми метками GUI
var myFormat:TextFormat = new TextFormat();
myFormat.color = 0xFFFFFF;
myFormat.size = 13;
//создаём счётчик FPS, чтобы отображать частоту кадров на экране
fpsTf = new TextField();
fpsTf.x = 0; fpsTf.y = 0;
fpsTf.selectable = false;
fpsTf.autoSize = TextFieldAutoSize.LEFT;
fpsTf.defaultTextFormat = myFormat;
fpsTf.text = "Initializing Stage3d...";
addChild(fpsTf);
//создаём отображение очков
scoreTf = new TextField();
scoreTf.x = 560;
scoreTf.y = 0;
scoreTf.selectable = false;
scoreTf.autoSize = TextFieldAutoSize.LEFT;
scoreTf.defaultTextFormat = myFormat;
addChild(scoreTf);
//добавляем несколько меток, чтобы описать каждый шейдер
label1 = new TextField();
label1.x = 250;
label1.y = 400;
label1.selectable = false;
label1.autoSize = TextFieldAutoSize.LEFT;
label1.defaultTextFormat = myFormat;
addChild(label1);
label2 = new TextField();
label2.x = 250;
label2.y = 42 0;
label2.selectable = false;
label2.autoSize = TextFieldAutoSize.LEFT;
label2.defaultTextFormat = myFormat;
addChild(label2);
label3 = new TextField();
label3.x = 250;
label3.y = 44 0;
label3.selectable = false;
label3.autoSize = TextFieldAutoSize.LEFT;
label3.defaultTextFormat = myFormat;
addChild(label3);
//устанавливаем эти метки
nextMesh();
nextTexture();
nextBlendmode();
}

Что же произойдёт?
Мы расширили наш код инициализации GUI, чтобы учесть новые метки. Сначала мы создали объект TextFormat и назначили ему некоторые данные стиля для использования во всех новых объектах TextField, которые мы собираемся добавить в GUI. Для каждой новой метки мы позиционируем её на экране, гарантируя, что пользователь не сможет выделить текст, поддерживаем её размер при обновлении и, наконец, добавляем её на сцену.

Время для действий: слушаем нажатия клавиш.
Для того, чтобы начать использовать клавиатуру в управлении вашей игрой, вы должны добавить слушатель событий, который слушает нажатия клавиш. Наверху вашей функции init() инструктируем Flash начать прослушивание для событий KEY_DOWN.
//получаем нажатия клавиш
stage.addEventListener(KeyboardEvent.KEY_DOWN, keyPressed);
Сейчас создаём следующую функцию keyPressed, такую же как функции, которые будут переключать каждый меш, режимы наложения и текстуры, используемые во время цикла рендеринга:
private function keyPressed(event:KeyboardEvent):void {
switch(event.charCode)
{
case 98: // the b key
nextBlendmode();
break;
case 109: // the m key
nextMesh();
break;
case 116: // the t key
nextTexture();
break;
}
}
Предыдущая функция будет вызываться всякий раз, когда нажимается клавиша. Если пользователь нажал клавиши b, m или t, то соответствующая функция при выполнении будет переключать режимы визуализации.
private function nextBlendmode():void {
blendNum++;
if (blendNum > blendNumMax) blendNum = 0;
switch(blendNum)
{
case 0:
label1.text = '[B] ONE,ZERO';
break;
case 1:
label1.text = '[B] SOURCE_ALPHA,ONE_MINUS_SOURCE_ALPHA';
break;
case 2:
label1.text = '[B] SOURCE_COLOR,ONE';
break;
case 3:
label1.text = '[B] ONE,ONE';
break;
case 4:
label1.text = '[B] DESTINATION_COLOR,ZERO';
break;
}
}

Предыдущая функция будет увеличивать порядковое значение выбранного в настоящее время режима наложения, произвольно возвращаясь в первый режим, когда мы переходим к последнему доступному. Затем обновляем наш GUI, чтобы отразить изменения.
private function nextTexture():void {
texNum++;
if (texNum > texNumMax) texNum = 0;
switch(texNum)
{
case 0:
label2.text = '[T] Transparent Leaf Texture';
break;
case 1:
label2.text = '[T] Fire Texture';
break;
case 2:
label2.text = '[T] Lens Flare Texture';
break;
case 3:
label2.text = '[T] Glow Texture';
break;
case 4:
label2.text = '[T] Smoke Texture';
break;
}
}

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

Не забудьте изменить эти функции , чтобы отражать независимо от разных имён, которые вы используете для ваших новых изображений. Например, если вы добавили дюжину новых мешей, тогда вы должны будете добавить немного больше линий после оператора switch, а также измените переменную MeshNumMax, которую мы определили ранее:
private function nextMesh():void {
meshNum++;
if (meshNum > meshNumMax) meshNum = 0;
switch(meshNum)
{
case 0:
label3.text = '[M] Random Particle Cluster';
break;
case 1:
label3.text = '[M] Round Puff Cluster';
break;
case 2:
label3.text = '[M] Cube Model';
break;
case 3:
label3.text = '[M] Sphere Model';
break;
case 4:
label3.text = '[M] Spaceship Model';
break;
}
}
В предыдущем коде мы реализовали возможность прокрутки списка доступных режимов наложения, текстур и мешей, реагируя на нажатия клавиши.

private function enterFrame(e:Event):void {
//принудительно очищаем сцену перед рендерингом
context3D.clear(0,0,0);
//перемещаем или поворачиваем каждый кадр
t += 2.0;
//прокручиваем и сразу рендерим поверхность
renderTerrain();
//рендерим независимо от выбранного меша
renderMesh();
//теперь показываем/переворачиваем задний буфер
//чтобы все меши нарисовались
context3D.present();
//обновляем индикатор FPS
fpsTicks++;
var now:uint = getTimer();
var delta:uint = now - fpsLast;
//обновляем индикатор только раз в секунду
if (delta >= 1000)
{
var fps:Number = fpsTicks / delta * 1000;
fpsTf.text = fps.toFixed(1) + " fps";
fpsTicks = 0;
fpsLast = now;
}
//обновляем счёт в GUI
updateScore();

Что же произойдет?
Наша функция enterFrame сейчас значительно прощем, с тех пор как мы переместили код рендеринга множества мешей в новые функции.
Всё что осталось – это написать алгоритм, который управляет рендерингом этих мешей. Функция renderTerrain осталась почти такой же (кроме того, что мы сбросили режимы наложения на значения по умолчанию), но есть новая функция renderMesh, которая проверяет переменные, устанавливаемые вашими событиями клавиатуры, и изменения состояния ( текстуры, режимы наложения, меши) перед рендерингом.

Время для действий: обновляем функцию renderTerrain.
Добавим следующее наверх вашей существующей функции renderTerrain:
//сочетания текстур : все без смешивания – непрозрачные
context3D.setBlendFactors(
Context3DBlendFactor.ONE,
Context3DBlendFactor.ZERO);
//рисуем глубинный z-буфер и не рисуем полигоны, которые скрыты
context3D.setDepthTest(true, Context3DCompareMode.LESS);

Что же произойдёт, если сделать это?
В предыдущем коде мы гарантировали, что состояния рендеринга, установленные в нашем демо, «сброшены» на режим по умолчанию (непрозрачный), требуемый нашей поверхности.

Время для действий: обновляем нашу инициализацию Stage3D.
Когда мы рисуем только один меш за раз, в отличие от предыдущей версии нашего демо, мы должны настроить угол камеры так, чтобы она расположилась по центру экрана. Кроме того, мы должны создать все пять текстур. Измените вашу функцию onContext3DCreate следующим образом, чтобы учесть эти изменения:
private function onContext3DCreate(event:Event):void {
//Удалите существующий приёмник кадра. Учтите, что потеря контекста
//может произойти в любой момент, что вынудит вас
//пересоздавать все объекты, которые мы здесь создали.
//Потеря контекста может произойти, например, если вы нажмёте
// CTRL-ALT-DELETE на Windows.
//Потребуется некоторое время прежде чем новый контекст будет доступен,
//следовательно, удаление приёмника enterFrame очень важно!
if (hasEventListener(Event.ENTER_FRAME))
removeEventListener(Event.ENTER_FRAME,enterFrame);
//Получаем текущий контекст
var t:Stage3D = event.target as Stage3D;
context3D = t.context3D;
if (context3D == null)
{
//В настоящее время контекст 3d не доступен (ошибка!)
trace('ERROR: no context3D - video driver problem?');
return;
}
//Отключение проверки ошибок значительно повысит производительность
//Если установлено в true, Flash посылает полезные сообщения об ошибках
//при компиляции AGAL, неинициализированных программных константах и т.д.
context3D.enableErrorChecking = true;
//инициализируем данные нашего меша
initData();
//Размера заднего 3d буфера в пикселях (2=сглаженный)
context3D.configureBackBuffer(swfWidth, swfHeight, 2, true);
//собираем все шейдеры, которые нам нужны
initShaders();
Предыдущий код идентичен тому, который использовался в прошлой главе. Теперь изменим код инициализации текстур, должным образом инициализировав все новые изображения, которые мы внедрили за это время.
myTexture1 = context3D.createTexture(
myTextureData1.width, myTextureData1.height, Context3DTextureFormat.BGRA, false); uploadTextureWithMipmaps(
myTexture1, myTextureData1.bitmapData);
//и т.д.
myTexture5 = context3D.createTexture(
myTextureData5.width, myTextureData5.height, Context3DTextureFormat.BGRA, false); uploadTextureWithMipmaps(
myTexture5, myTextureData5.bitmapData);
terrainTexture = context3D.createTexture(
terrainTextureData.width, terrainTextureData.height, Context3DTextureFormat.BGRA, false); uploadTextureWithMipmaps(
terrainTexture, terrainTextureData.bitmapData);
//создаём матрицу проекции для нашей 3D сцены
projectionmatrix.identity();
// FOV, отношение сторон 640/480, 0.1=near, 100=far
projectionmatrix.perspectiveFieldOfViewRH(
45.0, swfWidth / swfHeight, 0.01, 5000.0);
//создаём матрицу, которая определяет расположение камеры
viewmatrix.identity();
//перемещаем камеру немного назад, чтобы мы могли видеть меш
viewmatrix.appendTranslation(0, 0, -1.5);
//немного наклоняем поверхность, так чтобы она двигалась к нам
terrainviewmatrix.identity();
terrainviewmatrix.appendRotation(-6 0,Vector3D.X_AXIS);
//запускаем цикл рендеринга!
addEventListener(Event.ENTER_FRAME,enterFrame);
}

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

Время для действий: упрощаем функцию initShaders.
Сейчас у нас есть только одна вершинная и одна фрагментная программа, так что вы можете удалить программы 2, 3 и 4 включительно (или только просто закомментировать их).
private function initShaders():void {
//Простой вершинный шейдер, который делает 3D трансформацию
//для простоты используется всеми четырьмя шейдерами
var vertexShaderAssembler:AGALMiniAssembler = new AGALMiniAssembler(); vertexShaderAssembler.assemble(
Context3DProgramType.VERTEX,
//4х4 матричное умножение устанавливает угол камеры
"m44 op, va0, vc0\n" +
//рассказываем фрагментному шейдеру о XYZ
"mov v0, va0\n" +
// рассказываем фрагментному шейдеру о UV-координатах
"mov v1, va1\n" +
// рассказываем фрагментному шейдеру о RGBA
"mov v2, va2"
);
//текстурируем используя UV-координаты
var fragmentShaderAssembler1:AGALMiniAssembler = new AGALMiniAssembler(); fragmentShaderAssembler1.assemble (
Context3DProgramType.FRAGMENT,
//захватываем цвет текстуры из текстуры 0
//и UV-координаты из переменной регистра 1
//и сохраняем интерполированное значение в ft0
"tex ft0, v1, fs0 <2d,linear,repeat,miplinear>\n"+
//перемещаем это значение в результирующий цвет
"mov oc, ft0\n"
);
//комбинируем шейдеры в программу, которую затем загружаем в GPU
shaderProgram1 = context3D.createProgram(); shaderProgram1.upload(
vertexShaderAssembler.agalcode,
fragmentShaderAssembler1.agalcode);
}

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

Время для действий: парсим новые меши.
Мы также должны гарантировать, что четыре модели загрузятся. Настраиваем функцию initData следующим образом:
private function initData():void {
//парсим OBJ файл и создаём буферы
trace("Parsing the meshes...");
myMesh1 = new Stage3dObjParser(
myObjData1, context3D, 1, true, true);
myMesh2 = new Stage3dObjParser(
myObjData2, context3D, 1, true, true);
myMesh3 = new Stage3dObjParser(
myObjData3, context3D, 1, true, true);
myMesh4 = new Stage3dObjParser(
myObjData4, context3D, 1, true, true);
myMesh5 = new Stage3dObjParser(
myObjData5, context3D, 1, true, true);
//точно также парсим меш поверхности
trace("Parsing the terrain...");
terrainMesh = new Stage3dObjParser(
terrainObjData, context3D, 1, true, true);
}

Что же произойдёт?
Мы обновили нашу функцию инициализации мешей, чтобы учесть наши загатовки. По-прежнему гарантируем, чтобы значения имён переменных не зависели от мешей, которые мы создали. Если ваши модели слишком большие или маленькие, вы можете изменить масштаб во время парсинга в предыдущей строке, изменяя еденицы на большие или меньшие числа.

Время для действий: рендерим различные меши соответствующим образом.
Теперь напишем новую функцию рендеринга меша, которая будет отображать единственный меш, отцентрированный на экране в зависимости от текущего состояния, как обсуждалось ранее.
private function renderMesh():void {
if (blendNum > 1)
//игнорируем глубинный z-буфер
//всегда рисуем полигоны, даже те, которые позади других
context3D.setDepthTest(false, Context3DCompareMode.LESS);
else
//используем глубинный z-буфер
context3D.setDepthTest(true, Context3DCompareMode.LESS);
//очищаем матрицу трансформации в 0, 0, 0
modelmatrix.identity();
context3D.setProgram ( shaderProgram1 );
setTexture();
setBlendmode();
modelmatrix.appendRotation(t*0.7, Vector3D.Y_AXIS);
modelmatrix.appendRotation(t*0.6, Vector3D.X_AXIS);
modelmatrix.appendRotation(t*1.0, Vector3D.Y_AXIS);
//очищаем матрицу и добавляем новые углы
modelViewProjection.identity();
modelViewProjection.append(modelmatrix);
modelViewProjection.append(viewmatrix);
modelViewProjection.append(projectionmatrix);
//передаём данные нашей матрицы в шейдерную программу
context3D.setProgramConstantsFromMatrix( Context3DProgramType.VERTEX,
0, modelViewProjection, true );

В предыдущем коде мы обновляем текущее состояние рендерина ( выбранные текстуры, режимы наложения и проверка глубины) соответствующим образом и затем обновляем угол камеры и модель матрицы:
switch(meshNum){
case 0:
myMesh = myMesh1;
break;
case 1:
myMesh = myMesh2;
break;
case 2:
myMesh = myMesh3;
break;
case 3:
myMesh = myMesh4;
break;
case 4:
myMesh = myMesh5;
break;
}

По-прежнему, мы должны настроить оператор switch выше так, чтобы независимо от числа ваших моделей вы встраивали их в ваше демо.
//рисуем меш
//позиция
context3D.setVertexBufferAt(0, myMesh.positionsBuffer,
0, Context3DVertexBufferFormat.FLOAT_3);
//текстурные координаты
context3D.setVertexBufferAt(1, myMesh.uvBuffer,
0, Context3DVertexBufferFormat.FLOAT_2);
//rgba вершины
context3D.setVertexBufferAt(2, myMesh.colorsBuffer,
0, Context3DVertexBufferFormat.FLOAT_4);
//рендерим его
context3D.drawTriangles(myMesh.indexBuffer,
0, myMesh.indexBufferCount);
}

Что же произойдёт?
Предыдущая функция вызывает пару удобных алгоритмов, чтобы установить состояние рендеринга. Они выполняются прямо перед рендерингом и просто устанавливают такие состояния рендеринга, которые нам нужны, в зависимости от того, что было выбрано во время нашего события нажатия клавиши. Соответствующий меш выбирается, Stage3D инструктируется , чтобы использовать каждый буфер, и, наконец, меш рендерится.

Время для действий: переключаем текстуры.
Мы должны создать функцию, которая выбирает соответствующую текстуру и та рендерится в зависимости от текущего выбора пользователя.
private function setTexture():void {
switch(texNum)
{
case 0:
context3D.setTextureAt(0, myTexture1);
break;
case 1:
context3D.setTextureAt(0, myTexture2);
break;
case 2:
context3D.setTextureAt(0, myTexture3);
break;
case 3:
context3D.setTextureAt(0, myTexture4);
break;
case 4:
context3D.setTextureAt(0, myTexture5);
break;
case 5:
context3D.setTextureAt(0, myCubeTexture);
// нужен разный AGAL
break;
}

Что же произойдёт?
Предыдущая функция вызывается функцией renderMesh. Она инструктирует Stage3D использовать текстуры, которые выбрал пользователь.

Время для действий: переключаем режимы наложения.
Мы также должны создать функцию, которая выбирает соответствующий режим наложения в зависимости от текущего выбора.
private function setBlendmode():void {
//все возможные режимы наложения:
// Context3DBlendFactor.DESTINATION_ALPHA
// Context3DBlendFactor.DESTINATION_COLOR
// Context3DBlendFactor.ONE
// Context3DBlendFactor.ONE_MINUS_DESTINATION_ALPHA
// Context3DBlendFactor.ONE_MINUS_DESTINATION_COLOR
// Context3DBlendFactor.ONE_MINUS_SOURCE_ALPHA
// Context3DBlendFactor.ONE_MINUS_SOURCE_COLOR
// Context3DBlendFactor.SOURCE_ALPHA
// Context3DBlendFactor.SOURCE_COLOR
// Context3DBlendFactor.ZERO
switch(blendNum)
{
case 0:
//по умолчанию: хорошо для непрозрачных текстур
context3D.setBlendFactors(
Context3DBlendFactor.ONE,
Context3DBlendFactor.ZERO);
break;
case 1:
//безупречно для прозрачных текстур
//подобно листве/заборам/шрифтам
context3D.setBlendFactors(
Context3DBlendFactor.SOURCE_ALPHA, Context3DBlendFactor.ONE_MINUS_SOURCE_ALPHA); break;
case 2:
//безупречно, чтобы заставить их освещать только сцену
//(чёрный не рисуется)
context3D.setBlendFactors(
Context3DBlendFactor.SOURCE_COLOR, Context3DBlendFactor.ONE);
break;
case 3:
//просто освещаем сцену – превосходно для частиц
context3D.setBlendFactors(
Context3DBlendFactor.ONE,
Context3DBlendFactor.ONE);
break;
case 4:
//безупречно когда вы хотите просто затемнить (дым и т.д.)
context3D.setBlendFactors(
Context3DBlendFactor.DESTINATION_COLOR,
Context3DBlendFactor.ZERO);
break;
}
}

Что же произойдёт?
Предыдущая функция также вызывается функцией renderMesh. Она инструктирует Stage3D использовать режимы наложения, которые были выбраны пользователем.

Ваше демо обновлено!
Что есть на данный момент. Предыдущими изменениями мы обновили ваше Stage3D демо, чтобы включить множество мешей, текстур и режимов наложения. Попробуйте скомпилировать ваш .SWF файл и помните, что если при запуске возникли какие-то ошибки, вы можете проверить, что всё на своём месте просто скачав полный исходный код со страницы книги: http://www.packtpub.com/adobe-flash11-stage3d-molehill-game-
programming-beginners-guide/book

Как только вы сможете запустить игру, нажмите на неё, чтобы убедиться, что на имеет фокус, и попробуйте нажать Т для переключения между различными текстурами. Нажмите клавишу В, чтобы переключить режимы наложения и клавишу М, чтобы сменять модели.
Обойдя все виды текстур, мешей, режимов наложения и обратите внимание как выглядят одни и другие, которые имеют смысл для некоторых текстур. Сравните со следующим скриншотом:

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

Всё это и даже больше, может быть достигнуто путём умного использования функции setBlendFactors() вместе с настройкой отбора (куллинга) и режимов z-буфера, которые управляются функциями setCulling() и setDepthTest().

1. Какая строка кода, позволяет вам использовать .PNG текстуры с прозрачностью без наложений сложения и разницы (подобно изображению листа, который мы использовали в примере)?
a. context3D.setBlendFactors(ZERO,ONE)
b. context3D.setBlendFactors(SOURCE_ALPHA,ONE_MINUS_SOURCE_ ALPHA)
c. context3D.setBlendFactors(SOURCE_COLOR,SOURCE_COLOR)
d. context3D.setBlendFactors(PING,PONG)

2. Если вы хотите нарисовать группу полигонов, каждый из которых имеет простую текстуру, которая будет белой светящейся каплей на чёрном фоне, какой режим наложения вы можете использовать, так чтобы чёрный не отображался, а белый добавлялся к сцене, так чтоб это выглядело подобно светящемуся взрыву?
a. setBlendFactors(SOURCE_COLOR,SOURCE_COLOR)
b. setBlendFactors(SOURCE_COLOR,ZERO)
c. setBlendFactors(ONE,ONE)
d. setBlendFactors(BLOB,GLOWING)

3. Если мы хотим гарантировать, чтобы рендерились обе стороны каждого полигона (безупречно для случаев, когда вы хотите, чтобы внутренние или скрытые поверхности меша стали видимыми) какую функцию мы должны запустить?
a. setCulling(Context3DTriangleFace.NONE)
b. setCulling(Context3DTriangleFace.FRONT)
c. setDepthTest(false, Context3DCompareMode.LESS)
d. setDepthTest(maybe, Context3DCompareMode.GREATER_EQUAL)

Время для вашего стороннего квеста - улучшите ваше демо , показав две модели одновременно. Вместо медленного вращения моделей каждый кадр, просто поместите меш космического корабля в одну точку прямо вперёд без движения. Визуализируйте его с непрозрачной текстурой, такой как spaceship. Jpg из прошлой главы.
Теперь возьмите одну частицу каплей из текущего демо и отрендерьте его с blendFactor ONE,ONE и текстурой glow.jpg, так чтобы это выглядело подобно большой круглой светящейся массой частиц. Для этой модели, находящейся в самом низу вашей модели космического корабля, подстройте правильный размер, так чтобы выглядело, как будто огонь выходит из двигателей вашего корабля.

Итого.
В этой главе мы сосредоточились прежде всего на трёх функциях, которые влияют на то, как рендерятся ваши меши: setBlendFactors, setCulling, и setDepthTest. Творческое использование этих трёх установок рендеринга откроет для ваших игр мир всевозможных спецэффектов.

Реализовав этот функционал, мы преодолели ещё несколько пограничных камней. Наш игровой движок сейчас использует множество текстур и различные меши; у нас есть код для рендеринга текстур, использующий новые режимы наложения, которые позволяют нам отображать спецэффекты, такие как взрывы и дым. Мы можем изменять меши независимо от отобранных поверхностей, так что мы можем рисовать внутренние части прозрачных объектов, мы можем игнорировать или использовать z-буфер как пожелаем, и мы теперь обнаруживаем нажатия клавиш для пользовательского контроля действий.
Это последняя глава, где наш проект будет «техническим демо» и двигаясь вперёд, мы сможем справедливо назвать её «игра».

Уровень 6 достигнут!
Поздравляем с очередным повышением уровня. С каждым сеансом обучения ваши навыки становятся всё сильнее. Сейчас вам, возможно, не терпится действительно погрузиться дальше и начать программировать вашу игру.
Наконец пришло время для решающего удара! Вы готовы управлять вашим космическим кораблём с помощью клавиш стрелок и дать выход опустошению вашего игрового мира.
Мы обеспечим всё это и даже больше в следующей главе.

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

Переводчики: Orion45, a_metelsky, MuChan

Настройки

Готово:

100.00% КП = 1.0