2

在WPF中使用PlaneProjection模拟动态3D效果

 2 years ago
source link: https://hypersharp.net/coding/33-wpf-3d-effects-by-plane-projection/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

在WPF中使用PlaneProjection模拟动态3D效果

2013年3月12日 / 编程

  虽然在WPF中也集成了3D呈现的功能,在简单的3D应用中,有时候并不需要真实光影的3D场景。毕竟使用3D引擎会消耗很多资源,有时候使用各种变换和假的阴影贴图也能设计出既省资源,又有很好用户体验的“伪”3D界面。

  在Silverlight中,因为性能问题,一般并不使用真3D引擎,微软为Silverlight提供了System.Windows.Media.PlaneProjection 类,用投影变换来模拟3D的效果。

  下面让我们看下一个 Microsoft Expression Blend 4 提供的示例 Wall3D (位于帮助>欢迎屏幕>示例)。

blend-4-wall3d-project-1024x614.jpg

大家不要被这个可以流畅滚动的3D图片墙所迷惑,其实这只是一个ListBox控件。MainPage中给ListBox定义了一个ItemsPanelTemplate,使用新的控件来作为ListBox中Items的布局控件,这个控件就是这个项目最核心的类:CircularPanel3D。

CircularPanel3D类继承自System.Windows.Controls.Panel,它实现了一种新的布局方式,效果大家在上一张图片中都看到了。这种华丽的效果实际上都是由这个最重要的类中的最重要的方法: private void Refresh() 完成的。

private void Refresh()
//几个计数器,看名字就功能很明了
int count = 0;
int col = 0;
int row = 0;
int zLevel = 0;
//开始遍历子元素
foreach (FrameworkElement childElement in this.Children)
//AngleItem是指单个元素的旋转角度,算法是360除以列数
//这个方法的布局方式是先布满一圈,再下一环的,角度总是可以取模的
//所以这边直接AngleItem和count相乘了
//InitialAngle这个属性是用来确定整个圆环的偏转角度的,每次这个依赖属性变化就会重新计算布局(调用这个方法)
double angle = (this.AngleItem * count++) - this.InitialAngle;
//下面两个变量用来确定元素在屏幕上的位置,用到了三角函数,数学不好的请问高中数学老师
double x = this.Radius * Math.Cos(Math.PI * angle / 180);
double z = this.Radius * Math.Sin(Math.PI * angle / 180);
//创建个PlaneProjection对象,并赋值
PlaneProjection projection = new PlaneProjection();
if (projection != null)
projection.CenterOfRotationX = 0.5;
projection.CenterOfRotationY = 0.5;
projection.CenterOfRotationZ = 0.5;
projection.RotationY = angle + 90;
projection.GlobalOffsetX = x;
//Distance实际上就是模拟的镜头距离
projection.GlobalOffsetZ = z - this.Distance;
//-330。。。坑爹的硬编码,实际上就是两行元素的间距,OffsetY是纵向的偏移量,用于调整环在屏幕上的位置
projection.GlobalOffsetY = row * (-330) + this.OffsetY;
//实际上是让double数变成int数,但是又不会丧失区别性,下面要用到
int depth = (int)(z * 100);
double pDist = (this.Distance - 1000) / 2000;
double pZ = ((z + 1000) / 2000) + 0.5;
//让太远的和太近的变透明
double opacity = (pZ - pDist) + 0.4;
if (opacity >= 1)
childElement.Opacity = (2 - opacity);
else if (opacity < 0)
childElement.Opacity = 0;
childElement.Opacity = opacity;
// 嗯这边有原版的英文注释,不解释
// Variable zLevel changes value of ZIndex for each item in the ListBox.
// This way the reflex of elements at the top will be placed behind the item below it.
Canvas.SetZIndex(childElement, depth - (++zLevel * 10));
//根据Align属性设置对齐方式,不是很重要
double alignX = 0;
double alignY = 0;
switch (this.Align)
case AlignmentOptions.Left:
alignX = 0;
alignY = 0;
break;
case AlignmentOptions.Center:
alignX = childElement.DesiredSize.Width / 2;
alignY = childElement.DesiredSize.Height / 2;
break;
case AlignmentOptions.Right:
alignX = childElement.DesiredSize.Width;
alignY = childElement.DesiredSize.Height;
break;
//将PlaneProjection对象赋给子元素的Projection属性
childElement.Projection = projection;
//定位子元素
childElement.Arrange(new Rect(this.Width / 2 - alignX, this.Height / 2 - alignY, childElement.DesiredSize.Width, childElement.DesiredSize.Height));
//换行,又见坑爹的硬编码14。。这个代表有十四列
col++;
if (col > 14)
col = 0;
row++;
private void Refresh()
{
    //几个计数器,看名字就功能很明了
    int count = 0;
    int col = 0;
    int row = 0;
    int zLevel = 0;

    //开始遍历子元素
    foreach (FrameworkElement childElement in this.Children)
    {
        //AngleItem是指单个元素的旋转角度,算法是360除以列数
        //这个方法的布局方式是先布满一圈,再下一环的,角度总是可以取模的
        //所以这边直接AngleItem和count相乘了
        //InitialAngle这个属性是用来确定整个圆环的偏转角度的,每次这个依赖属性变化就会重新计算布局(调用这个方法)
        double angle = (this.AngleItem * count++) - this.InitialAngle;
        //下面两个变量用来确定元素在屏幕上的位置,用到了三角函数,数学不好的请问高中数学老师
        double x = this.Radius * Math.Cos(Math.PI * angle / 180);
        double z = this.Radius * Math.Sin(Math.PI * angle / 180);
        //创建个PlaneProjection对象,并赋值
        PlaneProjection projection = new PlaneProjection();
        if (projection != null)
        {
            projection.CenterOfRotationX = 0.5;
            projection.CenterOfRotationY = 0.5;
            projection.CenterOfRotationZ = 0.5;
            projection.RotationY = angle + 90;
            projection.GlobalOffsetX = x;
            //Distance实际上就是模拟的镜头距离
            projection.GlobalOffsetZ = z - this.Distance;
            //-330。。。坑爹的硬编码,实际上就是两行元素的间距,OffsetY是纵向的偏移量,用于调整环在屏幕上的位置
            projection.GlobalOffsetY = row * (-330) + this.OffsetY;
        }
        //实际上是让double数变成int数,但是又不会丧失区别性,下面要用到
        int depth = (int)(z * 100);

        double pDist = (this.Distance - 1000) / 2000;
        double pZ = ((z + 1000) / 2000) + 0.5;

        //让太远的和太近的变透明
        double opacity = (pZ - pDist) + 0.4;
        if (opacity >= 1)
        {
            childElement.Opacity = (2 - opacity);
        }
        else if (opacity < 0)
        {
            childElement.Opacity = 0;
        }
        else
        {
            childElement.Opacity = opacity;
        }

        // 嗯这边有原版的英文注释,不解释
        // Variable zLevel changes value of ZIndex for each item in the ListBox.
        // This way the reflex of elements at the top will be placed behind the item below it.
        Canvas.SetZIndex(childElement, depth - (++zLevel * 10));

        //根据Align属性设置对齐方式,不是很重要
        double alignX = 0;
        double alignY = 0;
        switch (this.Align)
        {
            case AlignmentOptions.Left:
                alignX = 0;
                alignY = 0;
                break;
            case AlignmentOptions.Center:
                alignX = childElement.DesiredSize.Width / 2;
                alignY = childElement.DesiredSize.Height / 2;
                break;
            case AlignmentOptions.Right:
                alignX = childElement.DesiredSize.Width;
                alignY = childElement.DesiredSize.Height;
                break;
        }
        //将PlaneProjection对象赋给子元素的Projection属性
        childElement.Projection = projection;
        //定位子元素
        childElement.Arrange(new Rect(this.Width / 2 - alignX, this.Height / 2 - alignY, childElement.DesiredSize.Width, childElement.DesiredSize.Height));

        //换行,又见坑爹的硬编码14。。这个代表有十四列
        col++;
        if (col > 14)
        {
            col = 0;
            row++;
        }
    }
}




About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK