1
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【OpenGL + C#】C#のWPFアプリでOpenTKを使う

Last updated at Posted at 2024-09-25

0. はじめに

 C#でOpenGLを使用するには,OpenTKをNuGetからインストールすればよい.そしてOpenTK.GLWpfControlもインストールすることで,WPF用の描画コントロールGLWpfControlを使用できるようになる.しかしGLWpfControlを扱った記事がなかったので,備忘録としてWPFアプリに描画コントロールGLWpfControlを実装する例を記す.
 
 今回は下のような,回転する線分をWPFアプリ内に描画し,回転の開始/停止をボタンクリックで制御する.
ソースコード:https://github.com/kkaneko1090/OpenTKWpfDemo

OpenTKデモ.gif

1. プロジェクトの作成とパッケージのインストール

 まず,WPFアプリケーション(.Net) を作成する.
image.png
 今回は .Net 8.0 を選択する.
image.png

 次に必要なパッケージをNuGetでインストールする.まずはC#版OpenGLであるOpenTK (ver 4.8.2) をインストールする.加えて,描画領域のコントロールを使用したいので,OpenTK.GLWpfControl (ver 4.3.2) もインストールする.
image.png

2. GUIの構築

 下図のように,GLWpfControl,ラベル,ボタンの3つでGUIを構築する.
image.png

 XAMLファイルには,GLWpfControlを使用するために,xmlns:glWpfControl="clr-namespace:OpenTK.Wpf;assembly=GLWpfControl" を追記する.その後,glWpfControl:GLWpfControl,Button,Labelを配置する.glWpfControl:GLWpfControlには描画を行うためにReadyRender のイベントを設定した.

MainWindow.xaml
<Window x:Class="OpenTKWpfDemo.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"
        xmlns:local="clr-namespace:OpenTKWpfDemo"
        xmlns:glWpfControl="clr-namespace:OpenTK.Wpf;assembly=GLWpfControl"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="600">
    <Grid>
        <glWpfControl:GLWpfControl x:Name="glControl" Ready="glControl_Ready" Render="glControl_Render"/>
        <Button x:Name="btnStartStop" Content="Start/Stop" Margin="0,0,10,10" HorizontalAlignment="Right" Width="126" Height="31" VerticalAlignment="Bottom" Click="btnStartStop_Click"/>
        <Label x:Name="lblAngle" Content="angle" Foreground="White" HorizontalAlignment="Right" Margin="0,0,10,46" VerticalAlignment="Bottom" Height="31" Width="126"/>
    </Grid>
</Window>

3. 描画用のコードの実装

 MainWindow.xaml.csには,GLWpfControlによる描画を開始するために InitializeComponent();の後ろに,コントロールの設定と描画開始用のコードを記述する.settings についてはOpenGLのバージョン3.1を使用したいので,MajorVersion = 3, MinorVersion = 1 とした.連続的にレンダリングしたいのでRenderContinuously = trueと設定した.

MainWindow.xaml.cs
using OpenTK.Graphics.OpenGL;
using OpenTK.Mathematics;
using OpenTK.Wpf;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace OpenTKWpfDemo
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();

            //GLコントロールの設定
            var settings = new GLWpfControlSettings()
            {
                MajorVersion = 3,
                MinorVersion = 1,
                RenderContinuously = true,
            };

            //描画開始
            glControl.Start(settings);
        }

        //...
        
    }
}

 glControl.Start(settings);の後に一度だけ呼び出されるイベントReady では,描画の初期設定を以下のように行う.ここでは描画に関する種々の設定を行っているが,線分を描画するだけならよりシンプルに実装できるが,今後3次元形状も描画することを考えて記述している.(x, y, z) = (0, 0, 1)の位置にライトを配置した.また今回はポリゴンの描画は行わないのだが,Readyイベントでポリゴン裏面のカリング設定も行うようにした.

MainWindow.xaml.cs
        /// <summary>
        /// Readyイベント
        /// </summary>
        private void glControl_Ready()
        {
            //背景を黒色でクリア
            GL.ClearColor(Color4.Black);
            //デプステストを有効化
            GL.Enable(EnableCap.DepthTest);
            //カリングの設定
            GL.Enable(EnableCap.CullFace); //カリングの有効化
            GL.CullFace(CullFaceMode.Back); //裏面をカリングする面に指定
            GL.FrontFace(FrontFaceDirection.Ccw); //反時計回りで描かれる面を表面に指定
            //ライトの設定
            GL.Enable(EnableCap.Lighting); //ライティングの有効化
            GL.Enable(EnableCap.Light0); //ライトを1つ(0番)を有効化
            GL.Light(LightName.Light0, LightParameter.Position, Vector4.UnitZ); //UnitZ(0,0,1)にライトを配置
            //法線の正規化
            GL.Enable(EnableCap.Normalize);
            //物体の質感の設定
            GL.Enable(EnableCap.ColorMaterial); //質感の有効化
            GL.ColorMaterial(MaterialFace.Front, ColorMaterialParameter.Diffuse);//表面を拡散反射する質感に指定

        }

 下記のRenderイベントは描画時に呼び出されるが,まず最初にGL.Clearでバッファを消去する.

MainWindow.xaml.cs
        /// <summary>
        /// Renderイベント
        /// </summary>
        /// <param name="obj"></param>
        private void glControl_Render(TimeSpan obj)
        {
            //カラーバッファ初期化
            GL.Clear(ClearBufferMask.ColorBufferBit);
            //デプスバッファ初期化
            GL.Clear(ClearBufferMask.DepthBufferBit);
            
            //描画領域を指定する2点のXY座標を指定
            GL.Viewport(0, 0, (int)glControl.ActualWidth, (int)glControl.ActualHeight); //(0,0)と((int)glControl.ActualWidth, (int)glControl.ActualHeight)が成す長方形領域を指定
            //視点を設定
            Matrix4 modelView = Matrix4.LookAt(Vector3.UnitZ, Vector3.Zero, Vector3.UnitY); //UnitZ(0,0,1)からZero(0,0,0)の向きに見るように指定,画面の上方向はUnitY(0,1,0)と指定
            //マトリックスの読み込みモードをModelView(視点)に変更
            GL.MatrixMode(MatrixMode.Modelview);
            //視点行列を読み込み
            GL.LoadMatrix(ref modelView);
            
            //投影領域を設定
            Matrix4 projection = Matrix4.CreateOrthographic((float)glControl.ActualWidth, (float)glControl.ActualHeight, -500.0f, 500.0f);
            //マトリクスの読み込みモードをProjection(投影領域)に変更
            GL.MatrixMode(MatrixMode.Projection);
            //投影行列を読み込み
            GL.LoadMatrix(ref projection);

            //線分の描画
            DrawLineSegment();
        }

 Viewportの設定では,下図のような視点となるようにコードを記述している.また,Projectionに関するパラメータは,コントロール(glControl)を目いっぱい使うように領域を指定した.
image.png

 最後に関数 DrawLineSegment で回転する線分を描画する.関数の中身は下の通り.

MainWindow.xaml.cs

//グローバル変数
//線分の角度
float angle = 0;
//線分の回転半径
float radius = 200;
//回転中フラグ
bool isRotating = false;

/// <summary>
/// 線分の描画
/// </summary>
private void DrawLineSegment()
{

   #region 描画処理
   //線の幅を設定
   GL.LineWidth(1);
   //Lineモードで描画開始
   GL.Begin(PrimitiveType.Lines);
   //色指定
   GL.Color4(Color4.Red);
   //線分の始点
   GL.Vertex3(0, 0, 0);
   //線分の終点
   GL.Vertex3(radius * MathF.Cos(angle), radius * MathF.Sin(angle), 0);
   //描画終了
   GL.End();

   //実行中フラグのとき
   if (isRotating)
   {
       //角度をインクリメント
       angle += 0.01f;
       //角度を正規化
       angle = angle % (2 * MathF.PI);
       //ラベルに角度を表示
       lblAngle.Content = string.Format("Angle: {0} deg.", (angle / MathF.PI * 180).ToString("F2"));
   }
   #endregion
}

image.png

 線分の始点と終点は上図のようになるようにGL.Beginを使用して描画する.if(isRotating)の中身では,線分の回転角度のインクリメントと,現在の角度をラベルに表示するコードを記述している.そして回転中フラグ isRotating は,以下のようにボタンクリックで変更するようにした.

MainWindow.xaml.cs
 /// <summary>
 /// ボタンによる処理
 /// </summary>
 /// <param name="sender"></param>
 /// <param name="e"></param>
 private void btnStartStop_Click(object sender, RoutedEventArgs e)
 {
     isRotating = !isRotating;
 }

 以上のコードを実行すると,以下のように回転する線分が描画され,ボタンクリックで回転を停止/開始できる.
OpenTKデモ.gif

1
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?