Зажигаем свет в 2d.
Здравствуйте. Хочу поделиться с вами, одним из вариантов, как можно создать 2d свет на actionscript 3.0. Сначала хотел сделать подробный урок для начинающих, но объяснять все свои действия оказалось настолько сложно, что я решил просто выложить свою наработку и кратко рассказать о том, как она работает и как ей пользоваться.
Свет можно делать и мягким и чётким. Пожалуй лучше всего, увидеть это на деле. В обоих флешках, подвигайте мышкой. К сожалению, лично у меня в браузере, обе подтормаживают.
Мягкий свет с прозрачными тенями :
и чёткий свет с непрозрачными тенями:
Немного теории. Свет будет идти из одной точки во все стороны. Для того чтобы свет не проникал сквозь объекты, надо сначала вычислить тень от объекта и просто не пускать в её область свет. Ведь тень – это отсутствие света.
Вид тени прямиком зависит от внешнего вида объекта, который её отбрасывает. Точнее от силуэта объекта или контура, как вам удобнее. По этому этот контур придётся как-то обозначить, но об этом чуть позже.
Возьмём самый простой вариант – найдём тень от простой линии. У линии есть точки A и B. Находим для каждой точки угол к свету. Затем проецируем дополнительную точку на нужное нам расстояние в направлении найденного угла и получаем – границы тени. Осталось только нарисовать из полученных четырёх точек – прямоугольную фигуру и тень готова!
Теперь осталось лишь обложить нужный объект по его контуру такими линиями (далее рёбрами) и всё.
Для создания таких рёбер и служит класс ShadowObject. К сожалению, он не создаст автоматически рёбра по контуру объекта, он может лишь составить рёбра из переданных ему точек. По этому нахождения этих точек, лежит полностью на ваших плечах, но это не так и сложно. ShadowObject – не визуальный класс. Это просто набор данных.
Ещё у этого класса, есть ссылка на любой визуальный объект, которому должен принадлежать этот контур из рёбер. Таких контуров в ShadowObject–е можно создавать сколько угодно. И визуальных объектов тоже можно передавать без ограничений. Ссылка на визуальный объект нужна для того, чтобы тень корректно просчитывалась, не завися от вложенности объектов и их трансформации. Например, если вы хотите сделать тени от ста одинаковых квадратов, то вам достаточно создать только один ShadowObject для этого, передать ему четыре точки с координатами углов квадрата и ссылки на все визуальные квадраты. Сами же визуальные квадраты можете перемещать, изменять их масштаб, поворачивать, добавлять в другие объекты, но это не повлияет на формирование тени от них. Подробнее о классе ShadowObject, я напишу ниже.
Второй класс, это класс света – Light. Это визуальный класс, который и нарисует сам свет. Его мы добавляем на сцену и перемещаем, как лампочку. Сам свет – это будет растровое изображение, по этому, чем больше радиус для света вы задаёте, тем дольше идут вычисления. Так же этому классу вы передаёте ссылки на все ShadowObject-ы, которые он должен обработать. Главный метод класса Light – это renderLight() и я очень вкратце опишу, что он делает.
1) Сначала просчитывает тени от всех рёбер, всех ShadowObject-ов, которые ему передали. С условием, что это ребро попадает в занимаемую область света (его ограничивающий прямоугольник). Точнее, если хоть одна из точек ребра попадает в эту область, то тень от этого ребра просчитывается. И рисует тени в спрятанном шейпе, с помощью обычных методов graphics.
2) Затем делается растровый снимок, этого шейпа с тенями. Получаем прозрачную картинку с чёрными тенями.
3) Инвертируем альфу этой картинки и получается всё наоборот. Всё что было прозрачное, стало видимое, а на месте теней – образовались дырки. Своего рода маска для рисунка светового пятна.
4) И на полученное изображение, налаживаем картинку света. По умолчанию – это радиальный градиент с прозрачными краями. Но можно и указать своё, если нужно. Вот собственно и всё.
А теперь опишу все публичные свойства и методы этих двух классов, чтобы было понятно, как этим всем пользоваться. Начну с
ShadowObject.
Манипуляции с передачей ссылок на дисплейОбъекты:
Добавляете дисплейОбъекты, относительно которых будут рассчитываться тени от всех рёбер этого ShadowObject-a.
Тоже самое что выше, но только наоборот.
Удалить все ссылки на все объекты.
Способы создания рёбер, названия говорят сами за себя:
Добавляет одно ребро, от одной точки ко второй.
Добавить цепочку из рёбер. Вы передаёте массив точек, и по ним строятся рёбра. От первой точки ко второй, от второй к третей и т.д. При надобности, вы можете создать дополнительно ребро, от последней точке к первой.
Добавить прямоугольник из рёбер, заданной ширины и высоты.
Добавить круг из рёбер. Чем больше сегментов, тем круглее.
Удалить все рёбра, которые были добавлены в этот ShadowObject.
Light
Конструктор:
Light(radius:uint=200, colorA:uint = 0xFFFFFF, colorB:uint = 0xFFFFFF, shadowAlpha:Number=1, quality:Number=1, shadowDistance:int = 3000)
colorA и colorB – Так как по умолчанию, картинка для света – это радиальный градиент, с прозрачными краями, то указываем два цвета для него. Цвет от середины, и цвет к краю. По умолчанию оба цвета – белые. Так же задаются только при создании.
shadowAlpha – Альфа каждой отдельной тени от ребра. По умолчанию, все тени непрозрачны. Если добавить прозрачность, то получается эффект наложения теней, что более реалистично в некоторых моментах, но от этого и дольше просчёт. Это значение можно потом поменять.
quality – Качество изображения света (его растрового изображения). Обычно, качество со значением 0.5, намного повышает производительность и мало заметна на качестве картинки. При очень низких значениях, изображение света будет пикселизироваться. Очень важный параметр, влияющий на скорость просчёта. Задаётся только при создании.
shadowDistance – Длина тени от рёбер. Обычно остаётся не изменой. Для чего я вывел этот параметр в конструктор, пока точно не знаю, но обещаю подумать.
Методы:
– Производит все расчёты с тенями и рисует растровый свет.
Манипуляции с ShadowObject-ами
– добавляет ShadowObject в список. И теперь, свет будет брать его рёбра в расчёт теней тоже.
– удалить ShadowObject из списка. Теперь свет не будет просчитывать тени от его рёбер.
– удаляет все ShadowObject-ы из своего списка и просчитывать тени больше не от чего.
Другие методы:
– вместо цветного света (радиального градиента, который рисуется по умолчанию), можно добавить своё изображение. Так как прозрачность тоже учитывается, то желательно чтобы это изображение к краям, плавно становилось прозрачным. Если при создании света, quality было ниже единицы, то данное изображение так же будет ухудшено в качестве.
– размытие для света. Не влияет на цветное изображение света. Чем больше размытие, тем дольше просчёт. По умолчанию - 0, размытия нет. При низких значения качества (quality) света в конструкторе при создании, можно добавить размытия, для более сглаженного варианта.
– определяет сколько света, попадёт на тени. Если 0 – то в тенях света не будет, если 1 – то тени полностью освещены.
На этом описание публичных методов завершено. Осталось лишь одно свойство:
shadowAlpha – альфа для каждой отдельной тени от ребра. При небольших значениях, тени налаживаются друг на друга, что выглядит реалистичнее. Но требует опять же дольше просчёта.
Описание закончено. Теперь попробуем что нибудь сделать. Давайте создадим 100 квадратов, разбросаем их по сцене и включим свет.
package { import flash.display.Shape; import flash.display.Sprite; import flash.events.Event; import LightEffect.Light; import LightEffect.ShadowObject; /** * ... * @author Samana */ public class Main extends Sprite { //создаём свет с низким качеством private var light:Light = new Light(500, 0xFFFFFF, 0xFF0080, 1, 0.3); //создаём теневой объект для квадрата private var shadowBox:ShadowObject = new ShadowObject(); public function Main():void { stage.color = 0x2C1B30; //создаём 100 квадратов и раскидываем их по сцене, //и меняем у них повороты и размеры for (var i:int = 0; i < 100; i++) { var box:Shape = new Shape(); box.graphics.beginFill(Math.random() * 0xFFFFFF); box.graphics.drawRect( -20, -20, 40, 40); box.graphics.endFill(); box.x = Math.random() * 800; box.y = Math.random() * 600; box.scaleX = Math.random(); box.scaleY = Math.random(); box.rotation = Math.random() * 360; addChild(box); //и передаём теневомуОбъекту, ссылку на шейп shadowBox.addToDisplayObject(box); } //добавляем в теневойОбъект прямоугольную область, //с такими же размерами, как у созданных выше шейпов. shadowBox.addEdgeRect( -20, -20, 40, 40); //добавляем в свет - теневойОбъект light.addShadowObject(shadowBox); //немного размываем свет light.lightBlur(2); //меняем режим наложения для красовы light.blendMode = "add"; //добавляем свет на сцену addChild(light); addEventListener(Event.ENTER_FRAME, enterFrame); } private function enterFrame(e:Event):void { light.x = mouseX; light.y = mouseY; //рендерим свет light.renderLight(); } } }
source.rar
Всего комментариев 31
Комментарии
11.12.2013 11:06 | |
Результат очень нравится
|
11.12.2013 13:53 | |
Psycho Tiger, спасибо!
|
11.12.2013 13:56 | |
Samana, очень круто.
Единственное, — для света блендмод бы + нелинейный градиент (я про вторую флэшку). |
11.12.2013 14:32 | |
Hauts, спасибо. Про blendMode честно говоря совсем забыл. А градиент там и так нелинейный выставлен, точнее значение по умолчанию, которое = "rgb".
|
11.12.2013 15:28 | |
Hauts, а, вот оно что. Я даже не знал, что этих точек можно делать больше, чем кол-во цветов. Спасибо за подсказку, узнал новое для себя!
|
11.12.2013 15:42 | |
Да не за что.
Вот картинка (тут, почему-то, нельзя вложения добавлять, надеюсь ссылка не сломается) http://cs409829.vk.me/v409829713/50d2/StfAWlk7p9Y.jpg — справа пример, как было бы лучше. Обрати внимание, что у второго ползунка значение прозрачности 51, хотя положение в ~20. |
11.12.2013 15:55 | |
Hauts, отлично! То, что надо! Сам же думал, "а как бы сделать градиент помягче, где настройки?", но почему-то не находил ответа. А оказывается он был под носом.
|
11.12.2013 21:26 | |
Крутячая штука. Как у нее с производительностью?
|
12.12.2013 00:17 | |
Супер.
|
12.12.2013 01:17 | |
Офигенно. Вдохновлялся статьями хабра?
|
14.12.2013 17:49 | |
Чума!!!
|
14.12.2013 18:19 | |
Zebestov, спасибо
|
14.12.2013 22:33 | |
Может, стоит выложить проект на Github?
|
14.12.2013 22:47 | |
MikroAcse, а что, выглядит настолько серьёзно? Хм... подумаю. Идея хорошая, спасибо.
|
14.12.2013 23:01 | |
samana, по-моему, очень даже
|
16.12.2013 14:23 | |
Спасибо, очень ценный материал. Недавно возникал такой вопрос с тенями, и не знал что делать, буду рад увидеть этот проект на гитхабе
|
16.12.2013 14:38 | |
Можешь попробовать вылить на bitbucket.org
С ним попроще разобраться. |
17.12.2013 19:50 | |
Давай, давай, учи англицкий! )
|
18.12.2013 02:45 | |
Придётся
|
19.12.2013 00:40 | |
Крутая штучка !
|
19.12.2013 01:03 | |
djyamato, спасибо!
|
15.02.2014 16:49 | |
Шикарно получилось. Особенно LightEdges понравилась с этими частицами.
|
15.02.2014 16:54 | |
Спасибо за отзыв, ashkart!
|
16.01.2015 05:51 | |
Еще раз захожу, чтобы сказать спасибо.
|
21.01.2015 12:12 | |
класс..можно , наверно, еще добавить учет светопропускаемости (можно на альфе базировать, как вариант)
|
23.01.2015 15:21 | |
Ух ты, спасибо, ребята!!!
cleptoman, а похожая возможность там есть! За это отвечает параметр shadowAlpha в конструкторе Light. |
Последние записи от samana
- Motion Path (23.01.2015)
- RastrMovieClip или свой велосипед (13.06.2014)
- Зажигаем свет в 2d. (11.12.2013)