备注:不过加载着色器这个行为的开销可能会比较大,所以必须在运行时将它编译为适当的特定于平台的着色器。
5.不支持无符号整数和布尔值
void mainImage( out vec4 fragColor, in vec2 fragCoord ){ float strength = 0.4; float t = iTime/3.0; // 堆代码 duidaima.com vec3 col = vec3(0); vec2 fC = fragCoord; for(int i = -1; i <= 1; i++) { for(int j = -1; j <= 1; j++) { fC = fragCoord+vec2(i,j)/3.0; vec2 pos = fC/iResolution.xy; pos.y /= iResolution.x/iResolution.y; pos = 4.0*(vec2(0.5) - pos); for(float k = 1.0; k < 7.0; k+=1.0){ pos.x += strength * sin(2.0*t+k*1.5 * pos.y)+t*0.5; pos.y += strength * cos(2.0*t+k*1.5 * pos.x); } col += 0.5 + 0.5*cos(iTime+pos.xyx+vec3(0,2,4)); } } col /= 9.0; col = pow(col, vec3(0.4545)); fragColor = vec4(col,1.0); }而在 Flutter 里,就需要转化为如下代码所示:
#version 460 core #include <flutter/runtime_effect.glsl> out vec4 fragColor; uniform vec2 resolution; uniform float iTime; void main(){ float strength = 0.25; float t = iTime/8.0; vec3 col = vec3(0); vec2 pos = FlutterFragCoord().xy/resolution.xy; pos = 4.0*(vec2(0.5) - pos); for(float k = 1.0; k < 7.0; k+=1.0){ pos.x += strength * sin(2.0*t+k*1.5 * pos.y)+t*0.5; pos.y += strength * cos(2.0*t+k*1.5 * pos.x); } col += 0.5 + 0.5*cos(iTime+pos.xyx+vec3(0,2,4)); col = pow(col, vec3(0.4545)); fragColor = vec4(col,1.0); }备注:第一行 #version 460 core 指定所用的 OpenGL 语言版本。
flutter: shaders: - shaders/warp.frag ············· late Ticker _ticker; Duration _elapsed = Duration.zero; @override void initState() { super.initState(); _ticker = createTicker((elapsed) { setState(() { _elapsed = elapsed; }); }); _ticker.start(); } @override Widget build(BuildContext context) => ShaderBuilder( assetKey: 'shaders/warp.frag', (BuildContext context, FragmentShader shader, _) => Scaffold( appBar: AppBar( title: const Text('Warp') ), body: CustomPaint( size: MediaQuery.of(context).size, painter: ShaderCustomPainter(shader, _elapsed) ), ), ); class ShaderCustomPainter extends CustomPainter { final FragmentShader shader; final Duration currentTime; ShaderCustomPainter(this.shader, this.currentTime); @override void paint(Canvas canvas, Size size) { shader.setFloat(0, size.width); shader.setFloat(1, size.height); shader.setFloat(2, currentTime.inMilliseconds.toDouble() / 1000.0); final Paint paint = Paint()..shader = shader; canvas.drawRect(Offset.zero & size, paint); } @override bool shouldRepaint(CustomPainter oldDelegate) => true; }这里唯一需要解释的就是 shader.setFloat 流程,因为它其实是通过索引来对应到我们在 .frag文件里的变量,简单来说:这里我们在 GLSL 里定义了 uniform vec2 resolution; 和 uniform float iTime; ,那么 vec2 resolution 就占据了索引 0 和 1 ,float iTime 就占据了索引 2 。
#include <flutter/runtime_effect.glsl> uniform vec2 resolution; uniform float pointer; uniform float origin; uniform vec4 container; uniform float cornerRadius; uniform sampler2D image; const float r = 150.0; const float scaleFactor = 0.2; #define PI 3.14159265359 #define TRANSPARENT vec4(0.0, 0.0, 0.0, 0.0) mat3 translate(vec2 p) { return mat3(1.0, 0.0, 0.0, 0.0, 1.0, 0.0, p.x, p.y, 1.0); } mat3 scale(vec2 s, vec2 p) { return translate(p) * mat3(s.x, 0.0, 0.0, 0.0, s.y, 0.0, 0.0, 0.0, 1.0) * translate(-p); } vec2 project(vec2 p, mat3 m) { return (inverse(m) * vec3(p, 1.0)).xy; } struct Paint { vec4 color; bool stroke; float strokeWidth; int blendMode; }; struct Context { vec4 color; vec2 p; vec2 resolution; }; bool inRect(vec2 p, vec4 rct) { bool inRct = p.x > rct.x && p.x < rct.z && p.y > rct.y && p.y < rct.w; if (!inRct) { return false; } // Top left corner if (p.x < rct.x + cornerRadius && p.y < rct.y + cornerRadius) { return length(p - vec2(rct.x + cornerRadius, rct.y + cornerRadius)) < cornerRadius; } // Top right corner if (p.x > rct.z - cornerRadius && p.y < rct.y + cornerRadius) { return length(p - vec2(rct.z - cornerRadius, rct.y + cornerRadius)) < cornerRadius; } // Bottom left corner if (p.x < rct.x + cornerRadius && p.y > rct.w - cornerRadius) { return length(p - vec2(rct.x + cornerRadius, rct.w - cornerRadius)) < cornerRadius; } // Bottom right corner if (p.x > rct.z - cornerRadius && p.y > rct.w - cornerRadius) { return length(p - vec2(rct.z - cornerRadius, rct.w - cornerRadius)) < cornerRadius; } return true; } out vec4 fragColor; void main() { vec2 xy = FlutterFragCoord().xy; vec2 center = resolution * 0.5; float dx = origin - pointer; float x = container.z - dx; float d = xy.x - x; if (d > r) { fragColor = TRANSPARENT; if (inRect(xy, container)) { fragColor.a = mix(0.5, 0.0, (d-r)/r); } } else if (d > 0.0) { float theta = asin(d / r); float d1 = theta * r; float d2 = (3.14159265 - theta) * r; vec2 s = vec2(1.0 + (1.0 - sin(3.14159265/2.0 + theta)) * 0.1); mat3 transform = scale(s, center); vec2 uv = project(xy, transform); vec2 p1 = vec2(x + d1, uv.y); s = vec2(1.1 + sin(3.14159265/2.0 + theta) * 0.1); transform = scale(s, center); uv = project(xy, transform); vec2 p2 = vec2(x + d2, uv.y); if (inRect(p2, container)) { fragColor = texture(image, p2 / resolution); } else if (inRect(p1, container)) { fragColor = texture(image, p1 / resolution); fragColor.rgb *= pow(clamp((r - d) / r, 0.0, 1.0), 0.2); } else if (inRect(xy, container)) { fragColor = vec4(0.0, 0.0, 0.0, 0.5); } } else { vec2 s = vec2(1.2); mat3 transform = scale(s, center); vec2 uv = project(xy, transform); vec2 p = vec2(x + abs(d) + 3.14159265 * r, uv.y); if (inRect(p, container)) { fragColor = texture(image, p / resolution); } else { fragColor = texture(image, xy / resolution); } } }
Widget _buildAnimatedCard(BuildContext context, Widget? child) { return ShaderBuilder( (context, shader, _) { return AnimatedSampler( (image, size, canvas) { _configureShader(shader, size, image); _drawShaderRect(shader, size, canvas); }, child: Padding( padding: EdgeInsets.symmetric(vertical: cornerRadius), child: widget.child, ), ); }, assetKey: 'shaders/page_curl.frag', ); void _configureShader(FragmentShader shader, Size size, ui.Image image) { shader ..setFloat(0, size.width) // resolution ..setFloat(1, size.height) // resolution ..setFloat(2, _animationController.value) // pointer ..setFloat(3, 0) // origin ..setFloat(4, 0) // inner container ..setFloat(5, 0) // inner container ..setFloat(6, size.width) // inner container ..setFloat(7, size.height) // inner container ..setFloat(8, cornerRadius) // cornerRadius ..setImageSampler(0, image); // image } void _drawShaderRect(FragmentShader shader, Size size, Canvas canvas) { canvas.drawRect( Rect.fromCenter( center: Offset(size.width / 2, size.height / 2), width: size.width, height: size.height, ), Paint()..shader = shader, ); }完整项目可见:https://github.com/Rahiche/riveo_page_curl