「#openTK」タグアーカイブ

【openTK】STLファイルを表示させたい。

以前取り上げたopenTKのやつ。

これを使ってSTLファイルを表示する、というのをやってみたいと思います。

使用するSTLファイルは、こちらからダウンロードしました。

https://www.3dagogo.com/creativetools/designs/3DBenchy

ただしく処理できれば、こんな風に表示されるはずです。

STLファイルの読み込みはこちらのサイトを参考にしました。

https://codingsquare.net/cs/stlfile/#toc11

STLのデータは法線ベクトルと、三角形を構成する頂点の座標を示す3つのベクトルで構成されています。

法線ベクトルは三角形の面の表側を向いている方向を示すベクトルです。

		public bool ReadBinary(string filePath)
		{
			// filePath が null か、ファイルが存在しない場合はエラーとする
			if (filePath == null || File.Exists(filePath) == false)
				return false;

			try
			{
				// バイナリファイルの読み込み
				using (var reader = new BinaryReader(new FileStream(filePath, FileMode.Open, FileAccess.Read)))
				{
					// ヘッダ読み込み
					Header = reader.ReadBytes(HeaderLength);

					// ファセットの枚数読み込み
					uint size = reader.ReadUInt32();

					// ファイルの残りのバイト数
					long rest = reader.BaseStream.Length - reader.BaseStream.Position;

					// ファセット1枚分のバイト数
					const int FacetLength = 50;

					// ファイルの残りのバイト数が、求められるファセットの枚数分のバイト数より少なければエラー
					if (rest < FacetLength * size)
						return false;

					// 全ファセット読み込み
					Facets = new Facet[size];
					for (int i = 0; i < size; ++i)
					{
						// ファセット1個分のバイト配列読み込み
						byte[] bytes = reader.ReadBytes(FacetLength);

						// ファセットデータ生成と配列への格納
						int index = 0;
						const int offset = sizeof(float);
						Facets[i] = new Facet(
							new Vertex(
								BitConverter.ToSingle(bytes, index),
								BitConverter.ToSingle(bytes, index += offset),
								BitConverter.ToSingle(bytes, index += offset)),
							new Vertex(
								BitConverter.ToSingle(bytes, index += offset),
								BitConverter.ToSingle(bytes, index += offset),
								BitConverter.ToSingle(bytes, index += offset)),
							new Vertex(
								BitConverter.ToSingle(bytes, index += offset),
								BitConverter.ToSingle(bytes, index += offset),
								BitConverter.ToSingle(bytes, index += offset)),
							new Vertex(
								BitConverter.ToSingle(bytes, index += offset),
								BitConverter.ToSingle(bytes, index += offset),
								BitConverter.ToSingle(bytes, index += offset))
						);
					}
				}
			}
			catch (Exception)
			{
				return false;
			}
			return true;
		}

これでバイナリのSTLデータを読み込み、描画させます。

        STLFile stlFile = new STLFile();
        public Game() : base(800, 600, GraphicsMode.Default, "0-3:GameWindow")
        {
            stlFile.ReadBinary("3DBenchy.stl");
        }

        //画面描画で実行される。
        protected override void OnRenderFrame(FrameEventArgs e)
        {
            base.OnRenderFrame(e);

            GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);

            GL.MatrixMode(MatrixMode.Modelview);
            Matrix4 modelview = Matrix4.LookAt(Vector3.Zero, Vector3.UnitZ, Vector3.UnitY);
            GL.LoadMatrix(ref modelview);

            int count = 0;
            foreach(var f in stlFile.Facets)
            {
                count++;
                GL.Begin(BeginMode.Triangles);

                GL.Color4(Color4.White);
                GL.Normal3(f.Normal.X, f.Normal.Y, f.Normal.Z);
                GL.Vertex3(f.Vertex1.X / 30, f.Vertex1.Y / 30, f.Vertex1.Z / 30);
                GL.Vertex3(f.Vertex2.X / 30, f.Vertex2.Y / 30, f.Vertex2.Z / 30);
                GL.Vertex3(f.Vertex3.X / 30, f.Vertex3.Y / 30, f.Vertex3.Z / 30);

                GL.End();
            }

            SwapBuffers();
        }

そしてライトの設定も加えます。

こちらのサイトを参考にしました。

https://ameblo.jp/nishi-u6fa4/entry-10864018960.html

        //ウィンドウのサイズが変更された場合に実行される。
        protected override void OnResize(EventArgs e)
        {
            base.OnResize(e);
            GL.Viewport(ClientRectangle.X, ClientRectangle.Y, ClientRectangle.Width, ClientRectangle.Height);
            GL.MatrixMode(MatrixMode.Projection);
            Matrix4 projection = Matrix4.CreatePerspectiveFieldOfView((float)Math.PI / 4, (float)Width / (float)Height, 1.0f, 64.0f);
            GL.LoadMatrix(ref projection);
            GL.MatrixMode(MatrixMode.Modelview);

            Matrix4 look = Matrix4.LookAt(3.0f * Vector3.One, Vector3.Zero, Vector3.UnitY);
            GL.LoadMatrix(ref look);
            GL.Enable(EnableCap.Lighting);
            float[] position = new float[] { 1.0f, 2.0f, 3.0f, 0.0f };
            GL.Light(LightName.Light0, LightParameter.Position, position);
            GL.Enable(EnableCap.Light0);
        }

実行結果はこちら。

なんだこりゃ。

たぶん、データは間違ってないと思うんですよ。

問題は視点なのかな、と思います。

視点を変えるとか、変更できるようにすればちゃんと表示させることができるかもしれません。

もうちょっと勉強します。

【C#】openTKを使用してみる

前回はAltseedというライブラリを使用してみたのですが、

このライブラリって調べてみると、構造はシンプルでゲーム制作に必要な機能は一通り揃っている(と思う)のですが、

シンプルすぎるというか、基本的なグラフィックス機能が少々貧弱な気がします。

例えば、四角形を表示させる場合、色の指定はできず、基本的に画像をテクスチャとして表示する、というイメージです。

なので、もうちょっと調べてみました。

ゲームライブラリにこだわらず、グラフィックスライブラリという括りで検索してみると、openTKというライブラリを発見しました。

https://opentk.net/index.html

これはopenGLのラッパーのライブラリですね。

openGLの機能はほぼほぼ使用できると考えていいようです。

NuGetに登録されているのでNuGetからopenTKを検索するとダウンロードして参照できるようになります。

サンプルコードを入力してみました。

https://masuqat.net/programming/csharp/OpenTK00-03.php

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using OpenTK;
using OpenTK.Graphics;
using OpenTK.Graphics.OpenGL;
using OpenTK.Input;

namespace OpenTK_sample
{
    class Program
    {
        [STAThread]
        static void Main(string[] args)
        {
            using (Game window = new Game())
            {
                window.Run(30.0);
            }
        }
    }

    class Game : GameWindow
    {
        public Game() : base(800, 600, GraphicsMode.Default, "0-3:GameWindow")
        {
        }

        //ウィンドウの起動時に実行される。
        protected override void OnLoad(EventArgs e)
        {
            base.OnLoad(e);

            GL.ClearColor(Color4.Black);
            GL.Enable(EnableCap.DepthTest);
        }

        //ウィンドウのサイズが変更された場合に実行される。
        protected override void OnResize(EventArgs e)
        {
            base.OnResize(e);

            GL.Viewport(ClientRectangle.X, ClientRectangle.Y, ClientRectangle.Width, ClientRectangle.Height);
            GL.MatrixMode(MatrixMode.Projection);
            Matrix4 projection = Matrix4.CreatePerspectiveFieldOfView((float)Math.PI / 4, (float)Width / (float)Height, 1.0f, 64.0f);
            GL.LoadMatrix(ref projection);
        }

        //画面描画で実行される。
        protected override void OnRenderFrame(FrameEventArgs e)
        {
            base.OnRenderFrame(e);

            GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);

            GL.MatrixMode(MatrixMode.Modelview);
            Matrix4 modelview = Matrix4.LookAt(Vector3.Zero, Vector3.UnitZ, Vector3.UnitY);
            GL.LoadMatrix(ref modelview);

            GL.Begin(BeginMode.Quads);

            GL.Color4(Color4.White);                            //色名で指定
            GL.Vertex3(-1.0f, 1.0f, 4.0f);
            GL.Color4(new float[] { 1.0f, 0.0f, 0.0f, 1.0f });  //配列で指定
            GL.Vertex3(-1.0f, -1.0f, 4.0f);
            GL.Color4(0.0f, 1.0f, 0.0f, 1.0f);                  //4つの引数にfloat型で指定
            GL.Vertex3(1.0f, -1.0f, 4.0f);
            GL.Color4((byte)0, (byte)0, (byte)255, (byte)255);  //byte型で指定
            GL.Vertex3(1.0f, 1.0f, 4.0f);

            GL.End();

            SwapBuffers();
        }
    }
}

これだけではちょっと難しそうだけど、必要なものだけ使用すればうまくいきそう。

ただ、一番の問題は座標系。

openGLの世界では、(0,0)がウインドウの中心にあり、左上が(-1.0-1.0)で右下が(1.0,1.0)になる。

これはこれで扱いが難しい。

うーん、ハードルが高い・・・。

CGを使うにはちょうどいいかもしれないけどね。