OpenTKでテクスチャーを使ってテキストを描画する

#1. はじめに
OpenTKでテキストを描画するには、まずテキストのビットマップを作成し、それをテクスチャーとして貼りつける方法がよさそうです。
OpenTK.GLControlで使用できるWPF版``DrawString()``を実装してみました。

#2. DrawString()

```
        public static void DrawString(string str, double fontSize)
        {
            //var window = System.Windows.Application.Current.MainWindow;

            double[] glDoubleColor = new double[4];
            GL.GetDouble(GetPName.CurrentColor, glDoubleColor);
            var glColor = new System.Windows.Media.Color();
            glColor.R = (byte)(glDoubleColor[0] * 255);
            glColor.G = (byte)(glDoubleColor[1] * 255);
            glColor.B = (byte)(glDoubleColor[2] * 255);
            glColor.A = (byte)(glDoubleColor[3] * 255); //255;
            System.Windows.Media.Brush foreground = new System.Windows.Media.SolidColorBrush(glColor);
            //System.Windows.Media.Brush foreground = window.Foreground;

            var text = new System.Windows.Media.FormattedText(
                str,
                new System.Globalization.CultureInfo("en-us"),
                System.Windows.FlowDirection.LeftToRight,
                new System.Windows.Media.Typeface(
                    window.FontFamily,
                    System.Windows.FontStyles.Normal,
                    System.Windows.FontWeights.Normal,
                    new System.Windows.FontStretch()),
                fontSize, //window.FontSize,
                foreground);
            var drawingVisual = new System.Windows.Media.DrawingVisual();
            using (System.Windows.Media.DrawingContext drawingContext = drawingVisual.RenderOpen())
            {
                drawingContext.DrawText(text, new System.Windows.Point(0, 0));
            }

            System.Windows.Media.Imaging.RenderTargetBitmap bmp = null;
            {
                int width = (int)text.Width;
                int height = (int)text.Height;
                int dpiX = 96;
                int dpiY = 96;
                bmp = new System.Windows.Media.Imaging.RenderTargetBitmap(
                    width, height, dpiX, dpiY, System.Windows.Media.PixelFormats.Pbgra32);
            }
            
            bmp.Render(drawingVisual);

            int bmpWidth = bmp.PixelWidth;
            int bmpHeight = bmp.PixelHeight;
            int stride = bmpWidth * 4;
            byte[] tmpbits = new byte[stride * bmpHeight];
            var rectangle = new System.Windows.Int32Rect(0, 0, bmpWidth, bmpHeight);
            bmp.CopyPixels(rectangle, tmpbits, stride, 0);
            // 上下反転する
            byte[] bits = new byte[stride * bmpHeight];
            for (int h = 0; h < bmpHeight; h++)
            {
                for (int w = 0; w < stride; w++)
                {
                    bits[h * stride + w] = tmpbits[(bmpHeight - 1 - h) * stride + w];
                }
            }

            // check
            //var png = new System.Windows.Media.Imaging.PngBitmapEncoder();
            //png.Frames.Add(System.Windows.Media.Imaging.BitmapFrame.Create(bmp));
            //using (var fs = new FileStream("1.png", FileMode.Create))
            //{
            //    png.Save(fs);
            //}

            bool isTexture = GL.IsEnabled(EnableCap.Texture2D);
            bool isBlend = GL.IsEnabled(EnableCap.Blend);
            GL.Enable(EnableCap.Texture2D);
            GL.Enable(EnableCap.Blend);
            GL.BlendFunc(BlendingFactor.SrcAlpha, BlendingFactor.OneMinusSrcAlpha);

            int texture = GL.GenTexture();
            GL.BindTexture(TextureTarget.Texture2D, texture);
            GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter,
                (int)TextureMinFilter.Linear);
            GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter,
                (int)TextureMagFilter.Linear);
            GL.TexImage2D(TextureTarget.Texture2D, 0,
                PixelInternalFormat.Rgba,
                bmpWidth, bmpHeight, 0,
                PixelFormat.Bgra, PixelType.UnsignedByte, bits);

            GL.PushMatrix();
            OpenTK.Matrix4d m;
            GL.GetDouble(GetPName.ModelviewMatrix, out m);
            GL.Scale(1.0 / m.M11, 1.0 / m.M22, 1.0 / m.M33);
            GL.Scale(FontScale, FontScale, 1.0); // TODO: これを計算で求める必要がある

            GL.Begin(PrimitiveType.Quads);
            GL.TexCoord2(0, 0);
            GL.Vertex2(0, 0);

            GL.TexCoord2(1, 0);
            GL.Vertex2(bmpWidth, 0);

            GL.TexCoord2(1, 1);
            GL.Vertex2(bmpWidth, bmpHeight);

            GL.TexCoord2(0, 1);
            GL.Vertex2(0, bmpHeight);
            GL.End();
            GL.PopMatrix();

            if (!isTexture)
            {
                GL.Disable(EnableCap.Texture2D);
            }
            if (!isBlend)
            {
                GL.Disable(EnableCap.Blend);
            }
        }
```
描画領域は、(-asp, -1)～(+asp, 1) (asp = ウィンドウの幅 / ウィンドウの高さ)に規格化しています。

#3. 使用例

```
        public void DrawStringTest(Camera Camera, OpenTK.GLControl glControl)
        {
            // ウィンドウの幅、高さの取得
            int[] viewport = new int[4];
            GL.GetInteger(GetPName.Viewport, viewport);
            int winW = viewport[2];
            int winH = viewport[3];
            int fontSize = 18;
            fontSize = (int)(fontSize * winW / (double)400);

            GL.ClearColor(Color4.White);
            GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
            GL.Enable(EnableCap.PolygonOffsetFill);
            GL.PolygonOffset(1.1f, 4.0f);
            GL.MatrixMode(MatrixMode.Modelview);
            GL.LoadIdentity();
            Camera.Fit(new BoundingBox3D(0, winW, 0, winH, 0, 0));
            OpenGLUtils.SetModelViewTransform(Camera);

            double asp = (winW + 1.0) / (winH + 1.0);
            GL.MatrixMode(MatrixMode.Projection);
            GL.PushMatrix();
            GL.LoadIdentity();
            GL.Ortho(-asp, asp, -1.0, 1.0, -1.0, 1.0);

            GL.MatrixMode(MatrixMode.Modelview);
            GL.PushMatrix();
            GL.LoadIdentity();

            string text = "DrawStringテスト";
            int n = 20;
            for (int i = 0; i < n; i++)
            {
                double xx = -asp + asp * 2.0 * i / (double)n;
                double yy = 1.0 - 2.0  * (i + 1) / (double)n;
                OpenTK.Vector2d drawpp = new OpenTK.Vector2d(xx, yy);
                GL.Translate(drawpp.X, drawpp.Y, 1.0);
                double ratio = 1.0 - i / (double)n;
                GL.Color3(1.0 * ratio, 0.5 * ratio, 0.5 * ratio);
                OpenGLUtils.DrawString(text, fontSize);
                GL.Translate(-drawpp.X, -drawpp.Y, -1.0);
            }

            GL.MatrixMode(MatrixMode.Projection);
            GL.PopMatrix();
            GL.MatrixMode(MatrixMode.Modelview);
            GL.PopMatrix();

            glControl.SwapBuffers();
        }
```

【描画結果】
![20190317_IvyFEM DrawStringTest.jpg](https://qiita-image-store.s3.amazonaws.com/0/252253/68d65846-123d-492b-4a26-bc6c5b2f8978.jpeg)

#4. まとめ
OpenTKで``DrawString()``してみました。テクスチャーを使えばテキストも描画できることがわかりました。