-->

Как создать shader в Flutter для эффектной анимации в приложении?

Как создать shader в Flutter для эффектной анимации в приложении?

Hola, Amigos! Меня зовут Сергей Климович, я Mobile Team Lead агентства заказной разработки Amiga. В мире мобильной разработки Flutter выделяется своей гибкостью и простотой в создании красивых пользовательских интерфейсов. С помощью шейдеров можно добавить дополнительные визуальные эффекты. Об этом я сегодня и расскажу.


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


Если вам интересна кроссплатформенная мобильная разработка, фреймворк Flutter, опыт других разработчиков, то приглашаю вас в телеграм-калан Flutter.Много.

(Анимация и Flutter)

Во Flutter уже встроены несколько шейдеров, которые используются в классах LinearGradient, RadialGradient, SweepGradient, ImageShader и т.д. Данные шейдеры мы можем извлечь из этих объектов и использовать их в таких классах, как ShaderMask или CustomPaint. Но что если нужно создать уникальные градиенты, теневые эффекты или реалистичные анимации. 


Можно создать собственный шейдер, сделав несколько манипуляций:


  1. Написать шейдер на языке GLSL и поместить его в проект.
  2. Скомпилировать шейдер внешним компилятором в файл SPIR‑V.
  3. Загрузить файл SPIR‑V во Flutter.
  4. Скомпилировать во Flutter SPIR‑V файл.
  5. Создать шейдер из ранее скомпилированного файла SPIR‑V.
  6. Передать этот шейдер в CustomPaint.

Чтобы создать шейдер, нам нужно написать код на GLSL. GLSL — это язык шейдеров высокого уровня, который используется для программирования графического процессора (GPU). 


Давайте пропустим часть кодирования GLSL и выберем уже закодированный GLSL и изменим его, чтобы он работал с нашим приложением Flutter.

(Создание шейдера)

Создадим файл shader.frag и скопируем код: 

https://www.shadertoy.com/view/fd33zn 


Затем добавим в начало:

#include  // импорт среды выполнения Flutter

uniform vec2 uSize; // универсальная переменная, в которой хранится размер визуализируемого объекта

uniform float iTime; // универсальная переменная, в которой хранится время, прошедшее с момента запуска шейдера

vec2 iResolution; // переменная, в которой хранится разрешение экрана

out vec4 fragColor; // выходная переменная, в которой хранится окончательный цвет визуализируемого объекта

Переименуем mainImage в main и заменим параметры на void.


Добавим две переменные внутри:

iResolution = uSize;

       vec2  fragCoord  = FlutterFragCoord();

(Интеграция во Flutter)

Чтобы не писать много кода для шиммера я просто форкну flutter_shimmer. Создадим отдельный компонент, который будет принимать дочерний элемент и шейдер. Затем мы наложим полученный шейдер поверх него.

class ShimmerFromShader extends StatefulWidget {

  final Widget child;

  final FragmentShader shader;

Далее создаем SingleChildRenderObjectWidget для получения объекта рендеринга, принадлежащий дочернему виджету.


Сам рендер будет происходить в _ShimmerFilter, где мы создаем ShaderMaskLayer и на него будем накладывать наш шейдер. В настройках таймера можно поиграть со скоростью вращения.

class _ShimmerFilter extends RenderProxyBox {

 FragmentShader shader;

 double _time;

 _ShimmerFilter(this.shader, this._time);



 @override

 ShaderMaskLayer? get layer => super.layer as ShaderMaskLayer?;



 @override

 bool get alwaysNeedsCompositing => child != null;



 set time(double newValue) {

   if (newValue == _time) {

     return;

   }

   _time = newValue;

   markNeedsPaint();

 }



 @override

 void paint(PaintingContext context, Offset offset) {

   if (child != null) {

     assert(needsCompositing);



     layer ??= ShaderMaskLayer();

     shader.setFloat(0, size.width);

     shader.setFloat(1, size.height);

     shader.setFloat(2, _time);

     layer!

       ..shader = shader

       ..maskRect = Offset.zero & size

       ..blendMode = BlendMode.srcIn;

     context.pushLayer(layer!, super.paint, offset);

   } else {

     layer = null;

   }

 }

(Далее сам пример)

Добавьте шейдер в Flutter pubspec.yaml:

 flutter:

 shaders:

   - shaders/shader.frag

Создаем объект шейдера:

Future loadMyShader() async {

   final program = await FragmentProgram.fromAsset('shaders/shader.frag');

   shader = program.fragmentShader();

   return shader!;

 }

И далее передаем в созданный ранее виджет, используя в качестве placeholderов виджеты из форкнутого пакета:

ShimmerFromShader.fromShader(

                 shader: snapshot.data!,

                 child: const SingleChildScrollView(

                   physics: NeverScrollableScrollPhysics(),

                   child: Column(

                     crossAxisAlignment: CrossAxisAlignment.start,

                     mainAxisSize: MainAxisSize.max,

                     children: [

                       BannerPlaceholder(),

                       TitlePlaceholder(width: double.infinity),

                       SizedBox(height: 16.0),

                       ContentPlaceholder(

                         lineType: ContentLineType.threeLines,

                       ),

                       SizedBox(height: 16.0),

                       TitlePlaceholder(width: 200.0),

                       SizedBox(height: 16.0),

                       ContentPlaceholder(

                         lineType: ContentLineType.twoLines,

                       ),

                       SizedBox(height: 16.0),

                       TitlePlaceholder(width: 200.0),

                       SizedBox(height: 16.0),

                       ContentPlaceholder(

                         lineType: ContentLineType.twoLines,

                       ),

                     ],

                   ),

                 ));

Полный код можно посмотреть тут.

(Заключение)

В мире Flutter шейдеры предоставляют захватывающие возможности для создания уникальных и впечатляющих пользовательских интерфейсов. Они могут стать мощным инструментом в ваших руках, наполняйте свои проекты индивидуальностью и выразительностью!


Переходите в наш телеграм-канал, там еще больше интересных и полезных новостей от нашей Flutter-команды.

Хотите связаться с владельцами компании напрямую?
Константин Франгуриди
Константин Франгуриди
Account director

НАПИСАТЬ

Дмитрий Тарасов
Дмитрий Тарасов
СЕО

НАПИСАТЬ