使用OpenGL将绘制的纹理保存到文件中

问题描述

我用openGL绘制纹理(视频图像)。在地图上绘制图像,因此,在大多数情况下,绘制的图像是梯形的。仅当我使用“透视正确纹理”时,图像在地图上看起来才不错。 我的问题是如何捕捉绘制的纹理并将其存储到文件中。 我只想保存绘制的纹理,而不保存屏幕截图或此函数中此处未绘制的任何其他内容(公共重写void OnRender())。 我还会在地图上渲染其他内容,因此无法进行屏幕截图。因此,如何绘制一些帧缓冲以在屏幕上使用它并将其保存到文件

使用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 },localTargetPosition.Y });
    double dLR = Common.distance(new double[] { LocalPoints[2].X,LocalPoints[2].Y },localTargetPosition.Y });
    double dLL = Common.distance(new double[] { LocalPoints[3].X,LocalPoints[3].Y },localTargetPosition.Y });

    var texCoords = new[]
    {
            new Vector4(0,1,1),new Vector4(1,new Vector4(0,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); //UL  LocalPoints[0] gimbalUL
        GL.TexCoord4(texCoords[1]); GL.Vertex4(LocalPoints[1].X,LocalPoints[1].Y,1); //UR  LocalPoints[1] gimbalUR
        GL.TexCoord4(texCoords[2]); GL.Vertex4(LocalPoints[2].X,LocalPoints[2].Y,1); //LR  LocalPoints[2] gimbalLR
        GL.TexCoord4(texCoords[3]); GL.Vertex4(LocalPoints[3].X,LocalPoints[3].Y,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,PixelInternalFormat.Rgba,bitmap.Width,bitmap.Height,OpenTK.Graphics.OpenGL.PixelFormat.Rgba,PixelType.UnsignedByte,IntPtr.Zero);

                    Rectangle rect = new Rectangle(0,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,rect.X,rect.Y,rect.Width,rect.Height,OpenTK.Graphics.OpenGL.PixelFormat.Bgra,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,RenderbufferTarget.Renderbuffer,renderedTexture);

            /* Depth renderbuffer. */
            GL.GenRenderbuffers(1,out depthrenderbuffer);
            GL.BindRenderbuffer(RenderbufferTarget.Renderbuffer,depthrenderbuffer);
            GL.RenderbufferStorage(RenderbufferTarget.Renderbuffer,RenderbufferStorage.DepthComponent24,fbo_height);
            GL.FramebufferRenderbuffer(FramebufferTarget.DrawFramebuffer,FramebufferAttachment.DepthAttachment,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,fbo_height);
        checkGlError();

        //clear all
        GL.ClearColor(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,localTargetPosition.Y });
        double dUR = Common.distance(new double[] { LocalPoints[1].X,localTargetPosition.Y });
        double dLR = Common.distance(new double[] { LocalPoints[2].X,localTargetPosition.Y });
        double dLL = Common.distance(new double[] { LocalPoints[3].X,localTargetPosition.Y });

        var texCoords = new[]
        {
            new Vector4(0,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,1); //UL  LocalPoints[0] gimbalUL
            GL.TexCoord4(texCoords[1]); GL.Vertex4(LocalPoints[1].X,1); //UR  LocalPoints[1] gimbalUR
            GL.TexCoord4(texCoords[2]); GL.Vertex4(LocalPoints[2].X,1); //LR  LocalPoints[2] gimbalLR
            GL.TexCoord4(texCoords[3]); GL.Vertex4(LocalPoints[3].X,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,fbo_height),ImageLockMode.writeonly,System.Drawing.Imaging.PixelFormat.Format32bppArgb);
            GL.ReadPixels((int)minX,(int)maxY,fbo_height,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,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,ClearBufferMask.ColorBufferBit,BlitFramebufferFilter.Nearest);
        */
    }
    else
    {
        base.OnRender();
    }
}

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

解决方法

如果要从帧缓冲区读取矩形区域,则可以使用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,target);

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

相关问答

Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其...
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。...
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbc...