1. 程式人生 > >【Win 10 應用開發】UI Composition 劄記(五):燈光

【Win 10 應用開發】UI Composition 劄記(五):燈光

傳播 目標 spa 速度 review sta sset ext 集合

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 劄記(五):燈光