Flash 8 & Sandy 3D. Третье измерение в мире Flash стало доступным

Этот материал начнет серию статей, посвященных созданию 3d-приложений с помощью flash 8. Сегодня мы будем изучать возможности библиотеки Sandy 3d (ее домашний сайт сайт ). Библиотек или 3d-движков под flash откровенно мало, причиной этого может служить, разумеется, отсутствие возможности взаимодействия со средствами операционной системы (directx, opengl). А на пути software эмуляции этих функций стоит, во-первых, низкая производительность flash player (которую нельзя даже близко сравнивать с решениями на c|c++.), и, во-вторых, примитивность самого языка actionscript2. Если вы писали actionscript2 проекты объемом больше десятка тысяч строк, то вы понимаете, о чем я говорю. Поэтому людьми, которые пишут бесплатный flash3d- движок (sandy распространяется по лицензии mozilla public license) и пишут его постоянно и хорошо, можно только восхищаться.

Sandy, конечно, не единственная библиотека, и, возможно, не самая лучшая. Но в моем личном рейтинге 3d-библиотек, ориентированных на actionscript2, я могу смело дать sandy место в тройке лидеров. На момент написания статьи последняя версия sandy была 1.1. В состоянии разработки следующая версия 1.2. Также в ближайших планах реализовать переход на actionscript 3 и составить конкуренцию такому известному "3d- монстру" как papervision 3d ( сайт ).

Модель мира в Sandy (как и в почти всех других 3d движках) представляется в виде дерева. Это дерево состоит из трех видов узлов: Group, TransformGroup и Object3D. Group и TransformGroup представляют собой контейнеры, в которые могут вкладываться другие объекты. Отличие TransformGroup в том, что к ней можно применять преобразования вращения и перемещения. Узел типа Object3D – это непосредственно видимый 3d- объект. Очевидно, что дочерних узлов у него быть не может. Особое место занимают объекты типа Sprite2D и Sprite3D, они представляют собой спрайты – плоские картинки, всегда ориентированные к лицу камеры. Особенность Sprite3d в том, что это не одна картинка, а целый набор их, создающий иллюзию настоящего трехмерного объекта, рассматриваемого с разных сторон.

У объекта Object3d есть очень важное свойство "enableBackFaceCulling". Оно отвечает за то, включен ли алгоритм отбраковки обратных сторон граней объектов. Какая сторона грани считается за "обратную" определяется ее нормалью, традиционно обратные стороны не отрисовываются. Это нужно для того, чтобы уменьшить нагрузку на процессор. Например, если у вас есть бокал, то нет смысла выполнять просчет его внутренних граней до тех пор, пока кто-нибудь не заглянет внутрь бокала.

Особое место занимают объекты transformations и interpolators. Их назначение в том, чтобы выполнять одиночное преобразование множества узлов (transformations) или же плавное изменение некоторого параметра (перемещение, масштабирование или вращение) от некоторого исходного параметра к конечному. По сути своей это упрощенный способ управлять анимацией.

Light – источник света. По-правде говоря, если у вас в сцене не будет никаких источников света, то все равно вы будете видеть объекты за счет фонового освещения. Не стоит надеяться на такие возможности управления светом, как в 3dsmax (с его множеством различных видов источников света, алгоритмами расчета освещения "почти как настоящее"). Все-таки flash как платформа не способствует реализации дорогостоящих алгоритмов расчета освещения. И все, что у нас есть, это источник света с двумя параметрами: направление, куда идет свет, а также мощность этого источника. Считается, что сам источник расположен где-то в бесконечной дали от нашей сцены, так что падающие лучи параллельны друг другу.

И наконец, объект Skin – его назначение управлять параметрами "материала", из которого изготовлены 3d-объекты. То, как объект будет реагировать на освещение, его текстура, цвет, прозрачность – все это в ведении объекта Skin.

Когда вы размещаете объекты в 3d-пространстве, всегда следует учитывать особенности системы координат (СК). Так, Sandy использует левостороннюю СК. Здесь ось OX направлена слева направо, ось OY направлена снизу вверх (в этом отличие от системы СК в flash, где ось OY направлена вниз), и ось OZ направлена вглубь сцены.

Для начала работы вам нужно выкачать исходные коды Sandy по адресу сайт Также вам потребуется библиотека pixlib, ее можно загрузить с сайта сайт . Затем необходимо выполнить настройку Flash8 так, чтобы он знал, где искать те файлы библиотек, которые мы будем использовать. Примечание: если вы пользуетесь для написания кода FlashDevelop или FDT, то я полагаю, что вы достаточно опытны, чтобы подключить эти библиотеки самостоятельно. Тех же, кто использует Flash8, прошу в меню "Edit->Preferences", затем закладка "ActionScript" и по кнопке "ActionScript 2.0 Settings" – вы увидите диалоговое окно, где перечислены внешние библиотеки. Добавьте путь к тому месту, куда вы распаковали исходники Sandy и библиотеку pixlib.

Теперь можно приступить к написанию собственно кода. Первым шагом для вас будет создание экрана, на который будет выполняться рендеринг 3d-мира. В пакете sandy.view есть интерфейс IView. Объекты, которые его поддерживают, могут быть использованы как "экраны". Сейчас есть два таких класса: sandy.view.BitmapScreen и sandy.view.ClipScreen. Оба эти класса имеют схожий конструктор, размеры экрана (высота и ширина) и, наконец, фоновый цвет экрана.

var screen:ClipScreen = new ClipScreen (_root.createEmptyMovieClip ('screen', 1), 640, 480);

Следующий шаг – это создание камеры: для этого вам следует вызвать конструктор класса sandy.view.Camera3D и передать ему как параметр значение фокусного расстояния, а также ссылку на родительный экран. После чего вы можете перемещать камеру или задать направление взгляда. С помощью метода setFocal можно изменять значение фокусного расстояния для камеры. К слову сказать, в следующей версии Sandy 1.2 планируется пересмотреть концепцию работы с камерой и заменить фокусное расстояние на FOV или угол обозрения. Если вам интересно, как соотносятся эти параметры, то обратитесь по ссылкам сайт и сайт

// создаем объект камеры
var cam:Camera3D = new Camera3D (500, screen);
// указываем координату, где будет находиться камера
cam.setPosition (50,0, 1000);
// указываем, куда будет направлен взгляд камеры
cam.lookAt (0, 500, 0);
// и добавляем камеру к 3d-миру
World3D.getInstance ().addCamera (cam);

Теперь остается только наполнить наш мир 3d-объектами. В общем случае здесь возможны следующие варианты. Прежде всего, есть некоторый набор предопределенных геометрических примитивов. Так в пакете sandy.primitive есть следующие классы: box – обычный параллелепипед, класс cylinder – задает цилиндр, есть класс pyramid – пирамида, sphere – сфера, наконец, hedra – многогранник. Кроме того, существуют еще два особых объекта Plane3D и Line3D – плоскость и линия в трехмерном пространстве. И это все, больше предопределенных объектов нет. Если вам нужны иные объекты, то придется их создавать вручную, указывая вершины и нормали. В особых ситуациях возможно выполнить импорт модели, созданной в профессиональном 3d- редакторе, например 3dsmax. Для этого вам нужно будет просто сохранить модель в формате ASE или WRL. Конечно, существует множество ограничений на модели и хитростей при их сохранении. Возможно, в следующих материалах я подробнее расскажу об этом направлении. Пока просто запомните название класса AseParser, выполняющего разбор модели в формате ASE и создание модели в виде Object3d.

В примере далее я создам некоторое подобие бильярдного стола. В нем столешница будет создана объектом Box, ножки стола – с помощью Cylinder. Все параметры конструкторов объектов интуитивно понятны, за исключением параметра mode – он отвечает за алгоритм, с помощью которого будут созданы грани объекта. Так возможны варианты: "tri" – в этом случае объект будет состоять из множества треугольников, или же "quad" – из прямоугольников. Если вы задаете для объекта какой-нибудь Skin, то особой разницы для вас не будет, в противном случае вы увидите объект в виде сетки и состоящим из множества треугольников или прямоугольников. Кроме того, часто у объектов бывает параметр quality (качество). Конкретное его поведение зависит от объекта, но в общем случае это показатель насколько качественна его модель (например, для цилиндра quality управляет количество сторон многоугольника, который аппроксимирует собой окружность основания). После создания объектов необходимо выполнить их перемещение в нужные координаты и, возможно, вращение. Сами объекты-примитивы не умеют ничего подобного, поэтому нужно поместить каждый из таких объектов внутрь специального объекта TransformGroup. А затем уже к нему и применять алгоритмы перемещения, вращения (более того, когда вы задаете вращение объекта, то можно управлять тем, вокруг чего именно будет выполняться это вращение, вы можете произвольно выбрать ось вращения) и, наконец, масштабирование. Более того, вы можете создать целый набор команд по преобразованию объектов (например, сначала вращаем так, потом переместим туда). Если вы знаете о матрицах преобразования и правилах их перемножения, то это вас не удивит. В противном случае я советую вам прочитать какую-нибудь хорошую книжку. Мне нравится Mathematics for Game Developers от Christopher Tremblay, эта книжку можно легко найти в internet. Запомните также, хотя это и несколько непривычно, что углы вращения следует задавать в градусах, а не в радианах. Также важно, что когда вы задаете несколько преобразований одновременно, то следует использовать специальный прием с комбинированием их матриц преобразований, т.к. сами объекты Transform3d не могут содержать более одной операции (детально это показано в последнем примере статьи в коде функции makeDeskLeg).

Последний шаг, который мы сделаем, это установим параметры материалов, из которых изготовлен стол. Как я уже говорил, за это отвечает интерфейс Skin, размещенный в пакете sandy.skin. Этот интерфейс реализовывают следующие классы:

SimpleLineSkin – это скин, назначаемый всем созданным 3d-объектам по умолчанию. В качестве параметров конструктора указывается цвет, толщина линии, значение ее прозрачности. Как результат выполняется только отрисовка граней, из которых состоит объект.

SimpleColorSkin – скин, выполняющий закраску объекта некоторым цветом с заданной прозрачностью (именно эти два параметра-числа следует указать в качестве параметров конструктора).

MixedSkin – этот скин - смесь двух предыдущих, он одновременно и заливает весь объект некоторым цветом, и рисует грани объекта еще одним цветом. ZLightenSkin – скин, поддерживающий закраску объекта методом Gouraud.

TextureSkin – это самый интересный скин, в качестве параметра его конструктора передается объект типа BitmapData. Этот объект, появившийся в 8- ом flash, представляет собой матрицу пикселей некоторого цвета и прозрачности. По своей сути это некоторая картинка текстуры, которую можно спроецировать на расположенный в 3d-пространстве треугольник. Для того, чтобы такое проецирование было возможно, необходимо, чтобы для каждой из вершин были заданы координаты UV. Вкратце, это правило, по которому мы ставим в соответствие каждой из вершин объекта некоторую точку картинки текстуры. Для остальных точек треугольника объекта поиск соответствующей координаты точки текстуры решается привычной интерполяцией. VideoSkin – этот скин позволяет отобразить на грани некоторый видеоролик.

MovieSkin – этот скин позволяет привязать к 3d-объекту некоторый клип (клип может быть анимированным), который и будет отображаться на его гранях. Конструктор этого скина получает также необязательный второй параметр. Это булева переменная, управляющая тем, будет ли выполняться перерасчет изображения клипа каждый кадр (это полезно, если клип анимированный) или же нет (это необходимо для экономии ресурсов). В документации разработчики постоянно напоминают о необходимости такой экономии, и поверьте, что это не пустые слова. Проверено на себе. Самое интересное то, что вы можете задать для объекта два Skin'а. Один из них будет примерен для видимых граней, второй - для "невидимых" (back) граней. Например, так:

var o:Object3D = new Box (50, 50, 50, 'quad'); // создали куб
o.enableBackFaceCulling = false;// включили отрисовку обратных граней
var sk1 = new MixedSkin (0x00FF00, 100, 0, 1);// создали скин 1
sk1.setLightingEnable(true);// включили поддержку освещения
o.setBackSkin (sk1, false);// указали что это скин для "обратной стороны"
var mc : MovieClip = _root.attachMovie("bar", "bar_0", _root.getNextHighestDepth());
// создали клип на основании добавленного в библиотеку объекта bar
mc._visible = false;
// обязательно спрятать этот клип, чтобы он не мешал демонстрации куба
var sk2 = new MovieSkin (mc);
sk2.setLightingEnable(true);
o.setSkin (sk2, false);
//установили скин как основной.
На рисунке 1 показан процесс вращения куба с двумя скинами, один из которых – это символ с нарисованной буквой A (попробуйте догадаться, почему буква A перевернута).
Последний момент заключается в том, что 3d-мир должен содержать специальную группу объектов, внутри которой находятся все остальные группы и отдельные объекты.
// создаем корневую группу
var bg:Group = new Group ();
//и привязываем ее к 3d-миру
World3D.getInstance ().setRootGroup (bg);
bg.addChild (o);// здесь o – некоторый 3d-объект
На этом, полагаю, введение в Sandy закрытым и привожу полный пример кода, иллюстрирующий все ранее рассмотренные возможности.

// подключаем нужные для работы библиотеки
import sandy.core.data.*;
import sandy.core.group.*;
import sandy.primitive.*;
import sandy.view.*;
import sandy.core.*;
import sandy.skin.*;
import sandy.util.*;
import sandy.core.transform.*;
import sandy.events.*;

var tg = new TransformGroup ();
var frame_num = 0;

function init( Void ):Void
{
var screen:ClipScreen = new ClipScreen( this.createEmptyMovieClip('screen', 1), 550, 400 );
var cam:Camera3D = new Camera3D( 400, screen);
cam.setPosition(200,-100, 500);
cam.lookAt (0,0,0);
World3D.getInstance().addCamera( cam );
var bg:Group = new Group();
World3D.getInstance().setRootGroup( bg );

// создаем объект стол
var stol:Object3D = new Box( 600, 300, 20, 'quad');
// назначаем ему скин в виде сплошной заливки зеленым цветом
stol.setSkin (new SimpleColorSkin (0x00FF00, 256));
var stol_m:Transform3D = new Transform3D ();
// теперь стол надо передвинуть в начало координат, вообще-то по умолчанию все объекты создаются именно в начале координат, но в общем случае необходимо выполнить их перемещение
var stol_t = new TransformGroup ();
// функция translate получает параметры X,Y,Z
stol_m.translate (0, 0, 0);
// стол не может двигаться сам, его нужно обязательно поместить в группу трансформации
// а потом указать для этой группы тот объект типа Transform3D, который нам нужен
stol_t.setTransform (stol_m);
stol_t.addChild (stol);
// добавляем стол в корневую группу трансформации
tg.addChild (stol_t);

// раз четыре ножки стола очень похожи, то пусть будет универсальная фукнция, получающая в качестве параметров: радиус ножки, ее высоту, цвет, а также координаты
var leg_t_1 = makeDeskLeg (20, 150, 0x0000FF, 260, 130, 75);
tg.addChild (leg_t_1);
var leg_t_2 = makeDeskLeg (20, 150, 0x00ACFF, -260, 130, 75);
tg.addChild (leg_t_2);
var leg_t_3 = makeDeskLeg (20, 150, 0x00FFFF, 260, -130, 75);
tg.addChild (leg_t_3);
var leg_t_4 = makeDeskLeg (20, 150, 0xF00AFF, -260, -130, 75);
tg.addChild (leg_t_4);

// для удобства будет создана система осей Ox, OY
var OX = new Line3D (new Vector(-600,0,0), new Vector(+600,0,0) );
OX.setSkin (new SimpleLineSkin (1, 0xFF0000, 255));
bg.addChild (OX);
var OY = new Line3D (new Vector(0,-600,0), new Vector(0,+600,0) );
OY.setSkin (new SimpleLineSkin (1, 0x00FF00, 255));
bg.addChild (OY);
bg.addChild(tg);
// запустить отрисовку мира
World3D.getInstance().render();
}
// функция создания ножки стола
function makeDeskLeg (rad, hei,colo, pos_x, pos_y, pos_z ){
var leg:Object3D = new Cylinder( rad, hei, 8, 'tri');
leg.setSkin (new SimpleColorSkin (colo, 256));
var leg_m_t:Transform3D = new Transform3D ();
var leg_m_r:Transform3D = new Transform3D ();
var leg_t = new TransformGroup ();
leg_m_r.rot (90, 0, 0);
leg_m_t.translate (pos_x, pos_y, pos_z);
leg_m_t.combineTransform(leg_m_r);
leg_t.setTransform (leg_m_t);
leg_t.addChild (leg);
return leg_t;
}
// создаем 3d-мир
init();

// и добавляем обработчик события, который при каждом новом кадре будет вращать стол на некоторый градус, зависящий от номера кадра _root.onEnterFrame = function (){
var t = new Transform3D ();
t.rot (frame_num, 0, 0);
tg.setTransform (t);
frame_num++;
}

Результат работы скрипта показан на рис. 2. Попробуйте самостоятельно добавить на наш стол пару бильярдных шаров или собранную из кубиков модельку игрока. В следующий раз мы продолжим рассмотрение возможностей Sandy. А также в перспективе нас ждут методы интеграции Sandy и какого- либо из физических движков.

black zorro black-zorro@tut.by


Компьютерная газета. Статья была опубликована в номере 20 за 2007 год в рубрике программирование

©1997-2024 Компьютерная газета