編寫和使用片段著色器
自定義著色器可用於提供 Flutter SDK 之外的豐富圖形效果。著色器是用一種小型的、類似 Dart 的語言(稱為 GLSL)編寫的程式,並在使用者的 GPU 上執行。
透過在 pubspec.yaml 檔案中列出自定義著色器,並使用 FragmentProgram API 獲取它們,可以將其新增到 Flutter 專案中。
將著色器新增到應用
#著色器以 .frag 副檔名的 GLSL 檔案的形式,必須在專案的 pubspec.yaml 檔案的 shaders 部分中宣告。Flutter 命令列工具將著色器編譯成其相應的後端格式,並生成必要的執行時元資料。然後,編譯後的著色器像資源一樣包含在應用程式中。
flutter:
shaders:
- shaders/myshader.frag在除錯模式下執行時,著色器程式的變化會觸發重新編譯,並在熱過載或熱重啟期間更新著色器。
來自包的著色器透過在著色器程式名稱前加上 packages/$pkgname(其中 $pkgname 是包的名稱)新增到專案中。
執行時載入著色器
#要在執行時將著色器載入到 FragmentProgram 物件中,請使用 FragmentProgram.fromAsset 建構函式。資源的名稱與 pubspec.yaml 檔案中給出的著色器路徑相同。
void loadMyShader() async {
var program = await FragmentProgram.fromAsset('shaders/myshader.frag');
}FragmentProgram 物件可用於建立一個或多個 FragmentShader 例項。FragmentShader 物件表示一個片段程式以及一組特定的 uniforms(配置引數)。可用的 uniforms 取決於著色器的定義方式。
void updateShader(Canvas canvas, Rect rect, FragmentProgram program) {
var shader = program.fragmentShader();
shader.setFloat(0, 42.0);
canvas.drawRect(rect, Paint()..shader = shader);
}Canvas API
#片段著色器可以透過設定 Paint.shader 與大多數 Canvas API 一起使用。例如,在使用 Canvas.drawRect 時,著色器會為矩形內的所有片段進行評估。對於像 Canvas.drawPath 這樣帶有描邊路徑的 API,著色器會為描邊線內的所有片段進行評估。一些 API,例如 Canvas.drawImage,會忽略著色器的值。
void paint(Canvas canvas, Size size, FragmentShader shader) {
// Draws a rectangle with the shader used as a color source.
canvas.drawRect(
Rect.fromLTWH(0, 0, size.width, size.height),
Paint()..shader = shader,
);
// Draws a stroked rectangle with the shader only applied to the fragments
// that lie within the stroke.
canvas.drawRect(
Rect.fromLTWH(0, 0, size.width, size.height),
Paint()
..style = PaintingStyle.stroke
..shader = shader,
)
}編寫著色器
#片段著色器以 GLSL 原始檔編寫。按照慣例,這些檔案具有 .frag 副檔名。(Flutter 不支援頂點著色器,頂點著色器將具有 .vert 副檔名。)
支援從 460 到 100 的任何 GLSL 版本,但某些可用功能受到限制。本文件中的其餘示例使用 460 core 版本。
在 Flutter 中使用著色器時受以下限制
- 不支援 UBO 和 SSBO
sampler2D是唯一支援的取樣器型別- 僅支援
texture的兩個引數版本(取樣器和 uv) - 不能宣告額外的 varying 輸入
- 針對 Skia 時,所有精度提示都被忽略
- 不支援無符號整數和布林值
Uniforms
#可以透過在 GLSL 著色器源中定義 uniform 值,然後為每個片段著色器例項在 Dart 中設定這些值來配置片段程式。
具有 GLSL 型別 float、vec2、vec3 和 vec4 的浮點 uniform 使用 FragmentShader.setFloat 方法設定。使用 sampler2D 型別的 GLSL 取樣器值使用 FragmentShader.setImageSampler 方法設定。
每個 uniform 值的正確索引由 uniform 值在片段程式中定義的順序決定。對於由多個浮點陣列成的資料型別,例如 vec4,您必須為每個值呼叫一次 FragmentShader.setFloat。
例如,給定 GLSL 片段程式中的以下 uniform 宣告
uniform float uScale;
uniform sampler2D uTexture;
uniform vec2 uMagnitude;
uniform vec4 uColor;初始化這些 uniform 值的相應 Dart 程式碼如下
void updateShader(FragmentShader shader, Color color, Image image) {
shader.setFloat(0, 23); // uScale
shader.setFloat(1, 114); // uMagnitude x
shader.setFloat(2, 83); // uMagnitude y
// Convert color to premultiplied opacity.
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
// Initialize sampler uniform.
shader.setImageSampler(0, image);
}請注意,與 FragmentShader.setFloat 一起使用的索引不計算 sampler2D uniform。這個 uniform 是透過 FragmentShader.setImageSampler 單獨設定的,索引從 0 開始重新計數。
任何未初始化的浮點 uniform 都將預設為 0.0。
當前位置
#著色器可以訪問一個 varying 值,其中包含正在評估的特定片段的區域性座標。使用此功能計算依賴於當前位置的效果,可以透過匯入 flutter/runtime_effect.glsl 庫並呼叫 FlutterFragCoord 函式來訪問。例如
#include <flutter/runtime_effect.glsl>
void main() {
vec2 currentPos = FlutterFragCoord().xy;
}從 FlutterFragCoord 返回的值與 gl_FragCoord 不同。gl_FragCoord 提供螢幕空間座標,通常應避免使用,以確保著色器在不同後端之間保持一致。針對 Skia 後端時,對 gl_FragCoord 的呼叫會被重寫以訪問區域性座標,但 Impeller 無法進行此重寫。
顏色
#沒有內建的顏色資料型別。相反,它們通常表示為 vec4,每個分量對應一個 RGBA 顏色通道。
單個輸出 fragColor 要求顏色值歸一化到 0.0 到 1.0 的範圍,並且具有預乘 alpha。這與典型的 Flutter 顏色不同,Flutter 顏色使用 0-255 值編碼並具有未預乘 alpha。
取樣器
#取樣器提供對 dart:ui Image 物件的訪問。此影像可以從解碼影像中獲取,也可以使用 Scene.toImageSync 或 Picture.toImageSync 從應用程式的一部分獲取。
#include <flutter/runtime_effect.glsl>
uniform vec2 uSize;
uniform sampler2D uTexture;
out vec4 fragColor;
void main() {
vec2 uv = FlutterFragCoord().xy / uSize;
fragColor = texture(uTexture, uv);
}預設情況下,影像使用 TileMode.clamp 來確定超出 [0, 1] 範圍的值的行為方式。不支援瓷磚模式的自定義,需要在著色器中模擬。
效能考量
#針對 Skia 後端時,載入著色器可能會很昂貴,因為它必須在執行時編譯成適當的平臺特定著色器。如果您打算在動畫期間使用一個或多個著色器,請考慮在開始動畫之前預快取片段程式物件。
您可以在幀之間重用 FragmentShader 物件;這比為每個幀建立新的 FragmentShader 更高效。
有關編寫高效能著色器的更詳細指南,請檢視 GitHub 上的 編寫高效著色器。
其他資源
#更多資訊,請參考以下資源。
- Patricio Gonzalez Vivo 和 Jen Lowe 撰寫的 《著色器之書》
- Shader toy,一個協作式著色器遊樂場
simple_shader,一個簡單的 Flutter 片段著色器示例專案flutter_shaders,一個簡化在 Flutter 中使用片段著色器的包