Flutter自定义着色器插件shader_presets的使用

发布于 1周前 作者 nodeper 来自 Flutter

Flutter自定义着色器插件shader_presets的使用

shader_presets 是一个Flutter包,它实现了一些现成可用的着色器,如来自 gl-transitions 的过渡效果和来自 ShaderToy 的效果。这个包使用了 shader_buffers 包来简化着色器的使用。目前有几个预设,并且可以很容易地添加更多。

入门指南

使用 shader_presets 插件非常简单,下面是一个基本的例子:

final presetController = ShaderPresetController();
ShaderPresetCube(
    child1: child1, // children can be either the path to the asset image or a widget
    child2: child2,
    presetController: presetController,
    progress: 0, // This is common to all gl-transitions
    reflection: 1, // This is one parameter of this Cube shader
)

presetController 允许你获取/设置uniforms并获取着色器控制器。着色器控制器让你可以播放/暂停/倒带、添加条件操作以检查指针位置、获取其状态、交换纹理通道以及动画化自定义uniform值。

动画化自定义uniform

例如,你可以通过以下代码动画化一个名为 progress 的uniform:

presetController.getShaderController()!.animateUniform(
    uniformName: 'progress',
    begin: 0,
    end: 1,
    duration: const Duration(milliseconds: 600),
    curve: Curves.decelerate,
    onAnimationEnded: (ctrl, uniformValue) {
        // handle animation end
    },
);

可用预设及其参数

来自 ShaderToy 参数
ShaderPresetBarrel
distortion
img
ShaderPresetWater
speed, frequency, amplitude
img
ShaderPresetPageCurl
radius
img
来自 gl-transitions 参数
ShaderPresetCube
progress, persp, unzoom, reflection, floating
img
ShaderPresetPolkaDotsCurtain
progress, dots, centerX, centerY
img
ShaderPresetRadial
progress, smoothness
img
ShaderPresetFlyeye
progress, size, zoom, colorSeparation
img

示例Demo

这里提供了一个完整的示例demo,展示了如何在Flutter项目中使用 shader_presets 包。

import 'dart:ui';

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:shader_buffers/shader_buffers.dart' show Uniforms;
import 'package:shader_preset_example/page1.dart';
import 'package:shader_preset_example/page2.dart';
import 'package:shader_presets/shader_presets.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Shader Preset demo',
      themeMode: ThemeMode.dark,
      darkTheme: ThemeData.dark(),
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
      scrollBehavior: const MaterialScrollBehavior().copyWith(
        dragDevices: {
          PointerDeviceKind.mouse,
          PointerDeviceKind.touch,
          PointerDeviceKind.stylus,
          PointerDeviceKind.unknown,
        },
      ),
      home: const MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key});

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  bool useImages = false;
  ValueNotifier<List<double>> floatUniforms = ValueNotifier([]);
  ValueNotifier<ShaderPresetsEnum> preset = ValueNotifier(ShaderPresetsEnum.cube);
  late ShaderPresetController presetController;

  /// Get the preset min-max ranges and set its uniform to the starting range
  late Uniforms uniforms;

  @override
  void initState() {
    super.initState();
    presetController = ShaderPresetController();
  }

  @override
  Widget build(BuildContext context) {
    final Widget page = Padding(
      padding: const EdgeInsets.all(32),
      child: ValueListenableBuilder(
        valueListenable: preset,
        builder: (_, p, __) {
          return Column(
            mainAxisAlignment: MainAxisAlignment.spaceBetween,
            children: [
              AspectRatio(
                aspectRatio: 16 / 9,
                child: createPreset(p),
              ),
              const Spacer(),
              const Divider(),
              uniformSliders(),
              Wrap(
                spacing: 4,
                runSpacing: 4,
                children: List.generate(
                  ShaderPresetsEnum.values.length,
                  (index) {
                    return ElevatedButton(
                      onPressed: () {
                        preset.value = ShaderPresetsEnum.values[index];
                      },
                      child: Text(ShaderPresetsEnum.values[index].name),
                    );
                  },
                ),
              ),
              const SizedBox(height: 8),
              Row(
                children: [
                  ElevatedButton(
                    onPressed: () {
                      if (!useImages) {
                        if (context.mounted) {
                          setState(() {
                            useImages = true;
                          });
                        }
                      }
                    },
                    child: const Text('use images'),
                  ),
                  const SizedBox(width: 16),
                  ElevatedButton(
                    onPressed: () {
                      if (useImages) {
                        if (context.mounted) {
                          setState(() {
                            useImages = false;
                          });
                        }
                      }
                    },
                    child: const Text('use widgets'),
                  ),
                ],
              ),
            ],
          );
        },
      ),
    );

    return Scaffold(
      appBar: AppBar(
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        title: const Text('Shader Presets demo'),
      ),
      body: kIsWeb
          ? Center(
              child: AspectRatio(
                aspectRatio: 2 / 3,
                child: page,
              ),
            )
          : page,
    );
  }

  Widget createPreset(ShaderPresetsEnum p) {
    uniforms = presetController.getDefaultUniforms(p);
    final dynamic child1 = useImages ? 'assets/flutter.png' : const Page1();
    final dynamic child2 = useImages ? 'assets/dash.png' : const Page2();
    Widget ret;
    switch (p) {
      case ShaderPresetsEnum.water:
        ret = ShaderPresetWater(
          child: child1,
          presetController: presetController,
        );
      case ShaderPresetsEnum.pageCurl:
        ret = ShaderPresetPageCurl(
          child1: child1,
          child2: child2,
          presetController: presetController,
        );
      case ShaderPresetsEnum.barrel:
        ret = ShaderPresetBarrel(
          presetController: presetController,
          child: Page1(
            onScrolling: (scrollingVelocity) {
              presetController.setUniform(0, scrollingVelocity);
            },
          ),
        );

      case ShaderPresetsEnum.cube:
        ret = ShaderPresetCube(
          child1: child1,
          child2: child2,
          presetController: presetController,
        );
      case ShaderPresetsEnum.polkaDotsCurtain:
        ret = ShaderPresetPolkaDotsCurtain(
          child1: child1,
          child2: child2,
          presetController: presetController,
        );
      case ShaderPresetsEnum.radial:
        ret = ShaderPresetRadial(
          child1: child1,
          child2: child2,
          presetController: presetController,
        );
      case ShaderPresetsEnum.flyeye:
        ret = ShaderPresetFlyeye(
          child1: child1,
          child2: child2,
          presetController: presetController,
        );
    }
    return ret;
  }

  /// Creates a column with a slider for each preset uniforms
  Widget uniformSliders() {
    /// Build the uniforms list
    floatUniforms = ValueNotifier(
      List.generate(uniforms.uniforms.length, (index) {
        return uniforms.uniforms[index].value;
      }),
    );

    /// Build slider for each uniforms
    return ValueListenableBuilder(
      valueListenable: floatUniforms,
      builder: (_, uniform, __) {
        return Column(
          mainAxisSize: MainAxisSize.min,
          children: List.generate(
            uniforms.uniforms.length,
            (index) {
              return Flex(
                direction: Axis.horizontal,
                children: [
                  Text('${uniforms.uniforms[index].name} \t\t'
                      '${uniform[index].toStringAsFixed(2)}'),
                  Expanded(
                    child: Slider(
                      value: uniform[index],
                      min: uniforms.uniforms[index].range.start,
                      max: uniforms.uniforms[index].range.end,
                      onChanged: (value) {
                        floatUniforms.value[index] = value;
                        floatUniforms.value = floatUniforms.value.toList();
                        presetController.setUniforms(floatUniforms.value);
                      },
                    ),
                  ),

                  /// Button to animate the uniform
                  IconButton(
                    onPressed: () {
                      presetController.getShaderController()!.animateUniform(
                            uniformName: uniforms.uniforms[index].name,
                            begin: uniforms.uniforms[index].range.start,
                            end: uniforms.uniforms[index].range.end,
                            onAnimationEnded: (ctrl, uniformValue) {
                              /// Just update sliders
                              Future.delayed(Duration.zero, () {
                                floatUniforms.value[index] = uniformValue;
                                floatUniforms.value =
                                    floatUniforms.value.toList();
                              });
                            },
                          );
                    },
                    icon: const Icon(Icons.animation),
                  ),
                ],
              );
            },
          ),
        );
      },
    );
  }
}

这个示例展示了如何创建一个包含多个着色器预设的应用程序,并允许用户通过滑块调整每个预设的uniform参数,还可以选择使用图像或小部件作为着色器的输入。

贡献

如果你想为 shader_presets 添加新的预设,可以参考文档中的“Adding a preset”部分进行操作。


更多关于Flutter自定义着色器插件shader_presets的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter自定义着色器插件shader_presets的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


当然,以下是如何在Flutter中使用自定义着色器插件 shader_presets 的一个代码示例。这个示例将展示如何集成并使用该插件来创建一个简单的自定义着色器效果。

首先,确保你已经在 pubspec.yaml 文件中添加了 shader_presets 依赖:

dependencies:
  flutter:
    sdk: flutter
  shader_presets: ^最新版本号 # 请替换为实际最新版本号

然后运行 flutter pub get 来安装依赖。

接下来,在你的 Flutter 项目中创建一个自定义着色器效果。以下是一个简单的示例,展示如何在 CustomPaint 小部件中使用 shader_presets 插件:

import 'package:flutter/material.dart';
import 'package:shader_presets/shader_presets.dart'; // 导入shader_presets包

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('Shader Presets Example'),
        ),
        body: Center(
          child: CustomPaint(
            size: Size(double.infinity, double.infinity),
            painter: MyCustomShaderPainter(),
          ),
        ),
      ),
    );
  }
}

class MyCustomShaderPainter extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {
    final Paint paint = Paint();
    final Rect rect = Rect.fromLTWH(0, 0, size.width, size.height);

    // 使用预设着色器,例如简单的波浪着色器
    final Shader shader = createWaveShader(
      rect.size,
      colors: [Colors.blue, Colors.lightBlueAccent],
      waveHeight: 20.0,
      waveLength: 100.0,
      direction: Alignment.topLeft,
    );

    paint.shader = shader;
    canvas.drawRect(rect, paint);
  }

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) {
    return false; // 在这个例子中,着色器不依赖于任何状态,因此不需要重绘
  }
}

在这个示例中,我们创建了一个 CustomPaint 小部件,并在其中使用了一个自定义的 MyCustomShaderPainter 类。该类重写了 paint 方法,并在其中创建了一个简单的波浪着色器。

请注意,createWaveShader 函数是假设 shader_presets 插件提供的预设函数之一(实际使用时,请查阅 shader_presets 的文档以获取正确的函数名和参数)。如果 shader_presets 没有直接提供 createWaveShader 函数,你可能需要根据插件提供的API自己实现波浪效果。

由于 shader_presets 的实际API可能会有所不同,这里只是一个概念性的示例。你需要根据 shader_presets 的具体文档来调整代码。如果 shader_presets 插件提供了自定义着色器的创建方法,你通常会使用类似的方式来创建和应用着色器。

确保查阅 shader_presets 的最新文档和示例代码,以获取最准确和最新的使用指南。

回到顶部