OpenTKを勉強し始めたのだが・・・球面の描画で挫折した記録!
pygletやらUnityやらでローレンズアトラクタを描いてみたりしたものの何とかOpenGLでも描いておきたいという思いがあった。
そこでネットからソースを盗んできて勉強を始めた。
勉強といっても本当に理解しようというものではなく、兎に角、動くところまでもっていきたいという不純なもの。
C#でコーディングできるものがよいので、OpenTKを使うことにした。
ネットでは「ほげほげ草」の記事を主に参考にさせていただいた、というかほぼコードを盗ませていただいた。
10年近い年月が経っての流用です。ほげさん、ありがとうございました。
さて、球面を描くコードを記述していたときに妙な現象に遭遇したので記録。
コンパイル時は OpenTK.dll と OpenTK.GLControl.dll をリンク、私のPCの環境では以下のような感じ。
/reference:OpenTK.dll
/reference:OpenTK.GLControl.dll
知識に乏しかったので両方のDLLを実行するディレクトリにコピーしました。
赤道付近は半径の大きい円筒を積み重ねる感じで、北極と南極で蓋をする要領で作ろうとした。
蓋は「ほげほげ草」の記事にあった TriangleFan を使ってみた。
しかし表示すると、蓋の色がおかしい。リサイズとかすると、それっぽい色になるのだが。
ソースコードは以下の通り。
なお、変数名のlatitudeとlongitudeは逆にすべきだったと反省しつつ。
using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using System.Windows.Forms; using System.ComponentModel; using System.Data; using System.Drawing; using System.Text; using System.Drawing.Imaging; using OpenTK; using OpenTK.Graphics; using OpenTK.Graphics.OpenGL; namespace FormsOpenTKTest1 { public class Form1 : Form { /// <summary> /// 必要なデザイナー変数です。 /// </summary> private System.ComponentModel.IContainer components = null; /// <summary> /// 使用中のリソースをすべてクリーンアップします。 /// </summary> /// <param name="disposing">マネージ リソースを破棄する場合は true を指定し、その他の場合は false を指定します。</param> protected override void Dispose(bool disposing) { if (disposing && (components != null)) { components.Dispose(); } base.Dispose(disposing); } #region Windows フォーム デザイナーで生成されたコード /// <summary> /// デザイナー サポートに必要なメソッドです。このメソッドの内容を /// コード エディターで変更しないでください。 /// </summary> private void InitializeComponent() { this.glControl = new OpenTK.GLControl(); this.SuspendLayout(); // // glControl // //this.glControl.BackColor = System.Drawing.Color.Black; this.glControl.BackColor = System.Drawing.Color.Yellow; //this.glControl.Location = new System.Drawing.Point(0, 0); this.glControl.Location = new System.Drawing.Point(10, 10); this.glControl.Margin = new System.Windows.Forms.Padding(7, 6, 7, 6); this.glControl.Name = "glControl"; //this.glControl.Size = new System.Drawing.Size(1696, 603); this.glControl.Size = new System.Drawing.Size(1200, 1000); this.glControl.TabIndex = 0; this.glControl.VSync = false; // // Form1 // this.AutoScaleDimensions = new System.Drawing.SizeF(13F, 24F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; //this.ClientSize = new System.Drawing.Size(1741, 647); this.ClientSize = new System.Drawing.Size(1300, 1300); this.Controls.Add(this.glControl); this.Name = "Form1"; this.Text = "Form1"; this.ResumeLayout(false); } #endregion private OpenTK.GLControl glControl; public Form1() { InitializeComponent(); //イベントの追加 glControl.Load += glControl_Load; glControl.Paint += glControl_Paint; //glControl.Resize += glControl_Resize; MouseDown += new MouseEventHandler(MyMouseDown); } private void glControl_Load(object sender, EventArgs e) { // 背景色の設定 GL.ClearColor(glControl.BackColor); // ビューポートの設定 GL.Viewport(0, 0, glControl.Width, glControl.Height); // 視体積の設定 GL.MatrixMode(MatrixMode.Projection); Matrix4 proj = Matrix4.CreatePerspectiveFieldOfView(MathHelper.PiOver4, glControl.AspectRatio, 0.2f, 5); GL.LoadMatrix(ref proj); // 視界の設定 GL.MatrixMode(MatrixMode.Modelview); Matrix4 look = Matrix4.LookAt(new Vector3(0.5f, 0.5f, 1.75f), new Vector3(-0.1f, -0.1f, 0.05f), Vector3.UnitZ); GL.LoadMatrix(ref look); // デプスバッファの使用 GL.Enable(EnableCap.DepthTest); // 光源の使用 GL.Enable(EnableCap.Lighting); //float[] position = new float[] { 1.0f, 2.0f, 3.0f, 0.0f }; float[] position = new float[] { 1.0f, 7.0f, 9.0f, 0.0f }; GL.Light(LightName.Light0, LightParameter.Position, position); GL.Enable(EnableCap.Light0); } private void glControl_Resize(object sender, EventArgs e) { // ビューポートの設定 GL.Viewport(0, 0, glControl.Width, glControl.Height); } private void glControl_Paint(object sender, System.Windows.Forms.PaintEventArgs e) { GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit); GL.Material(MaterialFace.FrontAndBack, MaterialParameter.Diffuse, Color4.Red); sphere( 0.0, 0.0, 0.0, 0.3); glControl.SwapBuffers(); } void sphere(double x, double y, double z, double radius) { int ndiv = 24; int i,j; double delta,theta, longitude1,longitude2,latitude; double rx1, ry1, rz1, ra1, rx2, ry2, rz2, ra2; delta = (float)Math.PI / (float)ndiv; // TriangleFan (top) GL.Begin(PrimitiveType.TriangleFan); { rz1 = (double)radius*(1.0 - Math.Cos((double)delta)); GL.Vertex3( 0.0f, 0.0f, rz1); for (i = 0; i <= ndiv; i++) { theta = 2.0 * delta * (double)i; rx1 = radius * (float)Math.Cos((double)theta); ry1 = radius * (float)Math.Sin((double)theta); GL.Vertex3( rx1, ry1, rz1); } } GL.End(); for ( i=1; i<ndiv-1; i++ ){ longitude1 = -(Math.PI/2.0) + delta * (double)i; longitude2 = -(Math.PI/2.0) + delta * (double)(i+1); ra1 = radius * Math.Cos(longitude1); rz1 = radius * Math.Sin(longitude1); ra2 = radius * Math.Cos(longitude2); rz2 = radius * Math.Sin(longitude2); GL.Begin(PrimitiveType.TriangleStrip); for ( j=0; j<=ndiv; j++ ){ latitude = 2.0f * delta * (double)j; rx1 = ra1 * Math.Cos(latitude); ry1 = ra1 * Math.Sin(latitude); rx2 = ra2 * Math.Cos(latitude); ry2 = ra2 * Math.Sin(latitude); GL.Normal3( rx1, ry1, rz1); GL.Vertex3( rx1, ry1, rz1); GL.Vertex3( rx2, ry2, rz2); } GL.End(); } // TriangleFan (bottom) GL.Begin(PrimitiveType.TriangleFan); { rz1 = - (double)radius*(1.0 - Math.Cos((double)delta)); GL.Vertex3( 0.0f, 0.0f, rz1); for (i = 0; i <= ndiv; i++) { theta = 2.0 * delta * (double)i; rx1 = radius * (float)Math.Cos((double)theta); ry1 = radius * (float)Math.Sin((double)theta); GL.Vertex3( rx1, ry1, rz1); } } GL.End(); } public void MyMouseDown(Object sender, MouseEventArgs e) { Console.WriteLine("Mouse Down"); glControl.Refresh(); Image img = TakeScreenshot(); img.Save("0000.jpg",ImageFormat.Jpeg); } public Bitmap TakeScreenshot() { if (GraphicsContext.CurrentContext == null) throw new GraphicsContextMissingException(); int w = glControl.ClientSize.Width; int h = glControl.ClientSize.Height; Bitmap bmp = new Bitmap(w, h); System.Drawing.Imaging.BitmapData data = bmp.LockBits(glControl.ClientRectangle, System.Drawing.Imaging.ImageLockMode.WriteOnly, System.Drawing.Imaging.PixelFormat.Format24bppRgb); GL.ReadPixels(0, 0, w, h, OpenTK.Graphics.OpenGL.PixelFormat.Bgr, PixelType.UnsignedByte, data.Scan0); bmp.UnlockBits(data); bmp.RotateFlip(RotateFlipType.RotateNoneFlipY); return bmp; } } static class Program { /// <summary> /// アプリケーションのメイン エントリ ポイントです。 /// </summary> [STAThread] static void Main() { Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); Application.Run(new Form1()); } } }
TriangleFanを使用するときには他に特別な処理が必要なのかもしれないが、原因を追究する気力もなかったので分からないまま諦めた。