OpenTKを勉強し始めたのだが・・・球面の描画で挫折した記録!

pygletやらUnityやらでローレンズアトラクタを描いてみたりしたものの何とかOpenGLでも描いておきたいという思いがあった。
そこでネットからソースを盗んできて勉強を始めた。
勉強といっても本当に理解しようというものではなく、兎に角、動くところまでもっていきたいという不純なもの。
C#でコーディングできるものがよいので、OpenTKを使うことにした。
ネットでは「ほげほげ草」の記事を主に参考にさせていただいた、というかほぼコードを盗ませていただいた。
10年近い年月が経っての流用です。ほげさん、ありがとうございました。

さて、球面を描くコードを記述していたときに妙な現象に遭遇したので記録。
コンパイル時は OpenTK.dll と OpenTK.GLControl.dll をリンク、私のPCの環境では以下のような感じ。
/reference:OpenTK.dll
/reference:OpenTK.GLControl.dll
知識に乏しかったので両方のDLLを実行するディレクトリにコピーしました。

赤道付近は半径の大きい円筒を積み重ねる感じで、北極と南極で蓋をする要領で作ろうとした。
蓋は「ほげほげ草」の記事にあった TriangleFan を使ってみた。
しかし表示すると、蓋の色がおかしい。

f:id:willwealth:20200223190754j:plain
球面の初期画面
リサイズとかすると、それっぽい色になるのだが。
f:id:willwealth:20200223190900j:plain
なにかしたあとの画面

ソースコードは以下の通り。
なお、変数名の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を使用するときには他に特別な処理が必要なのかもしれないが、原因を追究する気力もなかったので分からないまま諦めた。