«Полеты» трехмерных фигур
22WPF --- Графика и анимация WPF --- «Полеты» трехмерных фигур
Распространенный эффект, часто присутствующий в трехмерных сценах — перемещение камеры вокруг объекта. Эта задача концептуально проста для реализации в WPF: просто нужно использовать TranslateTransform для перемещения камеры.
Однако должны быть учтены два обстоятельства:
Обычно камера будет перемещаться по некоторому маршруту, а не просто по прямой линии от начальной точки к конечной. Есть два способа добиться этого: можно использовать анимацию на основе пути, чтобы провести камеру по геометрически заданному маршруту, или же применить анимацию ключевого кадра, определяющую несколько меньших сегментов.
По мере движения камеры необходимо подстраивать направление ее взгляда. Чтобы сохранить ориентацию на объект, также придется анимировать свойство LookDirection.
Следующий код разметки показывает анимацию, при которой камера "пролетает" через центр тора, оборачивается к его задней стороне и в конечном итоге возвращается в начальную точку:
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<StackPanel Orientation="Horizontal" Margin="5">
<Button Margin="3" Padding="3">
<Button.Content>Rotate Torus</Button.Content>
<Button.Triggers>
<EventTrigger RoutedEvent="Button.Click">
<BeginStoryboard>
<Storyboard RepeatBehavior="Forever">
<DoubleAnimation Storyboard.TargetName="ring"
Storyboard.TargetProperty="Transform.Children[0].Rotation.Angle" To="360" Duration="0:0:2.5"/>
<DoubleAnimation Storyboard.TargetName="ring"
Storyboard.TargetProperty="Transform.Children[1].Rotation.Angle" To="360" Duration="0:0:2.5"/>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Button.Triggers>
</Button>
<Button Margin="3" Padding="3">
<Button.Content>Begin Fly-Through</Button.Content>
<Button.Triggers>
<EventTrigger RoutedEvent="Button.Click">
<BeginStoryboard>
<Storyboard>
<Point3DAnimationUsingKeyFrames
Storyboard.TargetName="camera"
Storyboard.TargetProperty="Position"
>
<LinearPoint3DKeyFrame Value="0,0.2,-1" KeyTime="0:0:10"/>
<LinearPoint3DKeyFrame Value="-0.5,0.2,-1" KeyTime="0:0:15"/>
<LinearPoint3DKeyFrame Value="-0.5,0.5,0" KeyTime="0:0:20"/>
<LinearPoint3DKeyFrame Value="0,0,2" KeyTime="0:0:23"/>
</Point3DAnimationUsingKeyFrames>
<Vector3DAnimationUsingKeyFrames
Storyboard.TargetName="camera"
Storyboard.TargetProperty="LookDirection"
>
<LinearVector3DKeyFrame Value="-1,-1,-3" KeyTime="0:0:4"/>
<LinearVector3DKeyFrame Value="-1,-1,3" KeyTime="0:0:10"/>
<LinearVector3DKeyFrame Value="1,0,3" KeyTime="0:0:14"/>
<LinearVector3DKeyFrame Value="0,0,-1" KeyTime="0:0:22"/>
</Vector3DAnimationUsingKeyFrames>
<!-- Add this to wiggle the camera. -->
<!-- <Vector3DAnimation
Storyboard.TargetName="camera"
Storyboard.TargetProperty="UpDirection"
From="0,0,-1" To="0,0.1,-1" Duration="0:0:0.5" AutoReverse="True" RepeatBehavior="Forever"
></Vector3DAnimation>-->
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Button.Triggers>
</Button>
</StackPanel>
<Viewport3D Grid.Row="1">
<Viewport3D.Camera>
<PerspectiveCamera x:Name="camera" FarPlaneDistance="10" LookDirection="0,0,-1" UpDirection="0,1,0" NearPlaneDistance="0.2" Position="0,0,2" FieldOfView="50" />
</Viewport3D.Camera>
<ModelVisual3D>
<ModelVisual3D.Content>
<Model3DGroup x:Name="Scene">
<AmbientLight Color="#333333" />
<DirectionalLight Color="#FFFFFF" Direction="-0.612372,-0.5,-0.612372" />
<DirectionalLight Color="#FFFFFF" Direction="0.612372,-0.5,-0.612372" />
<Model3DGroup x:Name="ring">
<Model3DGroup.Transform>
<Transform3DGroup>
<RotateTransform3D>
<RotateTransform3D.Rotation>
<AxisAngleRotation3D Angle="0" Axis="1 0 0.14"/>
</RotateTransform3D.Rotation>
</RotateTransform3D>
<RotateTransform3D>
<RotateTransform3D.Rotation>
<AxisAngleRotation3D Angle="0" Axis="0 1 0.14"/>
</RotateTransform3D.Rotation>
</RotateTransform3D>
</Transform3DGroup>
</Model3DGroup.Transform>
<GeometryModel3D x:Name="Torus01OR9GR10">
<GeometryModel3D.Material>
<MaterialGroup>
<DiffuseMaterial>
<DiffuseMaterial.Brush>
<SolidColorBrush Color="DarkBlue" />
</DiffuseMaterial.Brush>
</DiffuseMaterial>
<SpecularMaterial SpecularPower="24">
<SpecularMaterial.Brush>
<SolidColorBrush Color="LightBlue" Opacity="1.000000"/>
</SpecularMaterial.Brush>
</SpecularMaterial>
<!--EmissiveMaterial Brush="DarkRed">
</EmissiveMaterial>-->
</MaterialGroup>
</GeometryModel3D.Material>
<GeometryModel3D.BackMaterial>
<MaterialGroup>
<DiffuseMaterial>
<DiffuseMaterial.Brush>
<SolidColorBrush Color="#7F7F92" Opacity="1.000000"/>
</DiffuseMaterial.Brush>
</DiffuseMaterial>
<SpecularMaterial SpecularPower="64">
<SpecularMaterial.Brush>
<SolidColorBrush Color="#DBDBDB" Opacity="1.000000"/>
</SpecularMaterial.Brush>
</SpecularMaterial>
</MaterialGroup>
</GeometryModel3D.BackMaterial>
<GeometryModel3D.Geometry>
<MeshGeometry3D
TriangleIndices="... "
Normals="..."
Positions="..."
/>
</GeometryModel3D.Geometry>
</GeometryModel3D>
</Model3DGroup>
</Model3DGroup>
</ModelVisual3D.Content>
</ModelVisual3D>
</Viewport3D>
</Grid>
Производительность 3-D
Визуализация трехмерной сцены требует намного больше работы, чем визуализация двухмерной сцены. В случае анимации трехмерной сцены WPF пытается обновлять изменяемые части по 60 раз в секунду. В зависимости от сложности сцены, это легко может исчерпать ресурсы памяти видеокарты, что приведет к снижению частоты кадров, и анимация станет дергаться.
Существует несколько базовых приемов для повышения производительности 3-D. Вот некоторые стратегии обращения с окном обзора, позволяющие сократить накладные расходы визуализации 3-D:
Если не нужно обрезать содержимое, выходящее за границы окна обзора, установите Viewport3D.ClipToBounds в false.
Если не нужно выполнять проверку попадания курсора в трехмерную сцену, установите Viewport3D.IsHitТеstVisible в false.
Если не пугает снижение качества — зубчатые грани в трехмерных фигурах — установите внутри Viewport3D присоединенное свойство RenderOptions.EdgeMode в Aliased.
Если Viewport 3D больше, чем необходимо, уменьшите его. Важно также обеспечить, насколько это возможно, правильное освещение трехмерной сцены. Ниже приведено несколько критичных советов по созданию наиболее эффективных сеток и моделей.
Где только возможно, создавайте одну сложную сетку вместо нескольких мелких.
Если в одной и той же сетке используются разные материалы, определите однажды объект MeshGeometry (как ресурс) и затем многократно используйте его для создания множества объектов GeometryModel3D.
Где только возможно, упаковывайте группы объектов GeometryModel3D в Model3DGroup и помещайте эту группу в единый объект ModelVisual3D. Не создавайте отдельных объектов ModelVisual3D для каждого GeometryModel3D.
Не определяйте черных материалов (с использованием GeometryModel3D.BackMaterial), если только пользователь действительно не будет видеть изнанку объекта. Аналогично, при определении сеток постарайтесь исключить невидимые треугольники (как, например, нижнюю поверхность куба).
Отдавайте предпочтение сплошным кистям, градиентным кистям и кистям ImageBrush перед кистями DrawingBrush и VisualBrush — обе они требуют больших накладных расходов. При использовании DrawingBrush и VisualBrush для рисования статического содержимого можно кэшировать содержимое кистей для повышения производительности. Чтобы сделать это, установите присоединенное свойство кисти RenderOptions.CachingHint в Cache.
Выполняя эти несложные рекомендации, можно обеспечить максимально возможную производительность при отображении трехмерной графики и максимально возможную частоту кадров для трехмерной анимации.