C#で書けて、Linuxでも動く、簡単な3Dモデルビューア

了解!
ここでは「C#で書けて、Linuxでも動く、簡単な3Dモデルビューア」を紹介します。
C#でクロスプラットフォームなGUI+3Dをやるなら、以下の組み合わせが最有力です👇


目次

✅ 構成:C# + Avalonia UI + OpenTK

技術役割
C#開発言語
Avalonia UILinux/Windows/macOS対応のGUIフレームワーク
OpenTKOpenGLラッパー(3D描画)
AssimpNet(任意)3Dモデル読み込み用ライブラリ(.obj, .glb, etc.)

🚀 開発環境セットアップ

必要ツール:

  • .NET SDK(例:dotnet 8.0
  • 任意のIDE:VS Code / JetBrains Rider / Visual Studio
dotnet new install Avalonia.Templates
dotnet new avalonia.app -n Simple3DViewer
cd Simple3DViewer

🎮 最小構成サンプル(OpenGLで三角形を描画)

MainWindow.axaml(UI定義)

<Window xmlns="https://github.com/avaloniaui"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        x:Class="Simple3DViewer.MainWindow"
        Width="800" Height="600"
        Title="3D Viewer">
    <Grid>
        <local:GLControl />
    </Grid>
</Window>

GLControl.cs(OpenTK描画)

using Avalonia.OpenGL;
using Avalonia.OpenGL.Controls;
using OpenTK.Graphics.OpenGL;

public class GLControl : OpenGlControlBase
{
    protected override void OnOpenGlInit(GlInterface gl, int fb)
    {
        GL.ClearColor(0.1f, 0.1f, 0.2f, 1.0f);
    }

    protected override void OnOpenGlRender(GlInterface gl, int fb)
    {
        GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);

        // 三角形描画
        GL.Begin(PrimitiveType.Triangles);
        GL.Color3(1.0, 0.0, 0.0); GL.Vertex2(-0.6, -0.4);
        GL.Color3(0.0, 1.0, 0.0); GL.Vertex2(0.6, -0.4);
        GL.Color3(0.0, 0.0, 1.0); GL.Vertex2(0.0, 0.6);
        GL.End();
    }
}

✅ ビルドと実行

dotnet run

WindowsでもLinuxでも、同じコードでビルド&実行可能!


🔁 今後追加できる機能例

機能方法
3Dモデル読み込みAssimpNet.obj, .glb 読み込み
マウスで回転・ズームOpenTKでカメラ制御追加
ライト・マテリアルOpenGLで基本ライティングを追加
GUIボタンで操作AvaloniaのUI部品と連携可

📦 モデル読み込みの例(AssimpNet)

dotnet add package AssimpNet
var importer = new Assimp.AssimpContext();
var scene = importer.ImportFile("sample.obj", Assimp.PostProcessSteps.Triangulate);
foreach (var mesh in scene.Meshes)
{
    // mesh.Vertices[i], mesh.Normals[i] などから描画用データ作成
}

✨ 補足

  • Avalonia + OpenTK の組み合わせは、WPFのようなGUIとOpenGLの自由度を両立できる貴重な選択肢
  • 特に、WPFを触っていた人には自然なXAMLベースで移行しやすいです。

もし興味があれば:

  • .glb ファイルの読み込み+マウス操作のコード
  • GUI操作でモデル選択する機能
  • 回転・ズーム対応のサンプル

なども順に追加できます!

C# + Avalonia + OpenTK で、 .glb ファイルの読み込み+マウス操作のコード

了解!では、C# + Avalonia + OpenTK で .glb ファイルを読み込み、マウスで回転・ズームできる簡単な3Dビューアのコード構成を紹介します。


🎯 やりたいことの概要

  • .glb(GLTFバイナリ)ファイルを読み込み
  • 3D描画 → OpenTK(OpenGL)
  • マウス操作 → カメラ回転&ズーム
  • UI → Avalonia(GUI)

🧱 使うライブラリ

dotnet add package OpenTK
dotnet add package AssimpNet
dotnet add package Avalonia

🔧 補足:AssimpNet は .glb ファイルの読み込みをサポートしています(内部的に Assimp を使用)


📁 構成イメージ

Simple3DViewer/
├── Program.cs
├── MainWindow.axaml
├── MainWindow.xaml.cs
├── GLControl.cs        ← OpenGL描画+マウス操作
├── GltfLoader.cs       ← .glbファイル読み込み

🧩 コアパート①:GLControl(描画 + マウス操作)

public class GLControl : OpenGlControlBase
{
    private float angleX = 0f, angleY = 0f;
    private float zoom = -3f;
    private List<Vector3> vertices = new();

    public void LoadModel(string path)
    {
        var context = new AssimpContext();
        var scene = context.ImportFile(path, PostProcessSteps.Triangulate);
        vertices.Clear();

        foreach (var mesh in scene.Meshes)
        {
            foreach (var v in mesh.Vertices)
                vertices.Add(new Vector3(v.X, v.Y, v.Z));
        }

        InvalidateVisual(); // 再描画
    }

    protected override void OnOpenGlInit(GlInterface gl, int fb)
    {
        GL.Enable(EnableCap.DepthTest);
    }

    protected override void OnOpenGlRender(GlInterface gl, int fb)
    {
        GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
        GL.LoadIdentity();
        GL.Translate(0, 0, zoom);
        GL.Rotate(angleX, 1, 0, 0);
        GL.Rotate(angleY, 0, 1, 0);

        GL.Begin(PrimitiveType.Triangles);
        GL.Color3(0.8f, 0.8f, 1f);
        foreach (var v in vertices)
        {
            GL.Vertex3(v);
        }
        GL.End();
    }

    protected override void OnPointerMoved(PointerEventArgs e)
    {
        if (e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
        {
            var delta = e.GetPosition(this);
            angleX += (float)delta.Y * 0.1f;
            angleY += (float)delta.X * 0.1f;
            InvalidateVisual();
        }
    }

    protected override void OnPointerWheelChanged(PointerWheelEventArgs e)
    {
        zoom += (float)e.Delta.Y * 0.1f;
        InvalidateVisual();
    }
}

🧩 コアパート②:MainWindow.axaml(ボタン付き)

<Window xmlns="https://github.com/avaloniaui"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:Simple3DViewer"
        x:Class="Simple3DViewer.MainWindow"
        Width="800" Height="600"
        Title="GLB Viewer">
    <DockPanel>
        <Button DockPanel.Dock="Top" Click="OnOpenFile">Open .glb File</Button>
        <local:GLControl Name="glView" />
    </DockPanel>
</Window>

🧩 コアパート③:MainWindow.xaml.cs(ファイル読み込み)

using Avalonia.Controls;
using Avalonia.Interactivity;
using Avalonia.Platform.Storage;

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
    }

    private async void OnOpenFile(object sender, RoutedEventArgs e)
    {
        var files = await StorageProvider.OpenFilePickerAsync(new FilePickerOpenOptions
        {
            Title = "Select .glb File",
            AllowMultiple = false,
            FileTypeFilter = new[] { new FilePickerFileType("GLB") { Patterns = new[] { "*.glb" } } }
        });

        if (files.Count > 0)
        {
            glView.LoadModel(files[0].Path.LocalPath);
        }
    }
}

✅ ビルドして試す

dotnet run
  1. 起動後、Open .glb File ボタンで .glb ファイルを選択
  2. 3Dモデルが表示され、マウスドラッグで回転、ホイールでズーム可能

🔄 拡張アイデア

機能方法
面ごとの法線・色mesh.Normals, mesh.Colors を活用
アニメーション再生glTFのアニメーション情報をパース
テクスチャ表示mesh.Material.TextureDiffuse を読み取り OpenGLに渡す

このテンプレートでかなり柔軟な3Dビューアが作れます。
もし「テクスチャ付きで表示したい」「GLBだけじゃなくOBJも読みたい」などあれば、それもすぐ追加できます!

次に進めたい部分はどこでしょう?たとえば:

  • アニメーション再生
  • カメラのリセット・回転中心変更

✅ ① カメラのリセット

目的: 「元の視点に戻す」ボタンなどで、カメラの角度・ズームを初期値に戻す。

🔧 追加コード(GLControl)

public void ResetCamera()
{
    angleX = 0f;
    angleY = 0f;
    zoom = -3f;
    InvalidateVisual();
}

🔘 UIでの呼び出し(MainWindow.xaml.cs)

private void OnResetCamera(object sender, RoutedEventArgs e)
{
    glView.ResetCamera();
}

✅ ② 回転中心の変更

問題点: 現在は「ワールド原点 (0,0,0)」を中心に回転している
やりたいこと: 読み込んだモデルの中心を算出して、そこを中心にカメラ回転

🔧 対応方法

モデル中心(重心)を求めて、OpenGL変換時に GL.Translate(-center) で調整

private Vector3 modelCenter = Vector3.Zero;

public void LoadModel(string path)
{
    // ...(Assimp読み込み)
    vertices.Clear();
    Vector3 sum = Vector3.Zero;

    foreach (var mesh in scene.Meshes)
    {
        foreach (var v in mesh.Vertices)
        {
            var vec = new Vector3(v.X, v.Y, v.Z);
            vertices.Add(vec);
            sum += vec;
        }
    }

    modelCenter = sum / vertices.Count;
    InvalidateVisual();
}

protected override void OnOpenGlRender(GlInterface gl, int fb)
{
    GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
    GL.LoadIdentity();

    GL.Translate(0, 0, zoom);
    GL.Rotate(angleX, 1, 0, 0);
    GL.Rotate(angleY, 0, 1, 0);
    GL.Translate(-modelCenter.X, -modelCenter.Y, -modelCenter.Z);

    // ...描画処理
}

これで、モデルの中心を原点に合わせて回転する動作になります。


✅ ③ アニメーション再生(GLBの)

.glb ファイルはアニメーション(ボーン/キーフレーム)を含む場合があります。

📦 AssimpNetでの取得例:

var scene = context.ImportFile(path, PostProcessSteps.Triangulate);
if (scene.HasAnimations)
{
    var anim = scene.Animations[0]; // 最初のアニメーション
    float duration = anim.DurationInTicks;
    float ticksPerSecond = anim.TicksPerSecond > 0 ? anim.TicksPerSecond : 25;

    float totalTime = duration / ticksPerSecond;
    // → 再生用のタイマー開始
}

⏱ アニメーション再生(Timer利用)

  • フレーム毎に currentTime += deltaTime
  • ボーンマトリクスを補間・適用
  • OpenGLで再描画

Assimpでのアニメーションはボーン階層付きスキニングが必要なので、完全な実装は少し重めですが、簡易バージョンなら可能です。


👇 まとめ

やりたいこと方法
カメラリセット角度・ズーム変数を初期値に戻す
回転中心変更モデル中心(重心)を求めて補正
アニメーション再生scene.Animations を参照して、毎フレーム補間

もし「スキニングやボーンアニメーションの具体例」や「Assimpからアニメーション行列を取り出して描画する方法」をやってみたい場合、より詳細なステップ付きで提供できます!

どこから取りかかってみたいですか?(カメラ中心変更の続き?アニメーションの具体実装?)

よかったらシェアしてね!
目次