撰寫和使用片段著色器
自訂著色器可用於提供 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
物件表示一個片段程式,以及一組特定的uniform(組態參數)。可用的 uniform 取決於著色器的定義方式。
void updateShader(Canvas canvas, Rect rect, FragmentProgram program) {
var shader = program.fragmentShader();
shader.setFloat(0, 42.0);
canvas.drawRect(rect, Paint()..shader = shader);
}
畫布 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。這與使用 0-255
值編碼且具有未預乘 alpha 的典型 Flutter 顏色不同。
取樣器
#取樣器提供對 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 上的 撰寫高效能著色器。
其他資源
#如需更多資訊,這裡有一些資源。
- The Book of Shaders,由 Patricio Gonzalez Vivo 和 Jen Lowe 撰寫
- Shader toy,一個協作式著色器遊樂場
simple_shader
,一個簡單的 Flutter 片段著色器範例專案flutter_shaders
,一個簡化在 Flutter 中使用片段著色器的套件
除非另有說明,否則本網站上的文件反映了 Flutter 的最新穩定版本。頁面上次更新於 2024-09-26。 檢視來源 或 回報問題。