Pull to refresh

Silverlight + Augmented reality + Shaders

Reading time 6 min
Views 3K
Привет всем.

Я люблю работать с Silverlight, и вот недавно натолкнулся на Дополненную реальность с использованием Silverlight. Про неё и на Хабре уже писали, и в блогах довольно много написано, но я всё же немного повторюсь. Сама идея дополненной реальности интересна, но я хотел использовать её как-то необычно. И вот недавно руки дошли до изучения пиксельных шейдеров. Стало интересно попробовать соединить это вместе.

Если интересно, что из этого получилось, то Добро пожаловать под кат:


Для начала скачиваем SLARToolkit.

В скачанном архиве содержатся необходимые библиотеки и 3 уже готовых маркера. Есть несколько свособов создания своих маркеров, но для начала хватит и готовых.

Теперь создаём новое Silverlight приложением и добавляем ссылки на SLARTooklit.dll и Matrix3DEx.dll из скачанного нами архива.

Вот XAML разметка моей заготовки

<UserControl x:Class="SilverlightForHabr.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    d:DesignHeight="550" d:DesignWidth="700">

    <Grid x:Name="LayoutRoot" Background="White">
        <StackPanel>
            <TextBlock Text="Silverlight + Augmented reality + Shaders" HorizontalAlignment="Center" />
            <Grid Width="640" Height="480">
                <Rectangle Name="Viewport" Stroke="Black" StrokeThickness="2" />
                <Canvas>
                    <Image Name="Logo" Source="habr.png" Width="300" Height="300" />
                </Canvas>
            </Grid>
            <Button Content="Начнём" Padding="5" Margin="5" HorizontalAlignment="Center" />
        </StackPanel>
    </Grid>
</UserControl>


Далее необходимо получить доступ к камере и вывести изображение с камеры на экран.
Делается это просто:

  1. Создаём объект CaptureSource
  2. Спрашиваем у пользователя разрешение на использование камеры
  3. Выводим картинку


Проще некуда.

Для этого в XAML добавляем ссылку на метод, который вызовется после загрузки и ссылку на обработчик нажатия кнопки:

Loaded="UserControl_Loaded">
и
<Button Content="Начнём" Padding="5" Margin="5" HorizontalAlignment="Center" Click="onStartBtnClick" />


Осталось описать сами методы:

private void UserControl_Loaded(object sender, RoutedEventArgs e)
{
      captureSource = new CaptureSource(); 
      captureSource.VideoCaptureDevice = CaptureDeviceConfiguration.GetDefaultVideoCaptureDevice(); //привязываемся к камере по-умолчанию
 
      var vidBrush = new VideoBrush();
      vidBrush.SetSource(captureSource);  //для кисти указываем видео с камеры как источник
      Viewport.Fill = vidBrush;                     //рисуем кистью на нашем прямоугольнике
}

private void onStartBtnClick(object sender, RoutedEventArgs e)
{
      if (CaptureDeviceConfiguration.RequestDeviceAccess()) //спрашиваем у пользователя, можно ли работать с камерой
      {
          captureSource.Start();//если да, то Поехали!
      }
}


Теперь картинка с камеры выводится на экран.

Приступим к работе с Дополненной реальностью


Принцип работы примерно следующий:

  1. Создаём экземпляр класса CaptureSourceMarkerDetector, который отвечает за обнаружение маркеров во входящем видео с камеры
  2. Если что-то нашли, то вызываем обработчик
  3. Определяем матрицу проекции(положения в пространстве) самого маркера
  4. Применяем эту матрицу к объекту, который хотим наложить поверх маркера


За все эти действия отвечает следующий код:

private void UserControl_Loaded(object sender, RoutedEventArgs e)
{
      captureSource = new CaptureSource();
      captureSource.VideoCaptureDevice = CaptureDeviceConfiguration.GetDefaultVideoCaptureDevice();

      var vidBrush = new VideoBrush();
      vidBrush.SetSource(captureSource);
      Viewport.Fill = vidBrush;


      arDetector = new CaptureSourceMarkerDetector();
      var marker = Marker.LoadFromResource("Marker_L_16x16segments_80width.pat", 16, 16, 80);
      arDetector.Initialize(captureSource, 1, 4000, marker);

      arDetector.MarkersDetected += (s, me) =>
      {
          Dispatcher.BeginInvoke(() =>
          {
              var dr = me.DetectionResults;
              if (dr.HasResults)
              {
                  var centerAtOrigin = Matrix3DFactory.CreateTranslation(-Logo.ActualWidth * 0.5, -Logo.ActualHeight * 0.5, 0);
                  var scale = Matrix3DFactory.CreateScale(0.5, -0.5, 0.5);
                  var world = centerAtOrigin * scale * dr[0].Transformation;

                   var vp = Matrix3DFactory.CreateViewportTransformation(Viewport.ActualWidth, Viewport.ActualHeight);
                   var m = Matrix3DFactory.CreateViewportProjection(world, Matrix3D.Identity, arDetector.Projection, vp);

                   Logo.Projection = new Matrix3DProjection { ProjectionMatrix = m };
              }
          });
      };
}


И конечно, добавляем в решение наш маркер. В данном случае это Marker_L_16x16segments_80width.pat. В его свойстве Build Action указываем Resource.

Теперь единственное, что нам осталось, это распечатать выбранный нами маркер. Для удобства разработчики SLARToolkit положили pdf версию маркера для печати.

Запускаем, и видим, что лого Хабра накладывается поверх маркера и следует за ним.

image

Замечательно!

Шейдеры


В двух словах, шейдер — это программа, написанная на языке HLSL (High Level Shader Language) в котором все действия сводятся к изменению цвета или положения какого-либо пиксела.

HLSL — С-подобный язык, поэтому, чтобы разобраться в нём нужно минимум времени.

Silverlight может работать с пиксельными шейдерами. Это очень хорошо т.к. при условии включения аппаратного ускорения шейдеры выполняются на графическом ядре.

Существует несколько программ, которые позволяют упростить создание шейдеров. Лично мне, больше всего понравилась программа Shazzam т.к. она позволяет сразу просмотреть результат на нескольких изображениях и видео, имеет множество примеров, а самое главное — генерирует C# или VB класс, который позволяет подключить шейдер к нашему проекту в одну строчку кода.

Итак, скачиваем Shazzam с сайта.
Устанавливаем и запускаем.

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

Код самого шейдера


/// <class>ZoomBlurEffect</class>

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

//-----------------------------------------------------------------------------------------
// Shader constant register mappings (scalars - float, double, Point, Color, Point3D, etc.)
//-----------------------------------------------------------------------------------------

/// <summary>The center of the blur.</summary>
/// <minValue>0</minValue>
/// <maxValue>0.2</maxValue>
/// <defaultValue>0.9,0.6</defaultValue>
float2 Center : register(C0);

/// <summary>The amount of blur.</summary>
/// <minValue>0</minValue>
/// <maxValue>0.2</maxValue>
/// <defaultValue>0.1</defaultValue>
float BlurAmount : register(C1);

//--------------------------------------------------------------------------------------
// Sampler Inputs (Brushes, including ImplicitInput)
//--------------------------------------------------------------------------------------

sampler2D  inputSource : register(S0);

//--------------------------------------------------------------------------------------
// Pixel Shader
//--------------------------------------------------------------------------------------

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;
}



Результатом работы Shazzam являются файлы ZoomBlur.ps и ZoomBlurEffect.cs. Добавляем их в наш проект.
Первый файл — готовый шейдер. Второй — автоматически сгенерированный класс, который облегчает использование шейдера.

Добавим ещё одну кнопку, которая будет включать наш шейдер:

<Button Content="Включить шейдер" Name="ShadersBtn" Padding="5" HorizontalAlignment="Center" Click="SwitchOnShaders" />


В код добавляем обработчик:

private void SwitchOnShaders(object sender, RoutedEventArgs e)
{
    if (Viewport.Effect == zb)
    {
        Viewport.Effect = null;
        ShadersBtn.Content = "Включить шейдер";
        return;
    }

    zb.Center = new Point();
    zb.BlurAmount = 0.2;

    Viewport.Effect = zb;
    ShadersBtn.Content = "Отключить шейдер";
            
}


Теперь, при перемещении маркера меняется размытие.

Послесловие

Прелесть этих технологий в том, что они ограничены лишь вашей фантазией, поэтому давайте играть с ними.

Линки

Линк на скачивание исходников
Посмотреть как это работает
SLARToolkit — SDK для начала игр с дополненной реальностью
Shazzam — приложение для комфортного создания шейдеров под Silverlight и WPF.
Tags:
Hubs:
+22
Comments 14
Comments Comments 14

Articles