最近折腾Viewport3D玩,遇到了一些诡异的问题,研究一下略有心得,特此和大家分享~
概要
三维坐标系
二维图形的 WPF 坐标系将原点定位在呈现区域(通常是屏幕)的左上角。 在二维系统中,x 轴上的正值朝右,y 轴上的正值朝下。 但是,在三维坐标系中,原点位于呈现区域的中心,x 轴上的正值朝右,但是 y 轴上的正值朝上,z 轴上的正值从原点向外朝向观察者。
照相机
透视投影和正投影
关于TextureCoordinates(纹理坐标):
<MeshGeometry3D Positions="-250,-150,0 250,-150,0 250,150,0 -250,150,0"
TextureCoordinates="0,1 1,1 1,0 0,0"
TriangleIndices="0,1,2 0,2,3" />
上面这段MeshGeometry3D(用于生成3-D形状的三角形基元)对照图2三维坐标系会发现是个从左下角逆时针绘制的矩形。如果要将一个窗体UserControl纹理应用到Viewport2DVisual3D上,我们就需要合理的设置TextureCoordinates(纹理坐标)。对照图1二维坐标系,纹理坐标同样是由左下开始绘制的矩形。这样设置,纹理就可以显示正常了。
关于视角和距离的关系:
含义:
点 | 说明 |
A | 视点 |
| |
线 | 说明 |
BC | 查看的目标宽度 |
bc | 展示的视窗宽度 |
AD | 视点到查看目标的距离 |
ad | 视点到视窗的距离 |
| |
角度 | 说明 |
cAb | 视角(FieldOfView) |
| |
结论:
BD/bd=AD/Ad
我们如果想看到一个原样大小的窗体,就需要设置我的角cAb为90。同时设置AD长度为查看目标的一半宽度。
好不容易设置好Viewport3D,将UserControl作为窗体的查看对象,却发现虽然窗体大小一致,但是显示出的UserControl字体和控件却很模糊。当我们以3D模型渲染窗体时,WPF应该做了性能优化,然而我并不清楚如何开启高清模式= =。
怎么解决这个问题呢?我试了好久,发现并没有比较简单快捷的解决方法。没办法,只能采取一些小技巧来解决啦。
具体思路:
第一步:窗体呈现时,将UserControl实例化后直接呈现到页面最前端的展示面板上,此时Viewport3D内容为空。
第二部:触发页面翻转动画时,将最前的展示面板置空,UserControl实例剪切到Viewport3D里面。
第三步:动画执行完毕后再从Viewport3D里面将UserControl剪切到页面最前端的展示面板中。
示例代码:
<Window x:Class="App3DWindow.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:app3DWindow="clr-namespace:App3DWindow"
xmlns:asyncDelegate="clr-namespace:AsyncDelegate"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Title="MainWindow"
Width="500"
Height="310"
AllowsTransparency="True"
Background="Transparent"
Loaded="MainWindow_OnLoaded"
WindowStyle="None"
WindowStartupLocation="CenterScreen"
mc:Ignorable="d">
<Window.Resources>
<Storyboard x:Key="Turn2ConfigViewStoryboard">
<Rotation3DAnimationUsingKeyFrames Storyboard.TargetName="containerUIElement3D" Storyboard.TargetProperty="(Visual3D.Transform).(Transform3DGroup.Children)[2].(RotateTransform3D.Rotation)">
<EasingRotation3DKeyFrame KeyTime="0:0:2">
<EasingRotation3DKeyFrame.EasingFunction>
<QuinticEase EasingMode="EaseOut" />
</EasingRotation3DKeyFrame.EasingFunction>
<EasingRotation3DKeyFrame.Value>
<AxisAngleRotation3D Angle="180" Axis="0,1,0" />
</EasingRotation3DKeyFrame.Value>
</EasingRotation3DKeyFrame>
</Rotation3DAnimationUsingKeyFrames>
</Storyboard>
<Storyboard x:Key="Turn2DownloadViewStoryboard">
<Rotation3DAnimationUsingKeyFrames Storyboard.TargetName="containerUIElement3D" Storyboard.TargetProperty="(Visual3D.Transform).(Transform3DGroup.Children)[2].(RotateTransform3D.Rotation)">
<EasingRotation3DKeyFrame KeyTime="0:0:2">
<EasingRotation3DKeyFrame.EasingFunction>
<QuinticEase EasingMode="EaseOut" />
</EasingRotation3DKeyFrame.EasingFunction>
<EasingRotation3DKeyFrame.Value>
<AxisAngleRotation3D Angle="360" Axis="0,1,0" />
</EasingRotation3DKeyFrame.Value>
</EasingRotation3DKeyFrame>
</Rotation3DAnimationUsingKeyFrames>
</Storyboard>
<!-- 相机 -->
<PerspectiveCamera x:Key="PerspectiveCamera"
FieldOfView="90"
LookDirection="0,0,-1"
NearPlaneDistance="1"
Position="0,0,250"
UpDirection="0,1,0" />
<OrthographicCamera x:Key="OrthographicCamera"
Width="500"
LookDirection="0,0,-1"
Position="0,0,250"
UpDirection="0,1,0" />
</Window.Resources>
<Grid Background="Transparent">
<Viewport3D Margin="0">
<Viewport3D.Camera>
<!-- 相机 -->
<PerspectiveCamera x:Name="PerspectiveCamera"
FieldOfView="90"
LookDirection="0,0,-1"
NearPlaneDistance="1"
Position="0,0,250"
UpDirection="0,1,0" />
</Viewport3D.Camera>
<Viewport3D.Children>
<ContainerUIElement3D x:Name="containerUIElement3D">
<ContainerUIElement3D.Transform>
<Transform3DGroup>
<TranslateTransform3D OffsetX="0"
OffsetY="0"
OffsetZ="0" />
<ScaleTransform3D ScaleX="1"
ScaleY="1"
ScaleZ="1" />
<RotateTransform3D d:EulerAngles="0,0,0">
<RotateTransform3D.Rotation>
<AxisAngleRotation3D Angle="0" Axis="0,1,0" />
</RotateTransform3D.Rotation>
</RotateTransform3D>
<TranslateTransform3D OffsetX="0"
OffsetY="0"
OffsetZ="0" />
<TranslateTransform3D OffsetX="0"
OffsetY="0"
OffsetZ="0" />
</Transform3DGroup>
</ContainerUIElement3D.Transform>
<!-- 正面视角 -->
<Viewport2DVisual3D x:Name="FrontViewport2DVisual3D">
<Viewport2DVisual3D.Transform>
<Transform3DGroup>
<TranslateTransform3D OffsetX="0"
OffsetY="0"
OffsetZ="0" />
<ScaleTransform3D ScaleX="1"
ScaleY="1"
ScaleZ="1" />
<RotateTransform3D d:EulerAngles="0,0,0">
<RotateTransform3D.Rotation>
<AxisAngleRotation3D Angle="0" Axis="0,0,0" />
</RotateTransform3D.Rotation>
</RotateTransform3D>
<TranslateTransform3D OffsetX="0"
OffsetY="0"
OffsetZ="0" />
<TranslateTransform3D OffsetX="0"
OffsetY="0"
OffsetZ="0" />
</Transform3DGroup>
</Viewport2DVisual3D.Transform>
<Viewport2DVisual3D.Geometry>
<MeshGeometry3D Positions="-250,-150,0 250,-150,0 250,150,0 -250,150,0"
TextureCoordinates="0,1 1,1 1,0 0,0"
TriangleIndices="0,1,2 0,2,3" />
</Viewport2DVisual3D.Geometry>
<Viewport2DVisual3D.Material>
<DiffuseMaterial Viewport2DVisual3D.IsVisualHostMaterial="True" />
</Viewport2DVisual3D.Material>
<Viewport2DVisual3D.Visual>
<app3DWindow:DownloadWindow x:Name="DownloadWindowPart"
Width="500"
Height="300"
Background="White"
SnapsToDevicePixels="True" />
</Viewport2DVisual3D.Visual>
</Viewport2DVisual3D>
<!-- 背面视角 -->
<Viewport2DVisual3D x:Name="BackViewport2DVisual3D">
<Viewport2DVisual3D.Transform>
<Transform3DGroup>
<TranslateTransform3D OffsetX="0"
OffsetY="0"
OffsetZ="0" />
<ScaleTransform3D ScaleX="1"
ScaleY="1"
ScaleZ="1" />
<RotateTransform3D d:EulerAngles="0,0,0">
<RotateTransform3D.Rotation>
<AxisAngleRotation3D Angle="0" Axis="0,0,0" />
</RotateTransform3D.Rotation>
</RotateTransform3D>
<TranslateTransform3D OffsetX="0"
OffsetY="0"
OffsetZ="0" />
<TranslateTransform3D OffsetX="0"
OffsetY="0"
OffsetZ="0" />
</Transform3DGroup>
</Viewport2DVisual3D.Transform>
<Viewport2DVisual3D.Geometry>
<MeshGeometry3D Positions="-250,-150,0 250,-150,0 250,150,0 -250,150,0"
TextureCoordinates="1,1 0,1 0,0 1,0 "
TriangleIndices="0,3,2 0,2,1" />
</Viewport2DVisual3D.Geometry>
<Viewport2DVisual3D.Material>
<DiffuseMaterial Viewport2DVisual3D.IsVisualHostMaterial="True" />
</Viewport2DVisual3D.Material>
<Viewport2DVisual3D.Visual>
<app3DWindow:ProxyConfigView x:Name="ProxyConfigViewPart"
Width="500"
Height="300"
Background="White"
SnapsToDevicePixels="True" />
</Viewport2DVisual3D.Visual>
</Viewport2DVisual3D>
</ContainerUIElement3D>
<ModelVisual3D>
<ModelVisual3D.Content>
<!-- 光源 -->
<AmbientLight x:Name="ViewLight" />
</ModelVisual3D.Content>
</ModelVisual3D>
</Viewport3D.Children>
</Viewport3D>
<Grid x:Name="DisplayGrid">
<Grid.RowDefinitions>
<RowDefinition Height="40" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<UserControl x:Name="DisPlayControl"
Grid.Row="0"
Grid.RowSpan="2" />
<!--<Border x:Name="Part_Drag"
Grid.Row="0"
Background="AliceBlue"
PreviewMouseLeftButtonDown="DisPlayControl_OnPreviewMouseLeftButtonDown" />-->
</Grid>
</Grid>
</Window>
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media.Animation;
namespace App3DWindow
{
/// <summary>
/// MainWindow.xaml 的交互逻辑
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
/// <summary>
/// 载入时进行模糊情况特殊处理
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void MainWindow_OnLoaded(object sender, RoutedEventArgs e)
{
//return;
SetDownloadWindowDisplayMode();
var turn2ConfigViewStoryboard = this.TryFindResource("Turn2ConfigViewStoryboard") as Storyboard;
if (turn2ConfigViewStoryboard != null)
{
turn2ConfigViewStoryboard.Completed += (s, e1) =>
{//动画执行完成切换展示模式
SetConfigViewDisplayMode();
SetDownloadWindowTurningMode();
};
turn2ConfigViewStoryboard.CurrentTimeInvalidated += (s, e1) =>
{//动画执行时切换成翻转模式
SetDownloadWindowTurningMode();
};
};
var turn2DownloadViewStoryboard = this.TryFindResource("Turn2DownloadViewStoryboard") as Storyboard;
if (turn2DownloadViewStoryboard != null)
{
turn2DownloadViewStoryboard.Completed += (s, e1) =>
{//动画执行完成切换展示模式
SetDownloadWindowDisplayMode();
SetConfigViewTurningMode();
};
turn2DownloadViewStoryboard.CurrentTimeInvalidated += (s, e1) =>
{//动画执行时切换成翻转模式
SetConfigViewTurningMode();
};
};
}
/// <summary>
/// 设置下载页面进入展示模式
/// </summary>
private void SetDownloadWindowDisplayMode()
{
//var container = DownloadWindowPart.Parent as Viewport2DVisual3D;
//if (container == null) return;
//container.Visual = null;
if (FrontViewport2DVisual3D.Visual==(DownloadWindowPart))
{
FrontViewport2DVisual3D.Visual = null;
}
DisPlayControl.Content = DownloadWindowPart;
}
/// <summary>
/// 设置配置页面进入展示模式
/// </summary>
private void SetConfigViewDisplayMode()
{
//var container = ProxyConfigViewPart.Parent as Viewport2DVisual3D;
//if (container == null) return;
//container.Visual = null;
if (BackViewport2DVisual3D.Visual==(ProxyConfigViewPart))
{
BackViewport2DVisual3D.Visual = null;
}
DisPlayControl.Content = ProxyConfigViewPart;
}
/// <summary>
/// 设置下载页面进入翻转模式
/// </summary>
private void SetDownloadWindowTurningMode()
{
var container = DownloadWindowPart.Parent as UserControl;
if (container == null) return;
container.Content = null;
FrontViewport2DVisual3D.Visual = DownloadWindowPart;
}
/// <summary>
/// 设置配置页面进入翻转模式
/// </summary>
private void SetConfigViewTurningMode()
{
var container = ProxyConfigViewPart.Parent as UserControl;
if (container == null) return;
container.Content = null;
BackViewport2DVisual3D.Visual = ProxyConfigViewPart;
}
/// <summary>
/// 支持拖拽
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void DisPlayControl_OnPreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
this.DragMove();
}
}
}
呈现效果:
效果见下图,翻转动画执行完毕后不再模糊。