[読書会]Fragment shaders 作成と利用

NOTE)
SkiaImpellerの両バックエンドは、カスタムシェーダーの作成をサポートしています。
注意書きがある場合を除き、同じ指示が両方に適用されます。

カスタムシェーダーは、Flutter SDKが提供する以上のリッチなグラフィック効果を提供するために使用できます。シェーダーは、GLSLと呼ばれる小さなDartのような言語で書かれたプログラムで、ユーザーのGPU上で実行されます。

カスタムシェーダーはpubspec.yamlファイルに記述することでFlutterプロジェクトに追加され、FragmentProgram APIを使って取得します。

1.アプリへの shaders 追加

シェーダーは、拡張子が.fragのGLSLファイルの形式で、プロジェクトのpubspec.yamlファイルのshadersセクションで宣言する必要があります。Flutterコマンドラインツールはシェーダーを適切なバックエンド形式にコンパイルし、必要なランタイムメタデータを生成します。コンパイルされたシェーダーは、アセットと同じようにアプリケーションに含まれます。

Dart
flutter:
  shaders:
    - shaders/myshader.frag

デバッグモードで実行している場合、シェーダプログラムへの変更が再コンパイルをトリガし、ホットリロードまたはホットリスタート時にシェーダが更新されます。

パッケージからのシェーダーは、シェーダープログラム名の前に packages/$pkgname を付けてプロジェクトに追加されます ($pkgname はパッケージ名です)。

(1)アプリ実行時の shaders 読込

実行時にシェーダーをFragmentProgramオブジェクトにロードするには、FragmentProgram.fromAssetコンストラクタを使用します。アセット名は pubspec.yaml ファイルで指定されたシェーダーへのパスと同じです。

Dart
void loadMyShader() async {
  var program = await FragmentProgram.fromAsset('shaders/myshader.frag');
}

FragmentProgramオブジェクトは、1つ以上のFragmentShaderインスタンスを作成するために使用できます。 FragmentShaderオブジェクトは、特定のユニフォーム(設定パラメータ)セットとともにフラグメントプログラムを表します。利用可能なユニフォームは、シェーダーの定義方法によって異なります。

Dart
void updateShader(Canvas canvas, Rect rect, FragmentProgram program) {
  var shader = program.fragmentShader();
  shader.setFloat(0, 42.0);
  canvas.drawRect(rect, Paint()..shader = shader);
}

(2)Canvas API

フラグメントシェーダは、Paint.shaderを設定することで、殆どのCanvas APIで使用できます。例えば、Canvas.drawRectを使用する場合、シェーダは矩形内の全てのフラグメントに対して評価されます。Canvas.drawPathの様な描画パスを持つAPIでは、シェーダは描画線内の全てのフラグメントに対して評価されます。Canvas.drawImageの様に、シェーダーの値を無視するAPIもあります。

Dart
void paint(Canvas canvas, Size size, FragmentShader shader) {
  //シェーダを色源として矩形を描画します。
  canvas.drawRect(
    Rect.fromLTWH(0, 0, size.width, size.height),
    Paint()..shader = shader,
  );

  //ストローク内にあるフラグメントにのみシェーダを適用して、
  //ストロークされた長方形を描画します。
  canvas.drawRect(
    Rect.fromLTWH(0, 0, size.width, size.height),
    Paint()
      ..style = PaintingStyle.stroke
      ..shader = shader,
  )
}

2.shaders 作成

フラグメントシェーダーGLSLソースファイルとしてオーサリングされます。慣習上、これらのファイルの拡張子は.fragです。(Flutterは vertex シェーダー( 拡張子 .vert )をサポートしていません。)

GLSLのバージョンは460から100までサポートされているが、使用できる機能には制限があります。この文書の残りの例では、バージョン460のコアを使用しています。

Flutter カスタムシェーダー高度グラフィック効果を実現する上で非常に便利ですが、いくつかの制約に注意する必要があります。これらの制約により、標準的OpenGL シェーダーと比べて実装できる効果制限されることがあります。そのため、制約を理解し、代替手段を検討することが重要です。Flutterでシェーダーを使用する場合、以下の制限があります。

① UBOs(Uniform Buffer Objects)と SSBOs(Shader Storage Buffer Objects)の非対応

 UBOs SSBOs は、シェーダープログラム内データを管理・やり取りするためのバッファオブジェクトです。これらは主に、以下のような用途に使用されます。

  • UBO: 複数シェーダー間一貫したデータ(例:ビュー行列ライト設定)を保持し、シェーダー間共有する。
  • SSBO: より大きなデータセット(例:頂点データカスタムデータ構造)を扱い、シェーダーの計算結果バッファに格納する。

 これらは OpenGL Vulkan などのグラフィック API では標準的な機能ですが、Flutter のシェーダーSkia Impeller バックエンド)ではサポートされていません。そのため、複雑なシェーダーを設計する際には、UBO SSBO 使用できない点に注意する必要があります。

sampler2D のみ対応

 Flutter のシェーダーでは、sampler2D 型サンプラ(テクスチャを取り扱うための型)のみがサポートされています

  • sampler2D は、2D テクスチャサンプリング(取得)するためのです。
  • 通常、3D テクスチャ用の sampler3D や、キューブマップ(環境マッピング)用の samplerCube といったサンプラ型OpenGL Vulkan で使用できますが、Flutter シェーダーではこれらは使用できません。

 そのため、2D テクスチャ限定した効果シェーダー実装することになります。3D テクスチャやその他の複雑なテクスチャ操作を必要とするシェーダーは Flutter では実現できないため、注意が必要です。

③ texture 関数の制約

 シェーダーでは、テクスチャピクセルデータ取得するために、texture 関数を使用します。この関数にはいくつかのバリエーションがありますが、Flutter では以下の 2 引数バージョンのみをサポートしています

Flutter では以下の 2 引数バージョンのみをサポートしています:
vec4 color = texture(sampler2D, uv);
  • sampler2D2D テクスチャ参照するサンプラ変数
  • uvテクスチャ座標0.0~1.0 の範囲の2次元ベクトル)。

 他の引数バリエーション(例:バイリニア補間や、オフセット付きの texture 関数など)は使用できないため、シンプルなテクスチャ参照に限定されます。

④ 追加の 可変入力を宣言できない

  • varying 変数は、バーテックスシェーダーフラグメントシェーダーデータを渡すために使用される変数です。
  • Flutter のシェーダーでは、シェーダー内追加varying 変数を宣言することはできません。つまり、バーテックスシェーダーからフラグメントシェーダーへのデータ転送は、Flutter 内部的提供する varying 変数限定されます。
  • これにより、カスタムデータ構造を用いたシェーダーの設計が難しくなることがあります。例えば、バーテックスごと特別な情報法線ベクトルカスタム属性など)をフラグメントシェーダーに渡すことができません。

⑤ Skia をターゲットにする際の精度ヒントの無視

  • OpenGL GLSL では、浮動小数点精度指定するための precision 修飾子があります(例:highpmediumplowp)。
  • しかし、Skia バックエンドを使用する場合、これらの精度指定無視されます。これは、プラットフォームごと異なる浮動小数点精度を持つデバイスがあるため、Skia 側統一的な動作を行うための仕様です。

 そのため、精度指定を用いたパフォーマンスの最適化や、低精度の演算を行いたい場合には制限があります。

Unsigned integers と booleans の非対応

 GLSL では、符号なし整数unsigned intや、ブーリアン型boolサポートしています。しかし、Flutter シェーダーではこれらの型は使用できません。故に・・・

  • int 型 を使用する!
    unsigned int 型が使えないため、負の値を扱わない計算などは代替方法通常の int 型を使用するなど)を使用する必要があります。
  • (bool の代わりに) int 型/float 型で、0 または 1 を使用する!
    bool 型の変数も使用できないため、条件分岐などで bool 型を使うのではなく、int や float を 0 または 1 として扱う代替手法を用いることになります。

(1)Uniforms

Uniforms とは、シェーダーに値を渡すための特殊な変数で、シェーダープログラム全体にわたって一定の値を保持し、シェーダーを通じて設定できます。GLSL(OpenGL Shading Language)では、uniform 修飾子を使用して定義します。これにより、外部のプログラム(Dart コードなど)から値を設定できるようになります。

Flutter でフラグメントシェーダーを扱う際、FragmentProgram と FragmentShader API を使用して uniform の値を設定できます。

フラグメントプログラムは、GLSL シェーダソースで一様な値を定義し、Dart で各フラグメントシェーダインスタンスにこれらの値を設定することで構成できます。

GLSLタイプfloat、vec2、vec3、vec4の浮動小数点ユニフォームは、FragmentShader.setFloatメソッドを使用して設定します。sampler2D型を使用するGLSLサンプラー値は、FragmentShader.setImageSamplerメソッドを使用して設定します。

各一様値に対する正しいインデックスは、フラグメント・プログラムの中で一様値が定義される順番によって決定される。vec4のような複数の浮動小数点数で構成されるデータ型の場合は、各値に対して1回ずつFragmentShader.setFloatを呼び出す必要があります。

例えば、GLSLフラグメント・プログラムで次のようなユニフォーム宣言があったとします。

GLSLフラグメント・プログラムにおける、ユニフォーム宣言(例):
uniform float uScale;
uniform sampler2D uTexture;
uniform vec2 uMagnitude;
uniform vec4 uColor;

Dart での uniform 値の設定:
上記の GLSL コードの uniform を Dart 側で設定する方法は、FragmentShader クラスを使用して行います。以下がその例です。

上記 uniform 値の Dart による設定(例):
void updateShader(FragmentShader shader, Color color, Image image) {
  // uScale の設定
  shader.setFloat(0, 23);  // uScale の値を設定
  
  // uMagnitude の設定
  shader.setFloat(1, 114); // uMagnitude の x 座標の値を設定
  shader.setFloat(2, 83);  // uMagnitude の y 座標の値を設定

  // uColor の設定
  shader.setFloat(3, color.red / 255 * color.opacity);   // uColor の r 値を設定
  shader.setFloat(4, color.green / 255 * color.opacity); // uColor の g 値を設定
  shader.setFloat(5, color.blue / 255 * color.opacity);  // uColor の b 値を設定
  shader.setFloat(6, color.opacity);                     // uColor の a 値を設定

  // sampler2D 型の uniform(uTexture)の設定
  shader.setImageSampler(0, image);  // インデックス 0 を使って画像を設定
}

① インデックスの指定について:

 FragmentShader.setFloat メソッドを使用して uniform 値を設定する際、インデックス(0, 1, 2…)は、GLSL の uniform 宣言順に設定します。

 このインデックスは、GLSL シェーダー内で宣言された順番をもとに割り当てられます。
ただし、sampler2D 型の uniform(uTexture)は FragmentShader.setFloat ではなく、FragmentShader.setImageSampler メソッドを使用します。

  • uScale のインデックス:0
  • uMagnitude x 値のインデックス:1
  • uMagnitude y 値のインデックス:2
  • uColor r, g, b, a のインデックス:3, 4, 5, 6
  • uTexture のインデックス:setImageSampler(0, image) 別途指定

sampler2D 型の uniform 変数は FragmentShader.setImageSampler を使って設定し、他の uniform 値とインデックスが重複しません(sampler2D のインデックスは別管理)。

② インデックスの順序と注意点:

 FragmentShader.setFloat メソッドは float, vec2, vec3, vec4 型の uniform に対して使用し、インデックスは GLSL コードの宣言順に設定します。

 sampler2D 型(テクスチャサンプラー)の uniform は FragmentShader.setImageSampler メソッドを使用し、インデックスは 0 から始まる別のカウントとして管理されるため、setFloat で使用するインデックスと競合しません。

③ Flutter での uniform 設定のポイント:

  • データ型ごとの設定方法: float、vec2、vec3、vec4 といった GLSL の型を Dart 側で設定する際には、FragmentShader.setFloat を使用します。
  • インデックスの割り当て: setFloat メソッドで使用するインデックスは sampler2D とは別のカウントを使用します。
  • 未初期化の uniform 値: Dart コードで uniform を初期化しなかった場合、float 型の uniform 変数はデフォルト値として 0.0 になります。

④ 「uniforms」を用いたシェーダープログラムの例:

 シェーダーの動的な効果を得るためには、Dart コードから uniform に様々な値を設定してシェーダーの動作を変更します。これにより、例えばアニメーション効果や、ユーザー入力に応じた色変化などを実装することが可能です。

(2)現在の位置

シェーダーが現在評価している フラグメント(ピクセル) のローカル座標にアクセスする方法について説明しています。この座標を使用すると、現在のフラグメントがどの位置にあるのかを取得でき、さまざまなグラフィカル効果(例: グラデーション、波紋効果など)を生成するのに役立ちます。

Local Coordinates(ローカル座標)とは?

 ローカル座標とは、シェーダーが描画するオブジェクトの座標系内での位置を指します。これは、ウィジェットやキャンバス内の位置を表し、オブジェクトがどこに描画されるかを決定するために使用されます。

 シェーダーコードでは、gl_FragCoord を使って画面全体(スクリーン)の座標を取得することができますが、gl_FragCoord はスクリーン座標系の情報を提供するため、異なるバックエンド(例えば、Skia と Impeller)間での動作の一貫性が保てない場合があります。

FlutterFragCoord を使用してローカル座標取得

 Flutter では、flutter/runtime_effect.glsl ライブラリをインポートして、FlutterFragCoord 関数を使うことを推奨しています。FlutterFragCoord を使用することで、各フラグメント(ピクセル)のローカル座標を取得できます。

FlutterFragCoord を使用することで、各フラグメント(ピクセル)のローカル座標を取得(例):
#include <flutter/runtime_effect.glsl>

void main() {
  // 各フラグメントのローカル座標を取得
  vec2 currentPos = FlutterFragCoord().xy;
}

// このコードでは、FlutterFragCoord() を呼び出して現在のフラグメントの座標を vec2 型の currentPos に格納しています。これを使うことで、シェーダー内でピクセルの位置に基づいた様々なエフェクトを作成することが可能です。

FlutterFragCoord と gl_FragCoord の違い

 gl_FragCoord フラグメントスクリーン座標を取得します。これはウィンドウ全体の座標系に基づいており、バックエンドごとに異なる処理がされる可能性があるため、プラットフォーム間での一貫性が損なわれることがあります。

 FlutterFragCoord は、フラグメントローカル座標取得します。これはシェーダーが現在描画しているオブジェクトの座標系に基づいており、各バックエンド(Skia や Impeller)で一貫性を保ちながら動作します。

FlutterFragCoord を使用する理由

 Flutter のシェーダーは、複数のバックエンド(: Skia Impeller)で動作します。
gl_FragCoord を使用すると、Skia では自動的にローカル座標に書き換えられますが、Impeller ではそのような変換が行われないため、動作に一貫性が保てません。)

 そのため、Flutter では gl_FragCoord の代わりに FlutterFragCoord を使用することを推奨しており、これによりすべてのバックエンドで一貫した挙動を保証します。

活用例:

 この FlutterFragCoord を活用することで、シェーダー内で以下のような効果を実現できます。

  • グラデーション
     各フラグメント位置に基づいた変化を行う。
  • エフェクトの調整
     中心からの距離に基づいた波紋効果や拡大・縮小効果を行う。
画面の中心から放射状に広がるグラデーション効果を作成するためのシェーダー(例):
#include <flutter/runtime_effect.glsl>

void main() {
  vec2 currentPos = FlutterFragCoord().xy;
  
  // 画面の中心を (0.5, 0.5) として正規化された座標を取得
  vec2 centeredCoord = (currentPos - 0.5) * 2.0;

  // 中心からの距離を計算
  float distance = length(centeredCoord);
  
  // 距離に応じて色を変化させる
  gl_FragColor = vec4(distance, 0.0, 1.0 - distance, 1.0);
}

//このシェーダーでは、各フラグメントのローカル座標 currentPos をもとに中心からの距離 distance を計算し、距離に基づいた色の変化を gl_FragColor に設定しています。

(3)色

 ここでは、Flutter のシェーダーにおいて、色の取り扱い方法について触れています。

① 色(Color)の表現方法:

 Flutter のシェーダーには、色を直接扱うための データ型は存在しません。
 その代わり、vec4 型として表現され、以下のように各コンポーネントが RGBA(Red, Green, Blue, Alpha) の各チャンネルを表しています。

  • vec4 の形式:
    • vec4(r, g, b, a)
      各成分は 0.01.0 の範囲で値を持ち、以下の意味を表します。
      • r : 赤色強度 (Red)
      • g : 緑色強度 (Green)
      • b : 青色強度 (Blue)
      • a : 透明度 (Alpha)

 この vec4 は、OpenGL や GLSL で一般的に色を扱う際の表現方法で、これを使ってシェーダー内で色の設定を行います。

② fragColor と色の範囲:

 シェーダーの出力変数である fragColor には、シェーダーが最終的に描画する色を設定する必要があります。
 この fragColor には、次の条件を満たす vec4 型の色を設定します。

  1. 正規化された値(Normalized Values)
    • fragColor の値は、全てのコンポーネント(r, g, b, a)が 0.0 ~ 1.0 の範囲に収まっていなければなりません。
    • 例えば、純粋な赤色は vec4(1.0, 0.0, 0.0, 1.0) として表されます。
  2. プリマルチプライドアルファ(Premultiplied Alpha)
    • fragColor に設定される色は、「プリマルチプライドアルファ」と呼ばれる形式である必要があります。これは、RGB 各値がアルファ(透明度)で事前に掛け算された状態のことを指します。
    • 例えば、通常の赤色(透明度 0.5)は vec4(1.0, 0.0, 0.0, 0.5) ですが、プリマルチプライドアルファでは RGB 値を透明度で掛けた vec4(0.5, 0.0, 0.0, 0.5) になります。

③ Flutter の Color との違い:

 Flutter の Color クラスは、色を扱う際に次のような特徴を持っています。

  1. 値の範囲:
    • Flutter の Color クラスは、各コンポーネント(R, G, B, A)が 0 ~ 255 の整数値を取り、ARGB(Alpha, Red, Green, Blue)の順で指定します。
    • 例: Color(0xFFFF0000) は不透明な赤色(赤: 255, 緑: 0, 青: 0, 透明度: 255)を表します。
  2. 非プリマルチプライドアルファ(Unpremultiplied Alpha):
    • Flutter の Color クラスでは、デフォルトで 非プリマルチプライドアルファ(Unpremultiplied Alpha) を使用します。これは、RGB 値がアルファ値と掛けられていない状態を指します。
    • 例えば、透明度 0.5 の赤色は Color(0x80FF0000)(赤: 255, 緑: 0, 青: 0, 透明度: 128)として表され、RGB 値はそのまま指定されます。

④ シェーダーとの違いの注意点:

 Flutter の Color クラスとシェーダーの vec4 型を使った色指定には、以下の違いに注意が必要です。

  • データ型の表現方法:
    • vec4 型は 0.0 ~ 1.0 の浮動小数点を使うのに対し、Flutter の Color クラスは 0 ~ 255 の整数値を使います。
  • アルファ値の扱い:
    • Flutter では ARGB の順番で指定しますが、vec4 型では RGBA の順番で指定します。
    • また、Flutter の Color クラスでは通常の RGB 値をそのまま使いますが、シェーダーではプリマルチプライドアルファの形式を使用する必要があります。

 これにより、Flutter の Color をシェーダー用の色に変換する際には、0 ~ 255 の値を 0.0 ~ 1.0 の範囲に正規化する必要がある他、アルファ値を RGB に掛け算してプリマルチプライドアルファにする必要が出てきます。

Flutter の Color クラスを vec4 に変換し、シェーダーで利用できます(例):
void setShaderColor(FragmentShader shader, Color color) {
  //Flutter の `Color` クラスの値を正規化し、プリマルチプライドアルファ形式に変換
  final double alpha = color.opacity;
  shader.setFloat(0, color.red / 255 * alpha);//Red *Alpha
  shader.setFloat(1, color.green / 255 * alpha);//Green *Alpha
  shader.setFloat(2, color.blue / 255 * alpha);//Blue *Alpha
  shader.setFloat(3, alpha);                   //Alpha
}

 このコードでは、Flutter の Color クラスの RGB 値を 255 で割り、さらにアルファ値を掛けてプリマルチプライドアルファの形式に変換し、シェーダーに設定しています。

 このように、Flutter の Color とシェーダーの vec4 を使った色指定にはいくつかの違いがあるため、変換や指定の方法に注意しながらシェーダーを実装する必要があります。

(4)sampler2D

 Sampler は、Flutter のシェーダー画像 (dart:ui.Image オブジェクト) を扱う際に使用される重要な要素です。この説明では、sampler2D を使って、シェーダーで画像をどのように操作するかについて述べられています。

① サンプラー(Samplers)とは

 sampler2D は GLSL(OpenGL Shading Language)の型の一種で、2D テクスチャ(画像データ)を扱うための型です。これを使うことで、シェーダー内で画像のピクセルデータを取得し、様々なエフェクト変換を行うことができます。

 例えば、sampler2D 型の変数を使って画像の色を取得し、それを加工したり、画面全体に表示したりすることが可能です。

② サンプラーの初期化

 サンプラーは Dart コード内の FragmentShader インスタンスを通して設定されます。
 sampler2D 型の uniform をシェーダー内に宣言し、以下のように Dart コード内で画像データをサンプラーに設定します。

画像データをサンプラーに設定(例):
// Dart コード内でサンプラーに画像を設定
// `myImage` は `dart:ui.Image` オブジェクト
shader.setImageSampler(0, myImage);

 このコードでは、shader インスタンスに対して setImageSampler を使い、指定した dart:ui.Image 型の画像をサンプラー(sampler2D)の uniform に渡しています。

③ GLSL 内での使用例

 シェーダー内で sampler2D を使用するためには、まず GLSL コードで sampler2D 型の uniform 変数を宣言し、その変数texture 関数を用いて参照します。

 以下の GLSL コード例では、uTexture という sampler2D 型の uniform 変数を宣言し、その画像を使って各ピクセルの色を設定しています。

Dart
#include <flutter/runtime_effect.glsl>

uniform vec2 uSize;          //画像のサイズ
uniform sampler2D uTexture;  //サンプラー(画像データ)

out vec4 fragColor;          //出力する色

void main() {
  //現在のフラグメントの位置を取得し、uSize で割って正規化
  vec2 uv = FlutterFragCoord().xy / uSize;
  
  //uTexture から現在の位置の色を取得し、fragColor に設定
  fragColor = texture(uTexture, uv);
}

④ texture 関数と uv 座標の使用

 GLSL 内で画像データ(テクスチャ)を扱う際、texture 関数を使います。texture 関数は次のような形式で使用されます。

指定されたテクスチャ座標 uv におけるテクスチャ(画像)の色を返す(例):
vec4 texture(sampler2D sampler, vec2 uv);

ここで、

  • sampler
     サンプラー変数(例:uTexture)
  • uv
     正規化されたテクスチャ座標(0.0~1.0 の範囲)

この関数は、指定されたテクスチャ座標 uv におけるテクスチャ(画像)の色を返します。

⑤ TileMode.clamp の挙動

 Flutter では、シェーダーサンプラーデフォルトTileMode.clamp という設定になっており、これはテクスチャ座標(uv)が 0.0~1.0 の範囲外にある場合の挙動を決定します。

  • TileMode.clamp とは:
    • テクスチャ座標が範囲外(負の座標や 1.0 を超える座標)にあると、その座標は端の色0.0 または 1.0)に クランプ(固定されます。
    • 例えば、uv.x が 1.2 の場合は、1.0 にクランプされ、その x 座標における最端の色を取得します。

 もし TileMode をカスタマイズしたい場合は、シェーダー内で手動で範囲外の座標を処理しなければなりません。例えば、uv 座標を clamp 関数で以下のようにクランプすることが考えられます。

Dart
vec2 clampedUV = clamp(uv, -1.0, 1.5);
fragColor = texture(uTexture, clampedUV);

 これにより、uv 座標が -1.0~1.5 の範囲で設定されます。

⓺ Flutter と GLSL の統合の注意点

 GLSL のシェーダーで扱う sampler2D texture 関数を用いた画像操作は、一般的な GLSL シェーダーと同じ要領で行えますが、いくつかの点に注意が必要です。

  1. sampler2D 型しかサポートしていない:
    • Flutter のシェーダーでは sampler2D 型のみを使用できます
      3D テクスチャやその他のテクスチャ形式(samplerCube など)はサポートされていません。
  2. タイルモードのカスタマイズ不可:
    • TileMode の設定をシェーダー内で直接変更することはできないため、カスタムのタイルモード(repeat や mirror など)を実装したい場合は、手動で座標の調整を行う必要があります。

(5)パフォーマンスに関する考慮事項

 Skiaバックエンドをターゲットとする場合、実行時に適切なプラットフォーム固有のシェーダにコンパイルする必要があるため、シェーダのロードに時間がかかる場合があります。アニメーション中に1つまたは複数のシェーダーを使用する場合は、アニメーションを開始する前にフラグメントプログラムオブジェクトをプリキャッシュすることを検討してください。
(フレームごとに新しいFragmentShaderを作成するよりも効率的です。)

① シェーダーのプリキャッシュ(Pre-Caching)

 アニメーションなどでシェーダーを多用する際に、以下のようなプリキャッシュ処理を行うと、アニメーションが始まる前にシェーダーのコンパイルを完了し、パフォーマンスを向上させることができます。

シェーダーのプリキャッシュ(Pre-Caching)例:
// シェーダーをプリコンパイルしてキャッシュ
final FragmentProgram shaderProgram = await FragmentProgram.fromAsset('shaders/my_shader.glsl');

② FragmentShader の再利用

 以下のように、FragmentShader オブジェクトを毎フレーム生成するのではなく、再利用することでパフォーマンスを向上させます。

FragmentShader の再利用(例):
// フラグメントシェーダーオブジェクトを生成
final FragmentShader shader = shaderProgram.fragmentShader();

// 毎フレーム再利用する(再生成せずに)
canvas.drawRect(rect, Paint()..shader = shader);

③ シェーダーのキャッシュ効果を確認する例

アニメーションでの使用時に、初回コンパイルで遅延が発生することが多いため、以下のようにアニメーションの開始前にシェーダーをキャッシュすることで、スムーズなアニメーションを実現できます。

シェーダーのキャッシュ効果を確認する(例):
void initAnimation() async {
  // アニメーション開始前にキャッシュ
  shader = await FragmentProgram.fromAsset('shaders/animated_shader.glsl').fragmentShader();
}

// アニメーション内でシェーダーを使用
void onDraw(Canvas canvas) {
  final paint = Paint()..shader = shader;
  canvas.drawRect(rect, paint);
}

 パフォーマンスの高いシェーダーの書き方についてのより詳しいガイドは、GitHubのWriting efficient shaderをご覧ください。

(6)その他のリソース

 以下に詳細な情報があるそうです。

The Book of Shaders by Patricio Gonzalez Vivo and Jen Lowe

Shader toy, a collaborative shader playground

simple_shader, a simple Flutter fragment shaders sample project

flutter_shaders, a package that simplifies using fragment shaders in Flutter

コメントを残す