【Win 10 應用開發】UI Composition 劄記(五):燈光
UI Composition 除了能夠為 UI 元素建立三維空間外,還有相當重要的一個部件——燈光。宇宙萬物的精彩繽紛,皆源於光明,光,使我們看到各種東西,除了黑洞之外的世界都是五彩斑讕的。故而,真要模擬現實物體,合理的燈光照射是很關鍵,不然就“不像”了。
Composition API 為各種燈光效果提煉了一個公共基類——CompositionLight,它帶有兩個規範性的屬性:
Targets:可視化元素的集合。用來確定場景中哪些東西應該被照亮。比如,你模擬了一面墻,墻壁上掛著各種畫,有山水,有鳥獸,有美女,有蝙蝠,如果你要看畫,黑乎乎的你連根狗毛也看不見的,所以你看到很多美術館或博物館都會安裝各種燈源,只有打燈你才能看到這些畫的。如果你希望看美女,那麽就把美女加入 Targets 集合,這樣美女就會被燈光照亮。
ExclusionsFromTargets:這是一個排除項列表。與上面的剛好反過來,就是指定你不希望被照亮的物體。如果你覺得蝙蝠太猙獰太恐怖,不想看,你可以把它排除掉,就不會被燈光照亮了。
環境光
環境光類似於咱們家裏的白熾光、節能燈等,這種光源比較均勻,基本可以把整個房間照亮。
我們看一個環境光的例子。下面示例,在界面上加載一張圖片,然後我們用環境光去照亮它。順便放一個 Slider 控件,目的是可以調節光照的強度。
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"> <Grid.RowDefinitions> <RowDefinition/> <RowDefinition Height="auto"/> </Grid.RowDefinitions> <Image Source="Assets/5.jpg" Stretch="Uniform" Name="img"/> <Slider Grid.Row="1" Margin="2,9" StepFrequency="0.1"Value="1" Minimum="0" Maximum="10" ValueChanged="OnSliderValChanged"/> </Grid>
切換到代碼文件,在頁面類的構造函數中,咱們添加一下燈光效果。
AmbientLight light = null; public MainPage() { this.InitializeComponent(); Visual v = ElementCompositionPreview.GetElementVisual(img); Compositor compos = v.Compositor; light = compos.CreateAmbientLight(); light.Targets.Add(v); }
註意,我為什麽要把 AmbientLight 的變量聲明到類級別呢,因為可以在後面調整它的強度。下面是 Slider 控件的 ValueChanged 事件的處理代碼。
private void OnSliderValChanged(object sender, RangeBaseValueChangedEventArgs e) { if(light != null) { light.Intensity = (float)e.NewValue; } }
這裏要先判斷一下 light 變量是否為 null,因為這個事件處理是在 XAML 代碼中關聯的,即在頁面類實例構造過程中會調用這個方法(主要是設置 Value 屬性的值時發生),那個時候,環境光對象還沒有創建,如果不判斷,就會出現 null 引用異常。
AmbientLight 類表示環境光,它有一個 Color 屬性,用以指定光的顏色,默認是白光。當物體被白光照亮時,它呈現的是本色(本來面目)。所以,上面代碼的執行效果如下圖。
Intensity 表示光照強度,從上面的例子咱們看到,這個值應該大於 0,小於等於 0 就全黑了,什麽都看不見,那就沒有意義了,值也不要太大,所以我這個例子最大就到 10 ,當然你可以設置 100、1000,可是強度太大了,會亮瞎眼的,什麽也看不見,也是沒有意義的。光照強度默認是 1 ,我們可以根據需要設置合適的值。
我們還可以換一下其他顏色的光,比如,我們改一下代碼,用充滿幽靈意味的綠光去照射一下。
light.Color = Colors.Green;
然後,效果很驚人。
定點光
點光,即 PointLight,它就像一盞小燈泡,發出的光並不能像環境光那樣覆蓋全面,而是點狀的,但它可以照亮四周的物體,而且距離物體近的話,照得更亮,這就很像火把、蠟燭。所以,PointLight 類的屬性會比環境光多一些,也復雜一些。
Color 和 Intensity 屬性是一樣的,前者表示燈光的顏色,後者表示強度。除此之外,還有以下這幾個:ConstantAttenuation、QuadraticAttenuation、LinearAttenuation,這幾個屬性的性質是一樣的,只是算法不同,有的是平方值的,有的是線性的。這些值是用來設置光的衰減速度,啥意思呢,我們剛剛不是說過嗎,點狀光的照亮程度是跟距離有關,隨著燈光與物體的距離增大,亮度會衰減。當然,如果光線很強的情況下,距離遠可能照亮的範圍更大,近距離情況下,會把局部照得更亮。這幾個值就是用來描述光線衰減的速度。在現實世界中,這可能與空氣能見度或空氣密度有關,因為這些要素會影響光的傳播。但在虛擬圖形中不存在真實的大氣,所以需要通過算法來模擬。
由於點狀光是一個發光點,所以它肯定會有位置的,即坐標,下面兩個屬性用來確定點狀光的坐標:Offset 屬性確定位置,它是一個三維坐標;CoordinateSpace 又是啥呢,它要求指定一個可視化對象,用來計算光照的強度的。你想啊,大晚上,你在一片荒野上點根火把,你會覺得這火把好像不怎麽亮,但是,如果你在一個狹窄的山洞裏面點一根火把,你就會覺得它特別亮。所以,這個屬性就是設置一個容器,好確定這點光到底能照多亮。
下面我們看看定點光的例子。
在界面上我們放置一個文本,然後,下面的 Slider 控件用來調整點光的衰減速度,即 ConstantAttenuation 屬性,這個值越大,表明同樣距離下燈光會更弱,因為它衰減得更快更明顯,這個值是大於0的任意值。
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"> <Grid.RowDefinitions> <RowDefinition /> <RowDefinition Height="auto"/> </Grid.RowDefinitions> <Border Background="Black" Grid.Row="0" Name="bd"> <TextBlock VerticalAlignment="Center" HorizontalAlignment="Center" Text="歡迎觀臨" FontSize="150" FontFamily="華文行楷" Foreground="Gold" Name="text"/> </Border> <Slider Grid.Row="1" Margin="2,7" Maximum="5" Minimum="1" Value="1" StepFrequency="0.1" Name="sld"/> </Grid>
TextBlock 為什麽要放到一個 Border 中呢,前面說了,定點光需要一個容器來計算照亮程度,所以,Border 是用來作為參考容器的。
切換到代碼視圖,在頁面類的構造函數中,我們來加一下定點光。
public MainPage() { this.InitializeComponent(); // 獲取容器 Visual vsContainer = ElementCompositionPreview.GetElementVisual(bd); // 獲取 TextBlock 的可視化對象 Visual txtVisual = ElementCompositionPreview.GetElementVisual(text); Compositor compos = vsContainer.Compositor; // 創建光源 PointLight light = compos.CreatePointLight(); // 燈光顏色 light.Color = Colors.Silver; // 強度 light.Intensity = 3.6f; // 位置 light.Offset = new Vector3(500f, 280f, 45f); // 照射目標 light.Targets.Add(txtVisual); // 相對容器 light.CoordinateSpace = vsContainer; // 處理 ValueChanged 事件 sld.ValueChanged += (k, x) => { light.ConstantAttenuation = (float)sld.Value; }; }
這一回處理 ValueChanged 事件就不需要判斷 light 是否為null了,因為附加這個事件處理時,light 對象已經初始化。
註意,這裏我們不僅要獲取 TextBlock 的Visual ,盡管我們的照亮目標是它,但是,因為這種光源需要容器,所以我們要同時獲得 Border 的 Visual。
來,看看效果吧。
錐光
這種光源類似手電筒的光,其實與上面的 Pointlight 很像,但錐光帶有內圈和外圈。所以,錐光也有顏色、強度、衰減程度等參數,當然也會有位置。
InnerConeAngle 是內圈的角度,OuterConeAngle 是外圈的角度,用弧度角表示。如果想用角度,可以用 InnerConeAngleInDegrees 和 OuterConeAngleInDegrees 屬性。
InnerConeIntensity 表示內圈的光線強度,OuterConeIntensity 表示外圈的光線強度。
Offset 表示光的位置,和上面的定點光類似,但錐光多了個 Direction 屬性。用過手電你都知道的,它有個照射方向。如果光源位於物體前方,要想讓它照亮物體,Z軸上的方向必須是負值,只有負值才會照進屏幕裏面;如果光源在物體後面,Z軸上的方向當然要正值,這樣照射方向才會指向屏幕外。
我們做個例子。在界面上放一張圖,先給大家看看原圖。
這書房是不是很高大上呢。然後我們讓它在 Image 元素上加載。
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"> <Border Name="bd" Background="Black"> <Image Name="img" Source="Assets/2.jpg"/> </Border> </Grid>
Image 元素外面也需要一個容器,這裏我還是用Border,因為錐光和定點光一樣,需要一個容器來計算光照。
定位到代碼文件,在頁面類的構造函數中添加光源。
public MainPage() { this.InitializeComponent(); // 獲取目標元素與容器元素 Visual container = ElementCompositionPreview.GetElementVisual(bd); Visual vimg = ElementCompositionPreview.GetElementVisual(img); // 創建光源 SpotLight light = vimg.Compositor.CreateSpotLight(); // 設置容器 light.CoordinateSpace = container; // 添加照亮目標 light.Targets.Add(vimg); // 外圈和內圈光線的顏色 light.OuterConeColor = Colors.Blue; light.InnerConeColor = Colors.LightYellow; // 外圈和內圈光線的強度 light.InnerConeIntensity = 3.2f; light.OuterConeIntensity = 1f; // 角度 light.InnerConeAngleInDegrees = 30f; light.OuterConeAngleInDegrees = 90f; // 位置 light.Offset = new Vector3(550f, 270f, 150f); // 方向 light.Direction = new Vector3(-1f, 1.1f, -1f); }
好了,看看效果吧。
OK,本篇就說到這裏了,開飯了。
【Win 10 應用開發】UI Composition 劄記(五):燈光