• 【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,本篇就说到这里了,开饭了。

  • 相关阅读:
    如何修改注册表立刻生效【搜藏】
    c#怎样让picturebox出现滚动条【搜藏】
    c#怎样让程序运行出错仍继续执行【原】
    marquee标签里文本的自动换行【搜藏】
    关于HyperLink的NavigateUrl属性的链接地址带参数出错的问题【整理】
    C#程序获得星期【整理】
    sql分别获取年/月/日 函数【搜藏】
    获取本周属于本年度第几周【搜藏】
    关于ref 和 out 关键字【整理】
    hdu 1823 Luck and Love
  • 原文地址:https://www.cnblogs.com/tcjiaan/p/7821066.html
Copyright © 2020-2023  润新知