Эффект электронно-лучевой трубки на Stage3D

Здравствуйте, дорогие друзья!

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

Эмулятор делался по мотивам игры You Have to Win the Game (не флэш), в целом вполне работоспособен (хотя до уровня YHtWtG ему еще далеко :)), но, как справедливо заметил в комментариях FreeS, не является эмулятором в полной мере (т.е. не имитирует работу реального устройства, а лишь реализует ряд эффектов постобработки) и представляет собой эдакий комбайн из шейдеров (по большей части основанных на шейдерах Themaister'а), которые последовательно применяются к входному изображению, подаваемому на вход многопроходного рендера.

Для демонстрации воспользуемся простейшим платформером на Flixel, внеся в него самую малость дополнений.

Оригинальная картинка из этого платформера:



Она же, выводимая через эмулятор:



Функционал эмулятора реализован в классе TV. Для создания экземпляра используется конструктор:

public function TV(
   displayObject
:DisplayObject,
   inputWidth
:int,
   inputHeight
:int,
   outputWidth
:int,
   outputHeight
:int)


Параметры:
displayObject:DisplayObject — любой объект Flash, при добавлении его ко стейджу будет инициализирован контекст Stage3D;
inputWidth:int — ширина входного изображения;
inputHeight:int — высота входного изображения;
outputWidth:int — ширина выходного изображения, выводимого на экран;
outputHeight:int — высота выходного изображения, выводимого на экран.

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

private var _tv:TV;
...
_tv
= new TV(this, 320, 240, 640, 480);
...


Для того же, чтобы отправить картинку из игры на рендер, достаточно вызвать метод present():

public function present(image:BitmapData):void


При этом из содержимого объекта image будет использована прямоугольная область изображения размером (0, 0, inputWidth, inputHeight).

Для настройки эмулятора могут быть использованы следующие его свойства (properties):

x:int (read/write)
y:int (read/write)
определяют положение (в оконных координатах) левого верхнего угла области экрана, используемой Stage3D для вывода изображения;

distortion:Number (read/write)
определяет степень искажения (скругления углов) выводимой картинки, значение 0 задает отсутствие искажений, по умолчанию используется значение 0.2;

brightness:Number (read/write)
определяет общую яркость экрана эмулятора, 1 (по умолчанию) — полная яркость, 0 — минимальная яркость;

scanlineMode:uint (read/write)
определяет интенсивность выделения скан-линий, задается одной из констант (от наименьшей интенсивности к наибольшей) TV.SCANLINE_MODE_LIGHT, TV.SCANLINE_MODE_MEDIUM (по умолчанию), TV.SCANLINE_MODE_DARK;

inputGamma:Number (read/write)
outputGamma:Number (read/write)
значения влияют на цветокоррекцию во время финального прохода рендера, значения по умолчанию соответственно: 2.4 и 2.2;

cellGlowSize:Number (read/write)
cellGlowPower:Number (read/write)
значения используются при имитации эффекта блика от светящегося зерна люминофора (область Cell glow на картинке ниже), свойство cellGlowPower определяет степень яркости блика, а свойство cellGlowSize — размер блика (если хотя бы одно из этих свойств равно 0, то проход рендера, имитирующий этот эффект, выключается; по умолчанию каждое свойство равно 0);

glassGlowSize:Number (read/write)
glassGlowPower:Number (read/write)
значения используются при имитации эффекта обширных относительно неярких бликов на стекле кинескопа, возникающих вокруг мест скопления в больших количествах ярко светящихся зерен (область Glass glow на картинке ниже); свойство glassGlowSize определяет размер бликов, glassGlowPower — яркость свечения (если хотя бы одно из этих свойств равно 0, то проход рендера, имитирующий этот эффект выключается; по умолчанию каждое свойство равно 0);



noisePower:Number (read/write)
определяет степень проявления шума в финальном изображении, значение 0 (по умолчанию) задает отсутствует шума;

isHardware:Boolean (read only)
если значение свойства равно true, то эмулятором используется аппаратное ускорение, если false — то используется программный рендер Flash;

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

gaussianPasses:uint (read/write)

Чем больше значение gaussianPasses, тем более качественным («мягким») получается результат, но в то же время растет и общее количество проходов рендера, выполняемых эмулятором. Значение 0 (по умолчанию) отключает применение размытия по Гауссу, и, следовательно, имитацию области Glass glow.

Использование эмулятора совместно с Flixel
Во Flixel растровый рендер сцены осуществляется в буфер камеры (камер). При этом, по умолчанию всегда доступна минимум одна камера (FlxG.camera). Отправить содержимое буфера этой камеры в эмулятор можно следующим образом:

_tv.present(FlxG.camera.buffer);


Но перед этим саму камеру необходимо вынести за пределы экрана:

FlxG.camera.x = -1000;


Лучше всего это делать внутри переопределенного метода create() в классах-потомках FlxState, а отправлять сцену на рендер — внутри класса Main (потомке FlxGame) в переопределенном методе draw(), не забыв перед этим вызвать super.draw().

В общем виде:

import org.flixel.*;

[SWF(width="640", height="480", backgroundColor="#000000")]
public class Main extends FlxGame
{
   
private var _tv:TV;
   
   
public function Main()
   
{
     
super(320, 240, SomeState, 1, 30, 30);
      _tv
= new TV(this, FlxG.width, FlxG.height, FlxG.width * 2, FlxG.height * 2);
   
}

   
override protected function draw():void
   
{
     
super.draw();
      _tv
.present(FlxG.camera.buffer);
   
}
}

public class SomeState extends FlxState
{
   
override public function create():void
   
{
     
FlxG.camera.x = -1000;
   
}
}


Использование эмулятора совместно с FlashPunk
Для растрового рендера сцены во FlashPunk также используется буфер (FP.buffer), содержимое которого можно отправить в эмулятор следующим образом:

_tv.present(FP.buffer);


Однако потребуется немного доработать класс net.flashpunk.Screen, добавив в него следующий метод:

public function hide():void
{
   _sprite
.visible = false;
}


В общем виде:

import net.flashpunk.*;

public class Main extends Engine
{
   
private var _tv:TV;  

   
public function Main()
   
{
     
super(320, 240, 30);
      _tv
= new TV(this, 320, 240, 640, 480);
   
}

   
override public function init():void
   
{
      FP
.screen.hide();
   
}

   
override public function render():void
   
{
     
super.render();
      _tv
.present(FP.buffer);
   
}
}


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

Производительность
При наличии аппаратного ускорения производительность сохраняется вполне на уровне:


(6 проходов рендера на каждый ENTER_FRAME)

То же, но при софтварном рендере Stage3D:



Исходники в проекте для FlashDevelop (эмулятор + платформер)

Благодарю за внимание!
  • +21

Комментарии (7)

+2
прямо слеза навернулась. Спасибо
+1
Прикольно, хороший результат.
Но вот эмулятором я бы это не называл. Всё таки, эмулятор должен эмулировать работу устройства, а тут просто постэффект.
Вот если бы вы лучи пускали и всё такое, а так, просто эффект.
  • FreeS
  • FreeS
+1
Относительно постэффекта полностью согласен.
Я подкорректировал немного пост, дабы не вводить читателей в заблуждение :)
0
а можешь дописать как к старлингу его элегантно привязать?
0
как вариант использовать ScanlineFilter для старлинга, правда он не такой навороченный, но там всего полстраницы кода )
Его и еще кучу фильтров, можно взять тут
0
github.com/devon-o/Starling-Filters
0
Честно говоря, о том, как работает старлинг я имею лишь самое общее представление, основанное на догадках. По идее, надо копать в сторону фильтров, по аналогии с теми, что указал Al-Rado.
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.