WPF (Windows Presentation Foundation) フレームワークには、標準で豊富なUI要素を扱う機能が備わっていますが、強力な3Dグラフィックス機能も組み込まれています。本記事では、外部で作成されたOBJ形式の3DモデルをWPFアプリケーション内で読み込み、表示するための基本的な手順を解説します。
WPFで3Dモデルをレンダリングするには、Viewport3Dコントロールが中心的な役割を担います。このコントロールは、3Dシーンを描画するためのキャンバスとして機能し、シーンを構成するカメラ、光源、そして表示する3Dオブジェクト(モデル)を配置します。
ここでは、カスタムのWavefrontObjLoaderクラスが存在し、OBJファイルを解析してWPFが理解できるModel3Dオブジェクトに変換できるものと仮定して話を進めます。まずは、基本的なViewport3Dと、カメラ位置を操作するためのスライダーをXAMLで定義します。
<Window x:Class="Wpf3DModelViewer.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Title="WPF 3Dモデルビューア" Height="600" Width="800">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<!-- 3Dシーンを表示するビューポート -->
<Viewport3D x:Name="modelViewport" Grid.Row="0">
<!-- カメラとシーンの内容はC#コードで設定します -->
</Viewport3D>
<!-- カメラ位置調整用のスライダー -->
<StackPanel Grid.Row="1" Orientation="Horizontal" Margin="10">
<TextBlock Text="カメラX:" VerticalAlignment="Center" Margin="0,0,5,0"/>
<Slider x:Name="cameraXSlider" Minimum="-200" Maximum="200" Value="0" Width="120" />
<TextBlock Text="カメラY:" VerticalAlignment="Center" Margin="15,0,5,0"/>
<Slider x:Name="cameraYSlider" Minimum="-200" Maximum="200" Value="0" Width="120" />
<TextBlock Text="カメラZ:" VerticalAlignment="Center" Margin="15,0,5,0"/>
<Slider x:Name="cameraZSlider" Minimum="50" Maximum="500" Value="250" Width="120" />
</StackPanel>
</Grid>
</Window>
次に、コードビハインドで3Dシーンを初期化し、OBJモデルをロードする処理を実装します。
便宜上、OBJファイルを解析するWavefrontObjLoaderクラスを簡易的に定義しますが、実際のアプリケーションではより堅牢な実装や既存のライブラリの利用が推奨されます。
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media.Media3D;
using System.Windows.Media;
// OBJファイルをロードするための簡易的なダミークラス。
// 実際のWPF 3Dアプリケーションでは、外部ライブラリを使用するか、
// OBJファイルフォーマットの仕様に基づいてModel3Dを構築するパーサーを実装します。
public class WavefrontObjLoader
{
/// <summary>
/// 指定されたパスからOBJファイルを読み込み、Model3Dオブジェクトを返します。
/// この実装はプレースホルダーであり、実際には立方体を返します。
/// </summary>
/// <param name="filePath">OBJファイルのパス。</param>
/// <returns>ロードされたModel3Dオブジェクト、またはエラーの場合はnull。</returns>
public Model3D LoadObjFile(string filePath)
{
// DEBUG: 実際のロード処理の代わりに簡単な立方体モデルを生成
MessageBox.Show($"DEBUG: 実際のOBJファイル '{filePath}' の代わりに立方体モデルをロードします。", "情報", MessageBoxButton.OK, MessageBoxImage.Information);
// 立方体の頂点
Point3D[] vertices = new Point3D[]
{
new Point3D(-50, -50, -50), new Point3D(50, -50, -50), new Point3D(50, 50, -50), new Point3D(-50, 50, -50), // Front face
new Point3D(-50, -50, 50), new Point3D(50, -50, 50), new Point3D(50, 50, 50), new Point3D(-50, 50, 50) // Back face
};
// 立方体の面を構成する三角形のインデックス (ここでは前面と背面のみ簡略化)
Int32Collection triangles = new Int32Collection
{
0, 1, 2, 0, 2, 3, // Front
5, 4, 7, 5, 7, 6 // Back (reversed for correct normal)
// ... 他の面も追加する必要がある
};
MeshGeometry3D mesh = new MeshGeometry3D
{
Positions = new Point3DCollection(vertices),
TriangleIndices = triangles,
// NormalVectorsやTextureCoordinatesも実際のモデルでは必要になります
};
// モデルにマテリアル(色や質感)を適用
Material modelMaterial = new DiffuseMaterial(Brushes.DarkGray);
return new GeometryModel3D(mesh, modelMaterial);
}
}
public partial class MainWindow : Window
{
private PerspectiveCamera _sceneCamera; // シーンのカメラ
private WavefrontObjLoader _fileLoader; // OBJファイルローダーインスタンス
public MainWindow()
{
InitializeComponent();
_fileLoader = new WavefrontObjLoader();
// スライダーのイベントハンドラを登録
cameraXSlider.ValueChanged += OnCameraPositionSliderChanged;
cameraYSlider.ValueChanged += OnCameraPositionSliderChanged;
cameraZSlider.ValueChanged += OnCameraPositionSliderChanged;
Initialize3DScene(); // 3Dシーンのセットアップを実行
}
/// <summary>
/// 3Dシーンのカメラ、光源、およびOBJモデルを初期設定します。
/// </summary>
private void Initialize3DScene()
{
// カメラの初期位置と向きを設定
_sceneCamera = new PerspectiveCamera
{
Position = new Point3D(cameraXSlider.Value, cameraYSlider.Value, cameraZSlider.Value), // スライダーの初期値を使用
LookDirection = new Vector3D(0, 0, -1), // Z軸の負の方向を見る
UpDirection = new Vector3D(0, 1, 0), // カメラの上方向をY軸に設定
FieldOfView = 60 // 視野角を60度に設定
};
modelViewport.Camera = _sceneCamera; // Viewport3Dにカメラを割り当てる
// シーン内のすべての3D要素(モデル、光源)を管理するグループ
Model3DGroup sceneElements = new Model3DGroup();
// シーンに指向性ライトを追加
DirectionalLight mainLight = new DirectionalLight
{
Color = Colors.White, // 白い光
Direction = new Vector3D(-0.6, -0.5, -0.6) // 光の方向
};
sceneElements.Children.Add(mainLight);
// OBJモデルをロードしてシーンに追加
try
{
// ここに実際にロードしたいOBJファイルのパスを指定してください
string objModelPath = @"C:\Models\sample_model.obj";
Model3D loadedModel = _fileLoader.LoadObjFile(objModelPath);
if (loadedModel != null)
{
sceneElements.Children.Add(loadedModel);
}
else
{
MessageBox.Show($"'{objModelPath}' のOBJモデルをロードできませんでした。ファイルが存在するか確認してください。", "ロードエラー", MessageBoxButton.OK, MessageBoxImage.Warning);
}
}
catch (Exception ex)
{
MessageBox.Show($"OBJモデルのロード中にエラーが発生しました: {ex.Message}", "エラー", MessageBoxButton.OK, MessageBoxImage.Error);
}
// 最終的なシーンコンテンツをViewport3Dに追加
// ModelVisual3DはModel3DGroupをViewport3Dの子として表示するためのコンテナです。
modelViewport.Children.Add(new ModelVisual3D { Content = sceneElements });
}
/// <summary>
/// カメラ位置調整スライダーの値が変更されたときにカメラの位置を更新します。
/// </summary>
private void OnCameraPositionSliderChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
{
// 各スライダーの現在の値に基づいてカメラの位置を更新
_sceneCamera.Position = new Point3D(cameraXSlider.Value, cameraYSlider.Value, cameraZSlider.Value);
}
}