【问题标题】:Save drawn texture with OpenGL in to a file将使用 OpenGL 绘制的纹理保存到文件中
【发布时间】:2020-10-28 13:03:25
【问题描述】:

我使用 openGL 绘制纹理(视频图像)。图像是在地图上绘制的,因此在大多数情况下绘制的图像是梯形的。仅当我使用“透视正确纹理”时,图像在地图上才看起来不错。 我的问题是如何捕捉绘制的纹理并将其存储到文件中。 我只想保存绘制的纹理而不是屏幕截图或此函数中未在此处绘制的任何其他内容(public override void OnRender())。 我还在地图上渲染了其他东西,所以截图不是解决方案。那么如何绘制到一些 Framebuffer 以在屏幕上使用它并将其保存到文件中。

使用 openTK nuGet v1.1.1589.5942 (v4.0.6)

using GMap.NET.OpenGL;
using OpenTK;
using OpenTK.Graphics.OpenGL;        

public GMapControl() : base(new OpenTK.Graphics.GraphicsMode(32, 24, 8, 4))
{
    Paint += glControl_Paint;
}

void glControl_Paint(object sender, PaintEventArgs e)
{
    if (!loaded)
        return;

    if (makeControlContext)
    {
        //VideoForm
        controlContext = new GraphicsContext(GraphicsMode, WindowInfo);
        makeControlContext = false;
    }
    if (controlContext != null)
        controlContext.MakeCurrent(WindowInfo);
    else
        MakeCurrent();

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

    GL.MatrixMode(MatrixMode.Modelview);

    GL.BlendFunc(BlendingFactorSrc.SrcAlpha, BlendingFactorDest.OneMinusSrcAlpha);
    GL.Enable(EnableCap.Blend);

    GL.Enable(EnableCap.LineSmooth);
    GL.Hint(HintTarget.LineSmoothHint, HintMode.Nicest);

    DrawMap();

    textureLoader();

    if (OnPaint != null)
    {
        GL.DepthMask(false);
        GL.LoadIdentity();
        OnPaint(sender, e);
        if (useViewPortFix)
            setupViewport();
    }
    GL.Disable(EnableCap.Blend);
    GL.Flush();
    SwapBuffers();
}

void DrawMap()
{
    try
    {
      
    }
    finally
    {
        if (themeFont != null)
            OnPaintOverlays();
        GL.PopMatrix();
    }
}

protected virtual void OnPaintOverlays()
{
    GL.LoadIdentity();
    GL.Translate(Core.renderOffset.X, Core.renderOffset.Y, 0);
    try
    {
        foreach (GMapOverlay o in Overlays)
        {
            if (o.IsVisibile)
            {
                o.OnRender();
            }
        }
    }
    catch { }
}

public override void OnRender()
{
    GL.Color4(backgroundColor.Value);

    lock (bitmapSync)
    {
        if (bitmap != null)
            createTexture();
    }

    GL.Enable(EnableCap.Texture2D);
    GL.BindTexture(TextureTarget.Texture2D, texture);

    //Do the magick for "Perspective correct texturing"
    // center point
    GPoint localTargetPosition = MainForm.instance.gMapControl.FromLatLngToLocalWithOffset(targetPosition);
    // determines distances to center for all vertexes
    double dUL = Common.distance(new double[] { LocalPoints[0].X, LocalPoints[0].Y }, new double[] { localTargetPosition.X, localTargetPosition.Y });
    double dUR = Common.distance(new double[] { LocalPoints[1].X, LocalPoints[1].Y }, new double[] { localTargetPosition.X, localTargetPosition.Y });
    double dLR = Common.distance(new double[] { LocalPoints[2].X, LocalPoints[2].Y }, new double[] { localTargetPosition.X, localTargetPosition.Y });
    double dLL = Common.distance(new double[] { LocalPoints[3].X, LocalPoints[3].Y }, new double[] { localTargetPosition.X, localTargetPosition.Y });

    var texCoords = new[]
    {
            new Vector4(0, 0, 1, 1),
            new Vector4(1, 0, 1, 1),
            new Vector4(1, 1, 1, 1),
            new Vector4(0, 1, 1, 1)
        };

    texCoords[0] *= (float)((dUL + dLR) / dLR);
    texCoords[1] *= (float)((dUR + dLL) / dLL);
    texCoords[2] *= (float)((dLR + dUL) / dUL);
    texCoords[3] *= (float)((dLL + dUR) / dUR);

    GL.Begin(PrimitiveType.Quads);
    {
        GL.TexCoord4(texCoords[0]); GL.Vertex4(LocalPoints[0].X, LocalPoints[0].Y, 1, 1); //UL  LocalPoints[0] gimbalUL
        GL.TexCoord4(texCoords[1]); GL.Vertex4(LocalPoints[1].X, LocalPoints[1].Y, 1, 1); //UR  LocalPoints[1] gimbalUR
        GL.TexCoord4(texCoords[2]); GL.Vertex4(LocalPoints[2].X, LocalPoints[2].Y, 1, 1); //LR  LocalPoints[2] gimbalLR
        GL.TexCoord4(texCoords[3]); GL.Vertex4(LocalPoints[3].X, LocalPoints[3].Y, 1, 1); //LL  LocalPoints[3] gimbalLL
    }

    GL.End();
    GL.Disable(EnableCap.Texture2D);

    //TODO store drawn texture/image to file (only the drawn texture not screenshot or anything else which is not drawn here)
}

我尝试使用 Framebuffer 执行此操作,但没有成功。绘制到帧缓冲区,然后从中读取像素,输出为空图像。

int FramebufferName = -1;
int depthrenderbuffer;
int fbo_width = 1280;
int fbo_height = 720;

public override void OnRender()
{
    if (!targetPosition.IsEmpty)
    {
        if (FramebufferName == -1)
        {
            //Create new Framebuffer only once
            GL.GenFramebuffers(1, out FramebufferName);
            GL.BindFramebuffer(FramebufferTarget.Framebuffer, FramebufferName);

            //create texture from bitmap 1280x720
            lock (bitmapSync)
            {
                if (bitmap != null)
                {
                    fbo_width = bitmap.Width;
                    fbo_height = bitmap.Height;
                    int t = GL.GenTexture();
                    GL.BindTexture(TextureTarget.Texture2D, t);
                    GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.Linear);
                    GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Linear);
                    GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapS, (int)TextureWrapMode.ClampToEdge);
                    GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapT, (int)TextureWrapMode.ClampToEdge);
                    GL.TexImage2D(TextureTarget.Texture2D, 0, PixelInternalFormat.Rgba, bitmap.Width, bitmap.Height, 0, OpenTK.Graphics.OpenGL.PixelFormat.Rgba, PixelType.UnsignedByte, IntPtr.Zero);

                    Rectangle rect = new Rectangle(0, 0, bitmap.Width, bitmap.Height);
                    System.Drawing.Imaging.BitmapData data = bitmap.LockBits(rect, System.Drawing.Imaging.ImageLockMode.ReadOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb);

                    GL.BindTexture(TextureTarget.Texture2D, t);
                    GL.TexSubImage2D(TextureTarget.Texture2D, 0, rect.X, rect.Y, rect.Width, rect.Height, OpenTK.Graphics.OpenGL.PixelFormat.Bgra, PixelType.UnsignedByte, data.Scan0);

                    bitmap.UnlockBits(data);
                    bitmap.Dispose();
                    bitmap = null;

                    if (renderedTexture > 0)
                        GL.DeleteTexture(renderedTexture);
                    renderedTexture = t;
                    GL.FramebufferTexture2D(FramebufferTarget.DrawFramebuffer, FramebufferAttachment.ColorAttachment0, TextureTarget.Texture2D, renderedTexture, 0); //original texture 1280x720
                }
            }

            /* Storage must be one of: */
            /* GL_RGBA4, GL_RGB565, GL_RGB5_A1, GL_DEPTH_COMPONENT16, GL_STENCIL_INDEX8. */
            //GL.RenderbufferStorage(RenderbufferTarget.Renderbuffer, RenderbufferStorage.DepthComponent16, fbo_width, fbo_height);
            //GL.FramebufferRenderbuffer(FramebufferTarget.Framebuffer, FramebufferAttachment.ColorAttachment0, RenderbufferTarget.Renderbuffer, renderedTexture);

            /* Depth renderbuffer. */
            GL.GenRenderbuffers(1, out depthrenderbuffer);
            GL.BindRenderbuffer(RenderbufferTarget.Renderbuffer, depthrenderbuffer);
            GL.RenderbufferStorage(RenderbufferTarget.Renderbuffer, RenderbufferStorage.DepthComponent24, fbo_width, fbo_height);
            GL.FramebufferRenderbuffer(FramebufferTarget.DrawFramebuffer, FramebufferAttachment.DepthAttachment, RenderbufferTarget.Renderbuffer, depthrenderbuffer);

            //GL.ReadBuffer(ReadBufferMode.ColorAttachment0);

            //DrawBuffersEnum[] drawBuffersEnum = new DrawBuffersEnum[] { DrawBuffersEnum.ColorAttachment0 };
            //GL.DrawBuffers(1, drawBuffersEnum);

            if (GL.CheckFramebufferStatus(FramebufferTarget.Framebuffer) != FramebufferErrorCode.FramebufferComplete)
            {
                GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
                GL.BindFramebuffer(FramebufferTarget.Framebuffer, 0); //would draw to the default framebuffer again, basically finishing the drawing to the other framebuffer(the backbuffer which will be brought to front by SwapBuffers)
                GL.DeleteFramebuffers(1, ref FramebufferName);
                GL.DeleteFramebuffers(1, ref depthrenderbuffer);
                return;
            }
            checkGlError();
        }

        //drawInFramebuffer
        //GL.BindTexture(TextureTarget.Texture2D, 0);
        //GL.Enable(EnableCap.Texture2D);
        GL.BindFramebuffer(FramebufferTarget.Framebuffer, FramebufferName);
        //GL.Viewport(0, 0, fbo_width, fbo_height);
        checkGlError();

        //clear all
        GL.ClearColor(1, 1, 1, 0);
        GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);

        //GL.MatrixMode(MatrixMode.Projection);
        //GL.LoadIdentity();
        checkGlError();

        //bind texture to framebuffer
        GL.Enable(EnableCap.Texture2D);
        checkGlError();
        GL.ActiveTexture(TextureUnit.Texture0);
        checkGlError();
        GL.BindTexture(TextureTarget.Texture2D, renderedTexture);
        checkGlError();
        //GL.BindRenderbuffer(RenderbufferTarget.Renderbuffer, renderedTexture);
        //checkGlError();

        //draw in framebuffer
        //Do the magick for "Perspective correct texturing"
        // center point
        GPoint localTargetPosition = MainForm.instance.gMapControl.FromLatLngToLocalWithOffset(targetPosition);
        // determines distances to center for all vertexes
        double dUL = Common.distance(new double[] { LocalPoints[0].X, LocalPoints[0].Y }, new double[] { localTargetPosition.X, localTargetPosition.Y });
        double dUR = Common.distance(new double[] { LocalPoints[1].X, LocalPoints[1].Y }, new double[] { localTargetPosition.X, localTargetPosition.Y });
        double dLR = Common.distance(new double[] { LocalPoints[2].X, LocalPoints[2].Y }, new double[] { localTargetPosition.X, localTargetPosition.Y });
        double dLL = Common.distance(new double[] { LocalPoints[3].X, LocalPoints[3].Y }, new double[] { localTargetPosition.X, localTargetPosition.Y });

        var texCoords = new[]
        {
            new Vector4(0, 0, 1, 1),
            new Vector4(1, 0, 1, 1),
            new Vector4(1, 1, 1, 1),
            new Vector4(0, 1, 1, 1)
        };

        texCoords[0] *= (float)((dUL + dLR) / dLR);
        texCoords[1] *= (float)((dUR + dLL) / dLL);
        texCoords[2] *= (float)((dLR + dUL) / dUL);
        texCoords[3] *= (float)((dLL + dUR) / dUR);

        GL.Begin(PrimitiveType.Quads);
        {
            GL.TexCoord4(texCoords[0]); GL.Vertex4(LocalPoints[0].X, LocalPoints[0].Y, 1, 1); //UL  LocalPoints[0] gimbalUL
            GL.TexCoord4(texCoords[1]); GL.Vertex4(LocalPoints[1].X, LocalPoints[1].Y, 1, 1); //UR  LocalPoints[1] gimbalUR
            GL.TexCoord4(texCoords[2]); GL.Vertex4(LocalPoints[2].X, LocalPoints[2].Y, 1, 1); //LR  LocalPoints[2] gimbalLR
            GL.TexCoord4(texCoords[3]); GL.Vertex4(LocalPoints[3].X, LocalPoints[3].Y, 1, 1); //LL  LocalPoints[3] gimbalLL
        }

        GL.End();
        GL.Disable(EnableCap.Texture2D);
        checkGlError();

        //TODO, get size an location where image is in framebuffer
        fbo_width = 1280;
        fbo_height = 720;
        long minX = 0;
        long maxY = 0;

        #endregion
        using (Bitmap bitmap = new Bitmap(fbo_width, fbo_height))
        {
            BitmapData bits = bitmap.LockBits(new Rectangle(0, 0, fbo_width, fbo_height), ImageLockMode.WriteOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
            GL.ReadPixels((int)minX, (int)maxY, fbo_width, fbo_height, OpenTK.Graphics.OpenGL.PixelFormat.Bgra, PixelType.UnsignedByte, bits.Scan0);
            bitmap.UnlockBits(bits);
            bitmap.RotateFlip(RotateFlipType.Rotate180FlipX);
            bitmap.Save(@"c:\Downloads\aaa\ReadPixels_" + now.ToString("HHmmss_fff") + ".png", ImageFormat.Png); //getting empty image, alpha = 0
        }
        
        checkGlError();
        GL.BindFramebuffer(FramebufferTarget.Framebuffer, 0); //would draw to the default framebuffer again, basically finishing the drawing to the other framebuffer(the backbuffer which will be brought to front by SwapBuffers)

        //TODO draw framebuffer on screen. HOW???
        /*
        GL.Enable(EnableCap.Texture2D);
        GL.BindTexture(TextureTarget.Texture2D, renderedTexture);
        GL.BlitFramebuffer(0, 0, fbo_width, fbo_height, 0, 0, fbo_width, fbo_height, ClearBufferMask.ColorBufferBit, BlitFramebufferFilter.Nearest);
        */
    }
    else
    {
        base.OnRender();
    }
}

private void checkGlError()
{
    ErrorCode errorCode = GL.GetError();
    if (errorCode != ErrorCode.NoError)
    {
        Console.WriteLine("ERROR: " + errorCode);
    }
}

【问题讨论】:

  • 您想从纹理中读取数据(代码中的texture)还是要获取正在渲染纹理的屏幕部分?
  • 听起来你想要截取部分屏幕,是吗?
  • 不,我不想要屏幕一部分的屏幕截图,也可以在该位置绘制其他内容,因此屏幕截图不正确
  • @Martin86 那你想要什么?
  • @Rabbid76 我要获取绘制的纹理(本例为小梯形),不是原图(矩形),一定不能截图

标签: c# opengl opentk gmap.net


【解决方案1】:

如果你想从帧缓冲区中读取一个矩形区域,那么你可以使用GL.ReadPixels。例如:

Bitmap bmp = new Bitmap(width, height);
System.Drawing.Imaging.BitmapData data =
    bmp.LockBits(this.ClientRectangle, System.Drawing.Imaging.ImageLockMode.WriteOnly,
                 System.Drawing.Imaging.PixelFormat.Format24bppRgb);
GL.ReadPixels(x, y, width, height, PixelFormat.Bgr, PixelType.UnsignedByte, data.Scan0);
bmp.UnlockBits(data);

GL.GetTexImage 可以读取纹理对象的像素数据。此功能仅在桌面 OpenGL 中提供,在 OpenGL ES 中不提供:

GL.BindTexture(TextureTarget.Texture2D, texture);
GL.GetTexImage(TextureTarget.Texture2D, 0, PixelFormat.Bgr, PixelType.UnsignedByte, target);

在 OpenGL ES 中,您需要将纹理附加到帧缓冲区。见opengl es 2.0 android c++ glGetTexImage alternative

【讨论】:

  • GL.ReadPixels 在我的情况下需要宽度和高度,地图上的图像是梯形,我会得到黑色角度还是什么?
  • @Martin86 不行。你只能得到一个矩形区域。我还是不明白你想要什么。我似乎想要没有任何“背景”的纹理。这是不可能的。您必须将纹理渲染到单独的帧缓冲区。
  • 我已经为帧缓冲区添加了我的代码,但结果我得到了黄色图像,所以看起来我不明白需要如何设置这个帧缓冲区
  • 新的帧缓冲是正确的解决方案。我不知道我需要在代码中的哪个位置创建它以及如何清理它以使其仅包含在新的梯形图像中。目前,当我 ReadPixels 时,它会从左下角开始给我截图。
  • 我已经更新了用于绘制到帧缓冲区然后读取像素的代码,输出是原始纹理 1280x720 而不是图像 1280x720 上的小梯形
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-10-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多