オタクじゃないよGeekだよ

趣味的なIT/IoT。そんな内容

VB.NET Formアプリで簡単にOpenGLを使う(View3D)その1

f:id:mae8bit:20200519140643p:plain
複雑になりすぎない程度に機能を持たせました



はじめに

VB.NETのFormアプリで簡単に3Dをいじりたかったので、View3Dクラスを作成しました。
View3DクラスはOpenGLの.NET用ラッパーであるOpenTKを使い、OpenTK.GLControlを継承しています。


View3Dのやっていること

  • 3Dビューを提供します。フォームやパネル、タブなどに貼り付けて使います。
  • 基本的な3D環境を管理します。カメラ、光源、簡単な3Dモデル、テクスチャ。
  • これらのファイル保存、読み込みを行えます。(専用フォーマットです)
  • マウスによるカメラ位置の変更をサポートします。(無効にできます)

これを作成した経緯としてフォームアプリでツール的な使い方を目指しています。
一般の3Dモデルを読み込むことはできません。(自分に必要ないので...出力はそのうち実装するかも?)

環境

Visual Studio Community 2019
VB.NET
フォームアプリケーション

ソース

github.com

NugetでOpenTKとOpenTK.GLControlが必要です。上記をダウンロードしたら、各プロジェクトにNugetから追加してください。
f:id:mae8bit:20200519124105p:plain



簡単に解説

上記のサンプルプロジェクトにほとんどの機能は詰め込んだので、EasyOpenGLプロジェクトのForm1.vbを見れば使い方はわかると思います。
必要なソースはView3D.vbだけです。



Tutorial1_Emptyプロジェクト

1. Form1上にPanel1を貼り付けます。
f:id:mae8bit:20200522113116p:plain
2. Form1のコードは以下のようになります。

Imports OpenTK
Imports OpenTK.Graphics

Public Class Form1

    '3Dを描画する基本クラス
    Private v3d As View3D

    Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load

       'View3Dクラスを初期化、フォーム上に配置します
        v3d = New View3D()
        AddHandler v3d.LoadModel, AddressOf v3d_LoadModel
        AddHandler v3d.Tick, AddressOf v3d_Tick
        With v3d
            .Dock = System.Windows.Forms.DockStyle.Fill
            .Name = "glControl"
            .TabIndex = 0
            .VSync = False
            .LimitFPS = 30
        End With
        Panel1.Controls.Add(v3d)
    End Sub

    '3Dビューが初期化されるタイミングで呼び出されます
    Private Sub v3d_LoadModel(sender As View3D)
    End Sub

    'フレームレートのタイミングで時間的な更新のために呼び出される
    Private Sub v3d_Tick(sender As View3D, elapsedMilliseconds As Long)
    End Sub
End Class
初期化について

View3DクラスはOpenTK.GLControlを継承しています。
OpenTK.GLControlはフォームデザイナで配置できるように設計されているようですが、私の環境ではエラーが出て使えません。そのため、それを継承したView3Dクラスもフォームデザイナでエラーが出てしまいます。
そのため、Form1上にPanel1を配置し、そこにView3Dコントロールをコードから作成します。

まだ何も表示されません。

f:id:mae8bit:20200522113525p:plain
実行結果

Tutorial2_RotationTriangleプロジェクト

Tutorial1_Emptyと異なる部分を説明します。
ここでは頂点色付き三角形を表示して、回転させてみます。


v3d_LoadModel() に初期化コードを追加します。

    Private Sub v3d_LoadModel(sender As View3D)

        '色付き三角形を描く
        Dim bo As View3D.BufferObject = v3d.Create3DObject(bNormal:=False, bTexture:=False, bColor:=True, "Triangle")
        With bo
            .AddTriangle(       '三角形を追加
                New Vector3(-10, -5, 0), Color4.Red,
                New Vector3(0, 15, 0), Color4.Yellow,
                New Vector3(10, -5, 0), Color4.Green)
            .Generate()         '頂点データから頂点バッファ、インデックスバッファを作成
            .Culling = False    '裏面も表示
            .Lighting = False   '頂点色を使いたいのでライティングをOFFにする
        End With
    End Sub
View3D.BufferObjectクラス

このクラスはView3Dにおいて最も使用されるクラスで、3Dオブジェクトの作成、保持、ファイルへの読み書きなど全般を請け負います。

    v3d.Create3DObject(bNormal:=False, bTexture:=False, bColor:=True, "Triangle")

Create3DObject()は3Dオブジェクトを作成するView3D.BufferObjectクラスを作成します。パラメータには使用する頂点データを指定します。

  • bNormal 法線ベクトルを持ちます。光源を使って3Dオブジェクトに陰影を持たせる場合に使用します。
  • bTexture ポリゴンにテクスチャを貼り付けて表示する場合、テクスチャ座標を持たせるために使用します。
  • bColor 頂点に色情報を持たせる場合に使用します。

最後のパラメータは3Dオブジェクトに名前をつけることができます。
3Dオブジェクトを操作する場合、Create3DObject() の返す View3D.BufferObject 型変数を保持しておくか、今回のように名前をつけて、名前で管理する2つの方法があります。


v3d_Tick() に三角形を回転させるコードを追加します。

Private Sub v3d_Tick(sender As View3D, elapsedMilliseconds As Long)

        '名前から登録した三角形を探す
        Dim bo As View3D.BufferObject = v3d.GetBufferObject("Triangle")
        If bo IsNot Nothing Then
            bo.Martices(0) *= Matrix4.CreateRotationY(      'Y軸で回転する行列を作成
                elapsedMilliseconds / 1000.0F * Math.PI)    '1秒間で180度
        End If
    End Sub
v3d.GetBufferObject("Triangle")

Triangle という名前の3Dオブジェクトを取得しています。名前を使わなくても、v3d_LoadModel() 内で取得した変数 bo を保持しておいても構いません。

View3D.BufferObject.Culling = False (裏面も表示)

ポリゴンには表と裏があります。View3Dでは三角形の各頂点の順番が時計回りに見える面を表、逆時計回りに見える面を裏としています。
Cullingとはポリゴンの裏面を表示させない機能で、デフォルトでON(True)になっています。Culling = False とすることでポリゴンの裏面も表示するようにしています。

bo.Martices(0)

これは4x4の姿勢制御用の行列を保持しています。この行列で3Dオブジェクトの向き、位置、スケールが決まります。このあたりは3Dにいける行列の知識が少し必要になります。
ここではY軸を回転する行列を生成、乗算することで三角形を回転させています。

この変数が配列になっている理由ですが、ここに新たに行列を追加することで、1つの3Dオブジェクトを複数の場所に表示できるようになります。デフォルトでインデックス0の1つの行列が登録されています。


実行すると、三角形が回転します。また、左ドラッグでカメラの向き、右ドラッグでカメラの位置、ホイールでカメラの前後位置を変更できます。

f:id:mae8bit:20200522121450p:plain
三角形が回転。マウスでカメラを操作できます


Tutorial3_Coordinatesプロジェクト

Tutorial2_RotationTriangleへの追加部分を説明します。
前チュートリアルに座標軸とXY平面を追加します。座標軸はライン、XZ平面には半透明ポリゴンを使ってみます。

v3d_LoadModel() に新しいコードを追加します。

    Private Sub v3d_LoadModel(sender As View3D)
                :
        '座標軸を描く
        bo = v3d.Create3DObject(bNormal:=False, bTexture:=False, bColor:=True, "Axis")
        With bo
            .AddLine(   'X軸
                New Vector3(-100, 0, 0), Color4.Red,
                New Vector3(100, 0, 0), Color4.Red)
            .AddLine(   'Y軸
                New Vector3(0, -100, 0), Color4.Blue,
                New Vector3(0, 100, 0), Color4.Blue)
            .AddLine(   'Z軸
                New Vector3(0, 0, -100), Color4.Green,
                New Vector3(0, 0, 100), Color4.Green)
            .Generate()         '頂点データから頂点バッファ、インデックスバッファを作成
            .Lighting = False   '頂点色を使いたいのでライティングをOFFにする
        End With

        'XZ平面を描く
        bo = v3d.Create3DObject(bNormal:=False, bTexture:=False, bColor:=True, "XZPlane")
        With bo
            .AddQuad(   'XZ平面。頂点に白色、50%の透明度を持たせる
                New Vector3(-50, 0, -50), New Color4(1.0F, 1.0F, 1.0F, 0.5F),
                New Vector3(50, 0, -50), New Color4(1.0F, 1.0F, 1.0F, 0.5F),
                New Vector3(50, 0, 50), New Color4(1.0F, 1.0F, 1.0F, 0.5F),
                New Vector3(-50, 0, 50), New Color4(1.0F, 1.0F, 1.0F, 0.5F))
            .Generate()         '頂点データから頂点バッファ、インデックスバッファを作成
            .Lighting = False   '頂点色を使いたいのでライティングをOFFにする
            .Culling = False    '裏面も表示
            .Blend = True       '半透明処理を行う
        End With
    End Sub

今回は動かすものは追加していないので、上記の追加だけです。
XZ平面に関しては頂点カラーのアルファ値を0.5にし、BlendフラグをTrueにすることで半透明表示を行っています。

View3D.BufferObjectの制限

上記のコードでは座標軸とXZ平面の3DオブジェクトをCreate3DObject()を使って別々に生成していますが、これには理由があります。
View3Dで扱えるプリミティブの種類は以下の3種類だけです。

  • ポイント 点を表示
  • ライン 2点をつなぐラインを表示
  • トライアングル 3点をつなぐポリゴンを表示(四角形は2つの三角形を組み合わせて表示)

View3D.BufferObject は上記のプリミティブのうち、いずれか1種類の集合体しか扱うことができません。どの種類のプリミティブを扱えるかは、最初に追加したプリミティブによって固定されます。
そのため、上記のコードでは座標軸(ライン)とXZ平面(トライアングル)という異なるプリミティブを扱うために、View3D.BufferObjectを別々に生成しました。

        '新しくBufferObjectを作成
        Dim bo As View3D.BufferObject = v3d.Create3DObject(...)

        '最初にポイントを追加
        bo.AddPoint(...)

        '異なるプリミティブのラインは追加されない。デバッグメッセージでエラーが表示されます
        bo.AddLine(...)


実行すると、XZ平面と座標軸が追加されたのがわかります。

f:id:mae8bit:20200522130419p:plain
半透明のXZ平面と座標軸

View3Dの座標系

View3Dの座標系は以下のようになっています。デフォルトでZ軸の+側からXY平面を見ている状態になります。

f:id:mae8bit:20200519141807p:plain
View3Dの座標系

とりあえず

長くなりすぎるので、今回はここまで。