zl程序教程

您现在的位置是:首页 >  其他

当前栏目

【Win 10 应用开发】UI Composition 札记(五):灯光

2023-03-20 14:47:05 时间

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