WPF アプリケーションを作る クッキング ガイド

第 5 回 「2 次元から抜け出そう ~ 3D とメディアでよりリッチなアプリへ~」

ついに WPF のウリの 1 つである 3D とマルチメディアについて解説します。
マルチメディアについては映像再生、音楽再生などある程度分かりやすいものですが 3D については 2D には無い概念を覚えなければなりません。今回の記事では 3D についての解説を重点的にマルチメディアについてはサラッと解説したいと思います。

3D を理解する流れ

3D の概念、簡単な 3D オブジェクトの描画、3D を利用したアプリケーションといった流れで解説します。

3D の概念

3D を利用する為にどのような情報が必要なのかを覚えましょう。

  • カメラの位置とカメラの向き
  • ライトの位置と照らす向き
  • 3D オブジェクト座標データ
図 1. 3D オブジェクトとライトのイメージ図
図 1. 3D オブジェクトとライトのイメージ図

 

3D オブジェクトの座標データの解説の前に 3D 空間の座標を把握する必要があります。図 2 が 3D 空間の座標です。この図を参考にどちらが + (プラス) でどちらが - (マイナス) かを把握できると思います。これは覚えておいた方が良い情報です。

図 2. 3D 空間の xyz 座標

図 2. 3D 空間の xyz 座標

 

では、実際に 3D 空間に 3D オブジェクトを生成してみます。ここでは例として平らな長方形の板を x 軸と y 軸に平行に配置してみました。すると長方形の 4 つの角は図 3 のような座標になります。

図 3. 3D 空間に配置した長方形の板と座標

図 3. 3D 空間に配置した長方形の板と座標

 

3D の概念としては以上の情報があれば十分ではないかと思います。

簡単な 3D オブジェクトの描画

実際に XAML 上で 3D 情報はどのように記述するか解説します。
基本的に 3D 情報は下記コードのように Viewport3D ノード配下に記述します。

<Viewport3D>
   <Viewport3D.Camera>
      <!-- カメラの位置とカメラの向き等の情報 -->
   </Viewport3D.Camera>
   <ModelVisual3D x:Name="object3D">
      <!-- 3Dオブジェクト座標データ -->
   </ModelVisual3D>
   <ModelVisual3D x:Name="Light">
      <!-- ライトの位置と照らす向き -->
   </ModelVisual3D>
</Viewport3D>

見て分かるとおり、カメラだけは Viewport3D.Camera というノードを使って記述しなければなりません。これは、ひとつの Viewport3D にはカメラがひとつしか使えない為です。逆に言うと ModelVisual3D は何個でも Viewport3D ノード内に記述でき、複数の 3D オブジェクト、複数のライトを使うことができます。

カメラと 3D オブジェクトとライトの 3 つの要素を入れた XAML のサンプルコードが下記のコードです。

<Viewport3D>
   <Viewport3D.Camera>
      <PerspectiveCamera x:Name="Camera" 
               FieldOfView="45" 
               NearPlaneDistance="0.1" 
               FarPlaneDistance="50" 
               Position="0,0,10" 
               LookDirection="0,0,-10" 
               UpDirection="0,10,0"/>
   </Viewport3D.Camera>
   <ModelVisual3D x:Name="object3D">
      <ModelVisual3D.Content>
         <GeometryModel3D>
            <GeometryModel3D.Geometry>
               <MeshGeometry3D>
                  <MeshGeometry3D.Positions>
                     <Point3DCollection>
                     -2,1,0 -2,-1,0 2,-1,0
                     2,-1,0 2,1,0 -2,1,0
                     </Point3DCollection>
                  </MeshGeometry3D.Positions>
                  <MeshGeometry3D.TextureCoordinates>
                     <PointCollection>
                     0,0 0,1 1,1
                     1,1 1,0 0,0
                     </PointCollection>
                  </MeshGeometry3D.TextureCoordinates>
               </MeshGeometry3D>
            </GeometryModel3D.Geometry>
            <GeometryModel3D.Material>
               <MaterialGroup>
                  <DiffuseMaterial>
                     <DiffuseMaterial.Brush>
                        <SolidColorBrush Color="#CCCCCC"/>
                     </DiffuseMaterial.Brush>
                  </DiffuseMaterial>
               </MaterialGroup>
            </GeometryModel3D.Material>
         </GeometryModel3D>
      </ModelVisual3D.Content>
   </ModelVisual3D>
   <ModelVisual3D x:Name="Light">
      <ModelVisual3D.Content>
         <DirectionalLight Color="#FFFFFF" Direction="-5,5,-5" />
      </ModelVisual3D.Content>
   </ModelVisual3D>
</Viewport3D>

ちょっと長くて読むのがイヤになりそうですが、ひとつひとつ解説していきます。

・カメラ

カメラの情報は PerspectiveCamera というノードの要素にすべて含みます。それぞれの要素の説明は下記コードを参照してください。

<Viewport3D.Camera>
   <PerspectiveCamera x:Name="Camera" 
      FieldOfView="45" ←視野
      NearPlaneDistance="0.1" ←描画開始位置
      FarPlaneDistance="50" ←描画終了位置
      Position="0,0,10" ←カメラの3D座標
      LookDirection="0,0,-10" ←カメラの向き
      UpDirection="0,10,0" ←カメラの上方向のベクトル
   />
</Viewport3D.Camera>

・3D オブジェクト

3D オブジェクトは三角ポリゴンと呼ばれるものの組み合わせで作られます。図 4 のように長方形を描画する為には三角形を組み合わせるように座標を記述します。解説には邪魔なノードが多いので座標の記述部分のみ取り出しています。

図 4. 長方形を描画する為の三角ポリゴンのイメージ

図 4. 長方形を描画する為の三角ポリゴンのイメージ

 

<MeshGeometry3D.Positions>
   <Point3DCollection>
   -2,1,0 -2,-1,0 2,-1,0 ←黄色の三角形の部分
   2,-1,0 2,1,0 -2,1,0 ←水色の三角形の部分
   </Point3DCollection>
</MeshGeometry3D.Positions>

これで、長方形は完成なのですが、座標を指定しただけではカメラに映りません。カメラに映るように長方形に色を付けます。

<DiffuseMaterial>
   <DiffuseMaterial.Brush>
      <SolidColorBrush Color="#CCCCCC"/>
   </DiffuseMaterial.Brush>
</DiffuseMaterial>

そして、色を塗る範囲も指定しなければなりません。

<MeshGeometry3D.TextureCoordinates>
   <PointCollection>
   0,0 0,1 1,1 ←黄色の三角形の表面に塗る基準となる座標
   1,1 1,0 0,0 ←水色の三角形の表面に塗る基準となる座標
   </PointCollection>
</MeshGeometry3D.TextureCoordinates>

これで長方形の表面に灰色の色がつきます。

・ライト

ライトには二種類あり、自然光と呼ばれる全体的に照らす光と、平行光源と呼ばれる一方からしか照らさない光です。今回のサンプルコードでは平行光源を使用しています。

<ModelVisual3D x:Name="Light">
   <ModelVisual3D.Content>
      <DirectionalLight Color="#FFFFFF" Direction="-5,5,-5" />
   </ModelVisual3D.Content>
</ModelVisual3D>

Direction という要素が平行光源の方向を指定する要素でここでは斜めから長方形を照らすように指定しています。

以上で、簡単な 3D オブジェクトの描画の解説は終わりです。ただ、ここで解説したものは Viewport3D で記述できる情報の氷山の一角です。もっと詳しく情報についてはこの MSDN 上でノード名で検索して調べる方が大量の情報を得られるかと思います。

マルチメディアの扱い方

単純な再生だけであれば下記のコードだけで映像の再生は問題なくできます。MediaElement を配置し、Storyboard で MediaTimeline とその要素の Source に映像ファイルのパスを記述するだけです (Triggers も必要です) 。

<Grid.Resources>
   <Storyboard x:Key="PlayMedia">
      <MediaTimeline
         Source="C:¥data¥movie¥filename.avi" 
         BeginTime="00:00:00" 
         Storyboard.TargetName="MediaElement"/>
   </Storyboard>
</Grid.Resources>
<Grid.Triggers>
   <EventTrigger RoutedEvent="FrameworkElement.Loaded">
      <BeginStoryboard Storyboard="{DynamicResource PlayMedia}"/>
   </EventTrigger>
</Grid.Triggers>
<MediaElement x:Name="MediaElement"/>

このコードを流用して、お持ちの映像ソースを再生してみてください。

3D を利用したアプリケーション

最後に 3D を使った映像再生アプリケーションをお見せします。
再生というボタンをクリックすると映像が再生されると同時に 3D のボタンが回転し、停止ボタンになるというアプリケーションです。

図 5. 3D を使った映像再生アプリケーション

図 5. 3D を使った映像再生アプリケーション

 

解説するにはコードが長すぎるため、解説は省きますが今後の参考にはなると思うので動作とコードは見ておいてください。

<Window
   xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
   xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
   xml:lang="ja-JP"
   x:Class="MediaPlayer.Window1"
   x:Name="Window"
   Title="Window1"
   Width="640" Height="480" xmlns:d="https://schemas.microsoft.com/expression/blend/2006" xmlns:mc="https://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d">

<Window.Resources> <Storyboard x:Key="Start"> <MediaTimeline RepeatBehavior="Forever" Source="C:\windows\clock.avi" BeginTime="00:00:00" Storyboard.TargetName="MediaElement"/> </Storyboard> <Storyboard RepeatBehavior="Forever" x:Key="Rotate"> <Point3DAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="perspectiveCamera" Storyboard.TargetProperty="(ProjectionCamera.Position)"> <SplinePoint3DKeyFrame KeyTime="00:00:00" Value="0,0,10"/> <SplinePoint3DKeyFrame KeyTime="00:00:02" Value="-4.924,-6.428,5.868"/> <SplinePoint3DKeyFrame KeyTime="00:00:04" Value="-5.404,4.556,7.074"/> <SplinePoint3DKeyFrame KeyTime="00:00:06" Value="8.728,2.922,3.911"/> <SplinePoint3DKeyFrame KeyTime="00:00:08" Value="-7.708,-3.937,5.009"/> <SplinePoint3DKeyFrame KeyTime="00:00:10" Value="0,0,10"/> </Point3DAnimationUsingKeyFrames> <Vector3DAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="perspectiveCamera" Storyboard.TargetProperty="(ProjectionCamera.UpDirection)"> <SplineVector3DKeyFrame KeyTime="00:00:00" Value="0,1,0"/> <SplineVector3DKeyFrame KeyTime="00:00:02" Value="-0.413,0.766,0.492"/> <SplineVector3DKeyFrame KeyTime="00:00:04" Value="-0.801,-0.024,-0.597"/> <SplineVector3DKeyFrame KeyTime="00:00:06" Value="-0.329,-0.243,0.913"/> <SplineVector3DKeyFrame KeyTime="00:00:08" Value="-0.618,0.651,-0.441"/> <SplineVector3DKeyFrame KeyTime="00:00:10" Value="0,1,0"/> </Vector3DAnimationUsingKeyFrames> <Vector3DAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="perspectiveCamera" Storyboard.TargetProperty="(ProjectionCamera.LookDirection)"> <SplineVector3DKeyFrame KeyTime="00:00:00" Value="0,0,-10"/> <SplineVector3DKeyFrame KeyTime="00:00:02" Value="4.924,6.428,-5.868"/> <SplineVector3DKeyFrame KeyTime="00:00:04" Value="5.404,-4.556,-7.074"/> <SplineVector3DKeyFrame KeyTime="00:00:06" Value="-8.728,-2.922,-3.911"/> <SplineVector3DKeyFrame KeyTime="00:00:08" Value="7.708,3.937,-5.009"/> <SplineVector3DKeyFrame KeyTime="00:00:10" Value="0,0,-10"/> </Vector3DAnimationUsingKeyFrames> </Storyboard> </Window.Resources> <Window.Triggers> <EventTrigger RoutedEvent="FrameworkElement.Loaded"> <BeginStoryboard Storyboard="{StaticResource Start}"/> <BeginStoryboard Storyboard="{StaticResource Rotate}"/> </EventTrigger> </Window.Triggers>

<Grid x:Name="LayoutRoot"> <Viewport3D x:Name="viewport3D"> <Viewport3D.Camera> <PerspectiveCamera FieldOfView="45" NearPlaneDistance="0.1" FarPlaneDistance="100" Position="0,0,10" LookDirection="0,0,-10" UpDirection="0,1,0" x:Name="perspectiveCamera"/> </Viewport3D.Camera> <ModelVisual3D > <ModelVisual3D.Content> <AmbientLight Color="#FFFFFFFF"/> </ModelVisual3D.Content> <ModelVisual3D.Transform> <Transform3DGroup> <TranslateTransform3D OffsetX="0" OffsetY="0" OffsetZ="0"/> <ScaleTransform3D ScaleX="1" ScaleY="1" ScaleZ="1"/> <RotateTransform3D d:EulerAngles="0,0,0"/> <TranslateTransform3D OffsetX="0" OffsetY="0" OffsetZ="0"/> <TranslateTransform3D OffsetX="0" OffsetY="0" OffsetZ="0"/> </Transform3DGroup> </ModelVisual3D.Transform> </ModelVisual3D> <ModelVisual3D x:Name="object3D"> <ModelVisual3D.Content> <GeometryModel3D> <GeometryModel3D.Geometry> <MeshGeometry3D> <MeshGeometry3D.Positions> <Point3DCollection>-1,1,1 -1,-1,1 1,-1,1 1,-1,1 1,1,1 -1,1,1</Point3DCollection> </MeshGeometry3D.Positions> <MeshGeometry3D.Normals> <Vector3DCollection>0,0,1 0,0,1 0,0,1 0,0,1 0,0,1 0,0,1</Vector3DCollection> </MeshGeometry3D.Normals> <MeshGeometry3D.TextureCoordinates> <PointCollection>0,0 0,1 1,1 1,1 1,0 0,0</PointCollection> </MeshGeometry3D.TextureCoordinates> </MeshGeometry3D> </GeometryModel3D.Geometry> <GeometryModel3D.Material> <MaterialGroup> <DiffuseMaterial> <DiffuseMaterial.Brush> <VisualBrush> <VisualBrush.Visual> <MediaElement x:Name="MediaElement"/> </VisualBrush.Visual> </VisualBrush> </DiffuseMaterial.Brush> </DiffuseMaterial> </MaterialGroup> </GeometryModel3D.Material> </GeometryModel3D> </ModelVisual3D.Content> </ModelVisual3D> <ModelVisual3D x:Name="object3D_Copy"> <ModelVisual3D.Content> <GeometryModel3D> <GeometryModel3D.Geometry> <MeshGeometry3D> <MeshGeometry3D.Positions> <Point3DCollection>-1,1,1 -1,-1,1 1,-1,1 1,-1,1 1,1,1 -1,1,1</Point3DCollection> </MeshGeometry3D.Positions> <MeshGeometry3D.Normals> <Vector3DCollection>0,0,1 0,0,1 0,0,1 0,0,1 0,0,1 0,0,1</Vector3DCollection> </MeshGeometry3D.Normals> <MeshGeometry3D.TextureCoordinates> <PointCollection>0,0 0,1 1,1 1,1 1,0 0,0</PointCollection> </MeshGeometry3D.TextureCoordinates> </MeshGeometry3D> </GeometryModel3D.Geometry> <GeometryModel3D.Material> <MaterialGroup> <DiffuseMaterial> <DiffuseMaterial.Brush> <VisualBrush> <VisualBrush.Visual> <TextBox Text="Media"/> </VisualBrush.Visual> </VisualBrush> </DiffuseMaterial.Brush> </DiffuseMaterial> </MaterialGroup> </GeometryModel3D.Material> </GeometryModel3D> </ModelVisual3D.Content> <ModelVisual3D.Transform> <Transform3DGroup> <TranslateTransform3D OffsetX="0" OffsetY="0" OffsetZ="0"/> <ScaleTransform3D ScaleX="1" ScaleY="1" ScaleZ="1"/> <RotateTransform3D d:EulerAngles="0,-90,0"> <RotateTransform3D.Rotation> <AxisAngleRotation3D Angle="90" Axis="0,-1,0"/> </RotateTransform3D.Rotation> </RotateTransform3D> <TranslateTransform3D OffsetX="0" OffsetY="0" OffsetZ="0"/> <TranslateTransform3D OffsetX="0" OffsetY="0" OffsetZ="0"/> </Transform3DGroup> </ModelVisual3D.Transform> </ModelVisual3D> <ModelVisual3D x:Name="object3D_Copy2"> <ModelVisual3D.Content> <GeometryModel3D> <GeometryModel3D.Geometry> <MeshGeometry3D> <MeshGeometry3D.Positions> <Point3DCollection>-1,1,1 -1,-1,1 1,-1,1 1,-1,1 1,1,1 -1,1,1</Point3DCollection> </MeshGeometry3D.Positions> <MeshGeometry3D.Normals> <Vector3DCollection>0,0,1 0,0,1 0,0,1 0,0,1 0,0,1 0,0,1</Vector3DCollection> </MeshGeometry3D.Normals> <MeshGeometry3D.TextureCoordinates> <PointCollection>0,0 0,1 1,1 1,1 1,0 0,0</PointCollection> </MeshGeometry3D.TextureCoordinates> </MeshGeometry3D> </GeometryModel3D.Geometry> <GeometryModel3D.Material> <MaterialGroup> <DiffuseMaterial> <DiffuseMaterial.Brush> <VisualBrush> <VisualBrush.Visual> <TextBox Text="Movie"/> </VisualBrush.Visual> </VisualBrush> </DiffuseMaterial.Brush> </DiffuseMaterial> </MaterialGroup> </GeometryModel3D.Material> </GeometryModel3D> </ModelVisual3D.Content> <ModelVisual3D.Transform> <Transform3DGroup> <TranslateTransform3D OffsetX="0" OffsetY="0" OffsetZ="0"/> <ScaleTransform3D ScaleX="1" ScaleY="1" ScaleZ="1"/> <RotateTransform3D d:EulerAngles="0,90,0"> <RotateTransform3D.Rotation> <AxisAngleRotation3D Angle="90" Axis="0,1,0"/> </RotateTransform3D.Rotation> </RotateTransform3D> <TranslateTransform3D OffsetX="0" OffsetY="0" OffsetZ="0"/> <TranslateTransform3D OffsetX="0" OffsetY="0" OffsetZ="0"/> </Transform3DGroup> </ModelVisual3D.Transform> </ModelVisual3D> <ModelVisual3D x:Name="object3D_Copy3"> <ModelVisual3D.Content> <GeometryModel3D> <GeometryModel3D.Geometry> <MeshGeometry3D> <MeshGeometry3D.Positions> <Point3DCollection>-1,1,1 -1,-1,1 1,-1,1 1,-1,1 1,1,1 -1,1,1</Point3DCollection> </MeshGeometry3D.Positions> <MeshGeometry3D.Normals> <Vector3DCollection>0,0,1 0,0,1 0,0,1 0,0,1 0,0,1 0,0,1</Vector3DCollection> </MeshGeometry3D.Normals> <MeshGeometry3D.TextureCoordinates> <PointCollection>0,0 0,1 1,1 1,1 1,0 0,0</PointCollection> </MeshGeometry3D.TextureCoordinates> </MeshGeometry3D> </GeometryModel3D.Geometry> <GeometryModel3D.Material> <MaterialGroup> <DiffuseMaterial> <DiffuseMaterial.Brush> <VisualBrush> <VisualBrush.Visual> <TextBox Text="WPF"/> </VisualBrush.Visual> </VisualBrush> </DiffuseMaterial.Brush> </DiffuseMaterial> </MaterialGroup> </GeometryModel3D.Material> </GeometryModel3D> </ModelVisual3D.Content> <ModelVisual3D.Transform> <Transform3DGroup> <TranslateTransform3D OffsetX="0" OffsetY="0" OffsetZ="0"/> <ScaleTransform3D ScaleX="1" ScaleY="1" ScaleZ="1"/> <RotateTransform3D d:EulerAngles="-90,0,0"> <RotateTransform3D.Rotation> <AxisAngleRotation3D Angle="90" Axis="-1,0,0"/> </RotateTransform3D.Rotation> </RotateTransform3D> <TranslateTransform3D OffsetX="0" OffsetY="0" OffsetZ="0"/> <TranslateTransform3D OffsetX="0" OffsetY="0" OffsetZ="0"/> </Transform3DGroup> </ModelVisual3D.Transform> </ModelVisual3D> <ModelVisual3D x:Name="object3D_Copy4"> <ModelVisual3D.Content> <GeometryModel3D> <GeometryModel3D.Geometry> <MeshGeometry3D> <MeshGeometry3D.Positions> <Point3DCollection>-1,1,1 -1,-1,1 1,-1,1 1,-1,1 1,1,1 -1,1,1</Point3DCollection> </MeshGeometry3D.Positions> <MeshGeometry3D.Normals> <Vector3DCollection>0,0,1 0,0,1 0,0,1 0,0,1 0,0,1 0,0,1</Vector3DCollection> </MeshGeometry3D.Normals> <MeshGeometry3D.TextureCoordinates> <PointCollection>0,0 0,1 1,1 1,1 1,0 0,0</PointCollection> </MeshGeometry3D.TextureCoordinates> </MeshGeometry3D> </GeometryModel3D.Geometry> <GeometryModel3D.Material> <MaterialGroup> <DiffuseMaterial> <DiffuseMaterial.Brush> <VisualBrush> <VisualBrush.Visual> <TextBox Text="Element"/> </VisualBrush.Visual> </VisualBrush> </DiffuseMaterial.Brush> </DiffuseMaterial> </MaterialGroup> </GeometryModel3D.Material> </GeometryModel3D> </ModelVisual3D.Content> <ModelVisual3D.Transform> <Transform3DGroup> <TranslateTransform3D OffsetX="0" OffsetY="0" OffsetZ="0"/> <ScaleTransform3D ScaleX="1" ScaleY="1" ScaleZ="1"/> <RotateTransform3D d:EulerAngles="90,0,0"> <RotateTransform3D.Rotation> <AxisAngleRotation3D Angle="90" Axis="1,0,0"/> </RotateTransform3D.Rotation> </RotateTransform3D> <TranslateTransform3D OffsetX="0" OffsetY="0" OffsetZ="0"/> <TranslateTransform3D OffsetX="0" OffsetY="0" OffsetZ="0"/> </Transform3DGroup> </ModelVisual3D.Transform> </ModelVisual3D> </Viewport3D> </Grid> </Window>


著者情報

著者情報
坂本 龍介
株式会社アゼスト

株式会社アゼストはリッチインターネットアプリケーションの パイオニアとして、事業価値を高めるコンサルティングやサービスを提供しています。 私はインターネットアプリケーションのシステム構築や新技術を積極的に取り入れ、 使いやすく新しいシステムの開発を担当しております。

 

ページのトップへ ページのトップへ