Главная
Блог разработчиков phpBB
 
+ 17 предустановленных модов
+ SEO-оптимизация форума
+ авторизация через соц. сети
+ защита от спама

Добавляем результат Motion Blur в WPF-приложения

Anna | 17.06.2014 | нет комментариев
Здравствуй, Прогр!
Все мы много раз слышали фразу о том, что 24 кадра в секунду — это максимально допустимое значение, которое способно воспринимать человеческое зрение. Следственно, если ускорить видеоряд каждого на один ненужный кадр, дозволено внедрить в подсознание зрителя всякую информацию. И все мы, безусловно, знаем, что это выдумка. Так же, как и фотодиоды матрицы цифровых фотоаппаратов, нейроны сетчатки фиксируют не мгновенную освещённость в данной точке, а суммарный световой поток за определенный короткий промежуток времени, в итоге чего быстродвижущиеся объекты кажутся нам «смазанными». Больше того, наш мозг привык к такой особенности зрения, следственно видео, скомпонованное из отдельных фотографий объекта, нам кажется противоестественным. То же самое касается и компьютерной анимации. Художники-мультипликаторы теснее давным-давно обучились дорисовывать размытые шлейфы за своими персонажами — такой приём именуется «Motion blur» и доступен во всех современных пакетах 2d- и 3d-анимации. Но как быть обыкновенным desktop-программистам? В этой статье я попытаюсь рассказать о том, как я прикручивал Motion Blur к WPF-приложению для придания результата отзывчивости при возникновении всплывающих окон.

Для начала, предлагаю взглянуть на две картинки. число кадров в них идентичное.

Обыкновенная анимация Motion Blur
Окно без Motion Blur
Окно с Motion Blur

Если вы не видите разницы, дозволено считать, что я потратил несколько вечеров впустую. Но хочется верить, что разница всё же приметна :)

А вот как это выглядит в покадровой развёртке:

Обыкновенная анимация
Motion Blur

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

Заглянем под капот?

Пиксельные шейдеры

Реализовать нетормозящий Motion Blur результат вряд ли допустимо даже на самых сильных современных центральных процессорах, следственно ключевую роль в отрисовке размытого «следа» в моём примере играет GPU. Вследствие поддержке в WPF пиксельных шейдеров, мы можем использовать к визуальным элементам разные результаты, в том числе тени, distortion-результаты (лупа, сплетение, рябь), метаморфоза цветового равновесия, размытие и т.п.
Если вам кажется, что шейдеры — это что-то ужасное и трудное, я с вами всецело согласен. До недавнего времени я был уверен, что никогда в жизни с ними не столкнусь, если только не пойду в game-dev. Но оказалось, что и в прикладном программировании они тоже могут сгодиться. При этом вовсе не непременно знать специализированные языки для написания шейдеров, такие как GLSLHLSL и т.д. На просторах интернета теснее давным-давно существует уйма готовых примеров шейдеров, один из которых я и применял. Именуется он ZoomBlurEffect и входит в поставку демо-шейдеров бесплатного редактора «Shazzam Shader Editor». Вот его код:

ZoomBlurEffect.fx

/// <class>ZoomBlurEffect</class>
/// <description>An effect that applies a radial blur to the input.</description>

sampler2D  inputSource : register(S0);

/// <summary>The center of the blur.</summary>
float2 Center : register(C0);

/// <summary>The amount of blur.</summary>
float BlurAmount : register(C1);

float4 main(float2 uv : TEXCOORD) : COLOR
{
	float4 c = 0;    
	uv -= Center;

	for (int i = 0; i < 15; i  )  {
		float scale = 1.0   BlurAmount * (i / 14.0);
		c  = tex2D(inputSource, uv * scale   Center);
	}

	c /= 15;
	return c;
}

Даже не зная языка HLSL, на котором написан данный шейдер, дозволено легко осознать алгорифм его работы: для всякой точки финального изображения вычисляется усредненное значение цвета 15-ти точек, расположенных на прямой, проходящей между данной точкой и центром размытия, хранящимся в регистре C0. Удалённость усредняемых точек от данной точки регулируется параметром BlurAmount, хранящемся в регистре C1. В нашем примере размытие происходит из центра изображения, следственно C0 равен (0.5;0.5), а значение параметра BlurAmount зависит от того, насколько крепко нынешний кадр отличается от предыдущего, но об этом чуть позднее.
Безусловно, в таком виде шейдер применять не получится — его нужно скомпилировать с поддержкой утилиты fxc.exe, входящей в состав DirectX SDK. Итогом компиляции пиксельного шейдера является файл с растяжением “.ps”, тот, что может быть использован в нашем WPF-приложении. Для этого добавим его в наш план в качестве источника и сделаем класс ZoomBlurEffect:

ZoomBlurEffect.cs

    /// <summary>An effect that applies a radial blur to the input.</summary>
    public class ZoomBlurEffect : ShaderEffect
    {
        public static readonly DependencyProperty InputProperty =
            RegisterPixelShaderSamplerProperty("Input", typeof (ZoomBlurEffect), 0);

        public static readonly DependencyProperty CenterProperty =
            DependencyProperty.Register("Center", typeof (Point), typeof (ZoomBlurEffect),
            new UIPropertyMetadata(new Point(0.9D, 0.6D), PixelShaderConstantCallback(0)));

        public static readonly DependencyProperty BlurAmountProperty =
            DependencyProperty.Register("BlurAmount", typeof (double), typeof (ZoomBlurEffect),
            new UIPropertyMetadata(0.1D, PixelShaderConstantCallback(1)));

        public ZoomBlurEffect()
        {
            PixelShader = new PixelShader
            {
                UriSource = new Uri(@"pack://application:,,,/ZoomBlurEffect.ps", UriKind.Absolute)
            };

            UpdateShaderValue(InputProperty);
            UpdateShaderValue(CenterProperty);
            UpdateShaderValue(BlurAmountProperty);
        }

        public Brush Input
        {
            get { return ((Brush) (GetValue(InputProperty))); }
            set { SetValue(InputProperty, value); }
        }

        /// <summary>The center of the blur.</summary>
        public Point Center
        {
            get { return ((Point) (GetValue(CenterProperty))); }
            set { SetValue(CenterProperty, value); }
        }

        /// <summary>The amount of blur.</summary>
        public double BlurAmount
        {
            get { return ((double) (GetValue(BlurAmountProperty))); }
            set { SetValue(BlurAmountProperty, value); }
        }
    }

(На самом деле, Shazzam Shader Editor сам может генерировать сходственные классы-обёртки для шейдеров, чем я и воспользовался.)

Анимация при возникновении окна

У всякого визуального элемента имеется качество RenderTransform, которое применяется графической подсистемой для трансформации элемента во время его отрисовки. К таким трансформациям относятся масштабирование, вращение и наклон. В нашем примере мы будем изменять масштаб контента окна от нуля (контент «свёрнут» в точку) до единицы (контент растянут на всё окно). Само окно при этом имеет прозрачный фон, а отрисовка хрома (рамки с заголовком) у него отключена.
Для анимации в WPF обычно применяются так называемые «функции плавности». Мы можем применять предопределённые функции либо написать свои, унаследовавшись от класса EasingFunctionBase.
В примере из данной статьи я применял функцию ElasticEase, которая придаёт окну результат «отпущенной пружины» — сначала оно круто расширяется до размеров, немножко превосходящих установленные, а после этого плавно уменьшается.

Псевдокод анимации возникновения окна без результата Motion Blur

double t = 0.0;
int ВремяНачалаАнимации = ТекущееСистемноеВремя;
while (t < 1.0)
{
  УстановитьМасштабКонтента(ElasticEase(t));
  t = (ТекущееСистемноеВремя - ВремяНачалаАнимации) / ПродолжительностьАнимации;
}
УстановитьМасштабКонтента(1.0);

Тут t изменяется в пределах от 0 до 1, где 0 — момент начала анимации, а 1 — момент её окончания. Значение функции ElasticEase(t) изменяется приблизительно по такому закону:

Добавим motion-blur к нашей анимации. Для этого используем качество Effect у дочернего контрола окна:

content.Effect = new ZoomBlurEffect { Center = new Point(0.5, 0.5) };
Псевдокод анимации с результатом Motion Blur

double t = 0.0;
double prevEase = 0.0;
int ВремяНачалаАнимации = ТекущееСистемноеВремя;
УстановитьЭффектКонтента(new ZoomBlurEffect { Center = new Point(0.5, 0.5) });
while (t < 1.0)
{
  var ease = ElasticEase(t);
  УстановитьМасштабКонтента(ease);
  content.Effect.BlurAmount = ease - prevEase;
  prevEase = ease;
  t = (ТекущееСистемноеВремя - ВремяНачалаАнимации) / ПродолжительностьАнимации;
}
УстановитьМасштабКонтента(1.0);
УстановитьЭффектКонтента(null);

Различие данного кода от предыдущего в том, что на всяком шаге мы изменяем значение BlurAmount в зависимости от того, на сколько крепко нынешнее значение функции ElasticEase отличается от значения на предыдущем шаге. В начале анимации функция растёт стремительно, BlurAmount имеет достаточно огромное значение, следственно и «смазанность» окна огромная. В конце — BlurAmount фактически равен нулю, а значит и «смазанность» примерно не приметна.

О недостатках

К сожалению, использование результата Motion Blur в WPF-приложениях вызывает некоторые задачи. Вот некоторые из них:

  • Продуктивность. Как показала практика, даже GPU не всесилен. По итогам тестов, добавление zoom-результата к окну замедляет рендеринг приблизительно в 1.5-2 раза (на моей видео-карте). Впрочем, от того что визуально кажется, что FPS значительно усилился, это не кажется мне огромный загвоздкой.
  • Не ясно, для чего это вообще необходимо :) Я проводил опрос среди друзей, видят ли они разницу между поведением с включенным результатом и без него. И все вначале сказали, что разницы нет. Позже указания, на что определенно необходимо глядеть, огромная часть выразила wow-результат, но иная часть сказала, что без результата значительно отменнее и чётче. Их суждение тоже необходимо рассматривать.
Завершение

К сожалению, я так и не довёл идею применения результата Motion Blur до состояния production-кода, от того что она едва ли применима в тех приложениях, которыми мне доводится заниматься. Делал, так сказать, для души. Верю, что данный материал окажется кому-то пригоден в его работе.

Скачать демонстрационный план дозволено отсель: github.com/misupov/motion-blur

Источник: programmingmaster.ru
Оставить комментарий
Форум phpBB, русская поддержка форума phpBB
Рейтинг@Mail.ru 2008 - 2017 © BB3x.ru - русская поддержка форума phpBB