1. 成果 獻祭了周末的晚上,成功召喚出了上面的番茄鐘。正當我在感慨“不愧是Shadow大人,這難道就是傳說中的五彩斑斕的黑?” “那才不是什麼陰影效果,那是發光效果。”被路過的老婆吐槽了。 繫系系,老婆說的都系對的。我還以為我在做陰影動畫,現在只好改博客標題了? 要實現上面的動畫效果,首先使用Co ...
1. 成果
獻祭了周末的晚上,成功召喚出了上面的番茄鐘。正當我在感慨“不愧是Shadow大人,這難道就是傳說中的五彩斑斕的黑?”
“那才不是什麼陰影效果,那是發光效果。”被路過的老婆吐槽了。
繫系系,老婆說的都系對的。我還以為我在做陰影動畫,現在只好改博客標題了?
要實現上面的動畫效果,首先使用CompositionDrawingSurface,在它上面用DrawTextLayout畫出文字,然後用GaussianBlurEffect模仿成陰影,然後用CanvasActiveLayer裁剪文字的輪廓,然後用這個CompositionDrawingSurface創建出CompositionSurfaceBrush,然後創建一個CompositionMaskBrush,將CompositionSurfaceBrush作為它的Mask,然後用CompositionLinearGradientBrush創建出漸變,再用BlendEffect將它變成四向漸變,再用ColorKeyFrameAnimation和ScalarKeyFrameAnimation在它上面做動畫並把它作為CompositionMaskBrush的Source,然後創建SpriteVisual將CompositionMaskBrush應用上去,然後使用兩個PointLight分別從左到右和從右到左照射這個SpriteVisual,再創建一個AmbientLight模仿呼吸燈。
仔細想想……好吧,老婆說得對,我還真的沒有用到任何Shadow的Api,這裡和Shadow大人半毛錢關係都沒有。
這個番茄鐘源碼可以在這裡查看:
OnePomodoro_ShadowTextView.xaml at master
也可以安裝我的番茄鐘應用試玩一下,安裝地址:
這篇文章將介紹其中幾個關鍵技術。
2. 使用GaussianBlurEffect模仿陰影
上一篇文章已經介紹過怎麼在CompositionDrawingSurface
上寫字,這裡就不再重覆。為了可以為文字添加陰影,需要用到CanvasRenderTarget
和GaussianBlurEffect
。
CanvasRenderTarget是一個可以用來畫圖的渲染目標。實現文字陰影的步驟如下:將文字畫到CanvasRenderTarget,然後用它作為GaussianBlurEffect.Source產生一張高斯模糊的圖片,這樣看上去就和文字的陰影一樣。然後再在這張模糊的圖片的前面畫上原本的文字。
代碼如下所示:
using (var session = CanvasComposition.CreateDrawingSession(drawingSurface))
{
session.Clear(Colors.Transparent);
using (var textLayout = new CanvasTextLayout(session, Text, textFormat, width, height))
{
var bitmap = new CanvasRenderTarget(session, width, height);
using (var bitmapSession = bitmap.CreateDrawingSession())
{
bitmapSession.DrawTextLayout(textLayout, 0, 0, FontColor);
}
var blur = new GaussianBlurEffect
{
BlurAmount = (float)BlurAmount,
Source = bitmap,
BorderMode = EffectBorderMode.Hard
};
session.DrawImage(blur, 0, 0);
session.DrawTextLayout(textLayout, 0, 0, FontColor);
}
}
效果如下(因為我用了白色字體,這時候已經不怎麼像陰影了):
關於CavasRenderTaget,死魚的這篇文章有詳細介紹。他的這個專欄的文章都很有趣。
3. 使用CanvasActiveLayer裁剪文字
關於裁剪文字,有幾件事需要做。
首先獲取需要裁剪的文字的輪廓,這使用上一篇文章介紹過的CanvasGeometry.CreateText
就可以了,這個函數的返回值是一個CanvasGeometry。然後使用CanvasGeometry.CreateRectangle
獲取整個畫布的CanvasGeometry,將他們用CombineWith相減得出文字以外的部分,具體代碼如下:
var fullSizeGeometry = CanvasGeometry.CreateRectangle(session, 0, 0, width, height);
var textGeometry = CanvasGeometry.CreateText(textLayout);
var finalGeometry = fullSizeGeometry.CombineWith(textGeometry, Matrix3x2.Identity, CanvasGeometryCombine.Exclude);
這裡之所以不直接使用textGeometry,是因為我們並不是真的裁剪出文字的部分,而是像WPF的OpacityMask那樣用透明度控制顯示的部分。CanvasActiveLayer就是用來實現這個功能。CanvasDrawingSession.CreateLayer
函數使用透明度和CanvasGeometry創建一個CanvasActiveLayer
,在創建Layer後CanvasDrawingSession
的操作都會應用這個透明度,直到Layer關閉。
using (var layer = session.CreateLayer(1, finalGeometry))
{
//DrawSth
}
最後效果如下:
關於CanvasActiveLayer的更多用法, 可以參考Lindexi的這篇文章。
4. 製作有複雜顏色的陰影
如上圖所示,UWP中的DropShadow的Color只能有一種顏色,所以DropShadow不能使用複雜的顏色。這時候就要用到CompositionMaskBrush,CompositionMaskBrush有兩個主要屬性:Mask和Source。其中Mask是一個CompositionBrush
類型的屬性,它指定不透明的蒙板源。簡單來說,CompositionMaskBrush的形狀就是它的Mask的形狀。而Source屬性則是它的顏色,這個屬性可以是 CompositionColorBrush、CompositionLinearGradientBrush、CompositionSurfaceBrush、CompositionEffectBrush 或 CompositionNineGridBrush 類型的任何 CompositionBrush。可以使用前面創建的CompositionDrawingSurface創建出CompositionSurfaceBrush,最後創建一個CompositionMaskBrush,將CompositionSurfaceBrush作為它的Mask。
var maskBrush = Compositor.CreateMaskBrush();
maskBrush.Mask = Compositor.CreateSurfaceBrush(DrawingSurface);
maskBrush.Source = Compositor.CreateLinearGradientBrush();
本來還想做到大紫大紅的,但被吐槽和本來低調內斂的目的不符合,所以復用了以前這篇文章的配色,CompositionLinearGradientBrush加BlendEffect做成了有些複雜的配色(但實際上太暗了看不出來):
這時候效果如下:
5. 使用PointLight和AmbientLight製作動畫
我在使用PointLight並實現動畫效果這篇文章里介紹了PointLight的用法及基本動畫,這次豪華些,同時有從左到右的紅光以及從右到左的藍光,這兩個PointLight的動畫效果大致是這樣:
因為PointLight最多只能疊加兩個,所以再使用AmbientLight
並對它的Intensity
屬性做動畫,這樣動畫就會變得複雜些,最終實現了文章開頭的動畫。
var compositor = Window.Current.Compositor;
var ambientLight = compositor.CreateAmbientLight();
ambientLight.Intensity = 0;
ambientLight.Color = Colors.White;
var intensityAnimation = compositor.CreateScalarKeyFrameAnimation();
intensityAnimation.InsertKeyFrame(0.2f, 0, compositor.CreateLinearEasingFunction());
intensityAnimation.InsertKeyFrame(0.5f, 0.20f, compositor.CreateLinearEasingFunction());
intensityAnimation.InsertKeyFrame(0.8f, 0, compositor.CreateLinearEasingFunction());
intensityAnimation.Duration = TimeSpan.FromSeconds(10);
intensityAnimation.IterationBehavior = AnimationIterationBehavior.Forever;
ambientLight.StartAnimation(nameof(AmbientLight.Intensity), intensityAnimation);
6. 參考
CompositionMaskBrush Class (Windows.UI.Composition) - Windows UWP applications _ Microsoft Docs
組合照明 - Windows UWP applications Microsoft Docs