隨著 iOS 26 發布,「液態玻璃」無疑是熱度最高的標簽,不僅僅是因為設計風格大變,更是因為 iOS 26 beta1 的各種 bug 帶來的毛坯感讓 iOS 26 沖上熱搜,比如通知中心和控制中心看起來就像是一個半成品:
當然,很多人可能說,不就是一個毛玻璃效果嗎?實際上還真有些不大一樣,特別是不同控件的“模糊”和“液態”效果都不大一樣,效果好不好看一回事,但是液態玻璃確實不僅僅只是一個模糊圖層,至少從下面這個鎖屏效果可以看到它類似液態的扭曲變化:
所以,在實現上就不可能只是一個簡單的 blur
,類似效果肯定是需要通過自定義著色器實現,而恰好在 shadertoy 就有人發布了類似的實現,可以比較方便移植到 Flutter :
針對這個 shader ,其中 LiquidGlass
部分是實現磨砂玻璃效果的核心:
-
vec2 radius = size / R;
計算模糊的半徑,將其從像素單位轉換為標準化坐標。 -
vec4 color = texture(tex, uv);
獲取當前像素uv
處的原始顏色 -
for (float d = 0.0; d < PI; d += PI / direction)
: 外層循環,確定采樣的方向,從 0 到 180 度進行迭代。 -
for (float i = 1.0 / quality; i <= 1.0; i += 1.0 / quality)
內層循環,沿著當前方向d
進行多次采樣,quality
越高,采樣點越密集 -
color += texture(tex, uv + vec2(cos(d), sin(d)) * radius * i);
在當前像素周圍的圓形區域內進行采樣,vec2(cos(d), sin(d))
計算出方向向量,radius * i
確定了沿該方向的采樣距離,通過累加這些采樣點的顏色,實際上是在對周圍的像素顏色進行平均 -
color /= (quality * direction + 1.0);
將累加的顏色值除以總采樣次數(以及原始顏色),得到平均顏色,這個平均過程就是實現模糊效果的過程
vec4 LiquidGlass(sampler2D tex, vec2 uv, float direction, float quality, float size) {vec2 radius = size / R;vec4 color = texture(tex, uv);for (float d = 0.0; d < PI; d += PI / direction) {for (float i = 1.0 / quality; i <= 1.0; i += 1.0 / quality) {color += texture(tex, uv + vec2(cos(d), sin(d)) * radius * i);}}color /= (quality * direction + 1.0); // +1.0 for the initial colorreturn color;
}
而在著色器的入口,它會將所有部分組合起來渲染,其中關鍵在于下方代碼,這是實現邊緣液體感的處理部分:
#define S smoothstepvec2 uv2 = uv - uMouse.xy / R;
uv2 *= 0.5 + 0.5 * S(0.5, 1.0, icon.y);
uv2 += uMouse.xy / R;
它不是直接用 uv
去采樣紋理,而是創建了一個被扭曲的新坐標 uv2
,icon.y
是前面生成的位移貼圖,smoothstep
函數利用這個貼圖來計算一個縮放因子。
在圖標中心(icon.y
接近 1),縮放因子最大,使得 uv2
的坐標被推離中心,產生放大/凸起的效果,就像透過一滴水或一個透鏡看東西一樣,從而實現視覺上的折射效果。
最后利用 mix 把背景圖片混合進來,其中 LiquidGlass(uTexture, uv2, ...)
通過玻璃效果使用被扭曲的坐標 uv2
去采樣并模糊背景:
vec3 col = mix(texture(uTexture, uv).rgb * 0.8,0.2 + LiquidGlass(uTexture, uv2, 10.0, 10.0, 20.0).rgb * 0.7,icon.x
);
所以里實現的思路是扭曲的背景 + 模糊處理,我們把中間的 icon 部分屏蔽,換一張人臉圖片,可以看到更明顯的邊緣扭曲效果:
當然,這個效果看起來并不明顯,我們還可以在這個基礎上做修改,比如屏蔽 uv2 *= 0.5 + 0.5 * S(0.5, 1.0, icon.y)
,調整為從中間進行放大扭曲:
//uv2 *= 0.5 + 0.5 * S(0.5, 1.0, icon.y);// 使用 mix 函數,以 icon.x (方塊形狀) 作為混合因子
// 在方塊外部 (icon.x=0),縮放為 1.0 (不扭曲)
// 在方塊內部 (icon.x=1),縮放為 0.8 (最大扭曲)
uv2 *= mix(1.0, 0.8, icon.x);
通過調整之后,實際效果可以看到變成從中間放大扭曲,從眼神扭曲上看起來更接近鎖屏里的效果:
當然,我們還可以讓扭曲按照類似水滴從中間進行扭曲,來實現非平均的液態放大:
//vec2 uv2 = uv - uMouse.xy / R;//uv2 *= 0.5 + 0.5 * S(0.5, 1.0, icon.y);//uv2 += uMouse.xy / R;// ================== 新的水滴扭曲 ==================// 1. 計算當前像素到鼠標中心點的向量 (在 st 空間)
vec2 p = st - M;// 2. 計算該點到中心的距離
float dist = length(p);// 3. 定義水滴效果的作用半徑 (應與方塊大小一致)
float radius = PX(100.0);// 4. 計算“水滴凸起”的強度因子 (bulge_factor)
// 我們希望中心點 (dist=0) 強度為 1,邊緣點 (dist=radius) 強度為 0。
// 使用 1.0 - smoothstep(...) 可以創造一個從中心向外平滑衰減的效果,模擬水滴的弧度。
float bulge_factor = 1.0 - smoothstep(0.0, radius, dist);// 5. 確保該效果只在我們的方塊遮罩 (icon.x) 內生效
bulge_factor *= icon.x;// 6. 定義中心點的最大縮放量 (0.5 表示放大一倍,值越小放大越明顯)
float max_zoom = 0.5;// 7. 使用 mix 函數,根據水滴強度因子,在 "不縮放(1.0)" 和 "最大縮放(max_zoom)" 之間插值
// 中心點 bulge_factor ≈ 1, scale ≈ max_zoom (放大最強)
// 邊緣點 bulge_factor ≈ 0, scale ≈ 1.0 (不放大)
float scale = mix(1.0, max_zoom, bulge_factor);// 8. 應用這個非均勻的縮放效果
vec2 uv2 = uv - uMouse.xy / R; // 將坐標中心移到鼠標位置
uv2 *= scale; // 應用計算出的縮放比例
uv2 += uMouse.xy / R; // 將坐標中心移回
使用這個非均勻的縮放效果,可以看到效果更接近我們想象中的液態 “放大”:
如下圖所示,最終看起來也會更想水面的放大,同時邊緣的“高亮”也顯得更加明顯:
當然,這里的實現都是非常粗糙的復刻,僅僅只是自娛自樂,不管是性能還是效果肯定和 iOS 26 的液態玻璃相差甚遠,就算不考慮能耗,想在其他平臺或者框架實現類似效果的成本并不低,所以單從技術實現上來說,能用液態玻璃風格作為系統 UI,蘋果應該是對于能耗控制和渲染成本控制相當自信才是。
最后,如果感興趣的可以直接通過下方鏈接獲取 Demo :
-
https://github.com/CarGuo/gsy_flutter_demo/blob/master/lib/widget/liquid_glass_demo.dart
-
https://github.com/CarGuo/gsy_flutter_demo/blob/master/lib/widget/liquid_glass_demo2.dart
-
https://github.com/CarGuo/gsy_flutter_demo/tree/master/shaders
參考鏈接:
-
https://www.shadertoy.com/view/WftXD2
-
https://rive.app/marketplace/20904-39287-liquid-glass/