使用 openTK 绘图,保存到文件,显示在屏幕上

Posted

技术标签:

【中文标题】使用 openTK 绘图,保存到文件,显示在屏幕上【英文标题】:Drawing with openTK, save to file, show on screen 【发布时间】:2020-12-11 11:47:05 【问题描述】:

我正在地球地图上绘制多张图像,我在每张图像上使用透视正确纹理link。

我想将渲染图像存储到文件中(源文件是 1280x760,渲染图像在大多数情况下旋转大约 160x90)。目前我正在使用 GL.ReadPixels 进行此操作

int width = 1920;
int height = 1080;
using (Bitmap bitmap = new Bitmap(width, height))

    System.Drawing.Imaging.BitmapData bits = bitmap.LockBits(new Rectangle(0, 0, width, height), System.Drawing.Imaging.ImageLockMode.WriteOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
    GL.ReadPixels(0, (0, width, height, OpenTK.Graphics.OpenGL.PixelFormat.Bgra, PixelType.UnsignedByte, bits.Scan0);
    bitmap.UnlockBits(bits);
    bitmap.RotateFlip(RotateFlipType.Rotate180FlipX);
    bitmap.Save("output.png", System.Drawing.Imaging.ImageFormat.Png);

当我移动地图时出现问题,渲染的图像在屏幕上不再可见,看起来在这种情况下 GL.ReadPixels 返回空像素。

即使当前未在屏幕上渲染,如何获取渲染图像?

额外的问题,如果我使用帧缓冲区,结果是相同的,但使用帧缓冲区我从未在屏幕上看到图像,看起来帧缓冲区没有在屏幕上绘制,但 GL.ReadPixels 可以获取图像。 还需要哪些代码行才能在屏幕上绘制帧缓冲区?

有什么想法吗?

我正在向帧缓冲区添加一些绘图代码,但结果是空图像。

int FBOHandle = 0;
int ColorTexture = 0;
int DepthTexture = 0;

public bool canRender = false;
public void onRender()

    int zoom = (int)MainForm.mainMap.Zoom;
    VideoMapOverlayBitmap pob = null;
    lock (videoMapOverlayBitmapsSync)
        videoMapOverlayBitmaps.TryGetValue(zoom, out pob);
    if (pob == null)
        return;

    if (canRender)
    
        canRender = false;
        int fboWidth = 1920;
        int fboHeight = 1080;

        // Create Color Tex for framebuffer
        GL.GenTextures(1, out ColorTexture);
        GL.BindTexture(TextureTarget.Texture2D, ColorTexture);
        GL.TexImage2D(TextureTarget.Texture2D, 0, PixelInternalFormat.Rgba8, fboWidth, fboHeight, 0, OpenTK.Graphics.OpenGL.PixelFormat.Rgba, PixelType.UnsignedByte, IntPtr.Zero);
        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.ClampToBorder);
        GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapT, (int)TextureWrapMode.ClampToBorder);
        // GL.Ext.GenerateMipmap( GenerateMipmapTarget.Texture2D );

        // Create Depth Tex for framebuffer
        GL.GenTextures(1, out DepthTexture);
        GL.BindTexture(TextureTarget.Texture2D, DepthTexture);
        GL.TexImage2D(TextureTarget.Texture2D, 0, (PixelInternalFormat)All.DepthComponent32, fboWidth, fboHeight, 0, OpenTK.Graphics.OpenGL.PixelFormat.DepthComponent, PixelType.UnsignedInt, IntPtr.Zero);
        // things go horribly wrong if DepthComponent's Bitcount does not match the main Framebuffer's Depth
        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.ClampToBorder);
        GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapT, (int)TextureWrapMode.ClampToBorder);
        // GL.Ext.GenerateMipmap( GenerateMipmapTarget.Texture2D );

        // Create a FBO and attach the textures
        GL.Ext.GenFramebuffers(1, out FBOHandle);
        GL.Ext.BindFramebuffer(FramebufferTarget.FramebufferExt, FBOHandle);
        GL.Ext.FramebufferTexture2D(FramebufferTarget.FramebufferExt, FramebufferAttachment.ColorAttachment0Ext, TextureTarget.Texture2D, ColorTexture, 0);
        GL.Ext.FramebufferTexture2D(FramebufferTarget.FramebufferExt, FramebufferAttachment.DepthAttachmentExt, TextureTarget.Texture2D, DepthTexture, 0);

        //check for errors on framebuffer
        FramebufferErrorCode errorCode = GL.Ext.CheckFramebufferStatus(FramebufferTarget.Framebuffer);
        if (errorCode != FramebufferErrorCode.FramebufferComplete)
        
            if (errorCode == FramebufferErrorCode.FramebufferUnsupported)
                Console.WriteLine("FramebufferUnsupported");
            OnUnload();
            return;
        

        GL.ClearColor(0, 0, 0, 0);
        GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
        /* 
        //this corrupts my main screen
        GL.ClearColor(Color.White);
        GL.MatrixMode(MatrixMode.Projection);
        GL.LoadIdentity();
        GL.Ortho(0, fboWidth, fboHeight, 0, -1, 1);  // Up-left corner pixel has coordinate (0, 0)
        GL.Viewport(0, 0, fboWidth, fboHeight);      // Use all of the glControl painting area   
        */

        // Render all images
        PureProjection proj = MainForm.mainMap.MapProvider.Projection;
        List<VideoLogEntry> log = Log;
        //go over all images in the loop
        foreach (var videoEntry in log)
        
            if (videoEntry == null)
                continue;
            if (videoEntry.projectedRectangleEmpty())
                continue;

            PointLatLng[] rect = videoEntry.getProjectedRectangle();

            if (videoEntry.bmp != null)
                videoEntry.createTexture();

            GL.Enable(EnableCap.Texture2D);
            GL.BindTexture(TextureTarget.Texture2D, videoEntry.texture);
            //GL.Ext.FramebufferTexture2D(FramebufferTarget.FramebufferExt, FramebufferAttachment.ColorAttachment0Ext, TextureTarget.Texture2D, videoEntry.texture, 0);
            //GL.DrawBuffers(1, new int[]  videoEntry.texture ); //compiler error

            //Do the magick for "Perspective correct texturing"
            // center point
            GPoint localTargetPosition = MainForm.instance.gMapControl.FromLatLngToLocalWithOffset(videoEntry.target);
            videoEntry.UpdatePolygonLocalPosition(videoEntry.projectedRectangle);
            // determines distances to center for all vertexes
            double dUL = Common.distance(new double[]  videoEntry.LocalPoints[0].X, videoEntry.LocalPoints[0].Y , new double[]  localTargetPosition.X, localTargetPosition.Y );
            double dUR = Common.distance(new double[]  videoEntry.LocalPoints[1].X, videoEntry.LocalPoints[1].Y , new double[]  localTargetPosition.X, localTargetPosition.Y );
            double dLR = Common.distance(new double[]  videoEntry.LocalPoints[2].X, videoEntry.LocalPoints[2].Y , new double[]  localTargetPosition.X, localTargetPosition.Y );
            double dLL = Common.distance(new double[]  videoEntry.LocalPoints[3].X, videoEntry.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(videoEntry.LocalPoints[0].X, videoEntry.LocalPoints[0].Y, 1, 1); //UL  LocalPoints[0] gimbalUL
                GL.TexCoord4(texCoords[1]); GL.Vertex4(videoEntry.LocalPoints[1].X, videoEntry.LocalPoints[1].Y, 1, 1); //UR  LocalPoints[1] gimbalUR
                GL.TexCoord4(texCoords[2]); GL.Vertex4(videoEntry.LocalPoints[2].X, videoEntry.LocalPoints[2].Y, 1, 1); //LR  LocalPoints[2] gimbalLR
                GL.TexCoord4(texCoords[3]); GL.Vertex4(videoEntry.LocalPoints[3].X, videoEntry.LocalPoints[3].Y, 1, 1); //LL  LocalPoints[3] gimbalLL
            

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

        // Grab your screenshot
        // draw FBO in to file
        lock (pob.masterBitmapSync)
        
            using (Bitmap mybitmap = new Bitmap(fboWidth, fboHeight))
            
                //fill bitmal so we will see what ReadPixels draw
                using (Graphics gfx = Graphics.FromImage(mybitmap))
                using (SolidBrush brush = new SolidBrush(Color.FromArgb(0, 0, 255)))
                
                    gfx.FillRectangle(brush, 0, 0, mybitmap.Width, mybitmap.Height);
                

                GPoint p = new GPoint(0, 0);
                int outputWidth = mybitmap.Width;
                int outputHeight = mybitmap.Height;

                System.Drawing.Imaging.BitmapData bits = mybitmap.LockBits(new Rectangle(0, 0, mybitmap.Width, mybitmap.Height), System.Drawing.Imaging.ImageLockMode.WriteOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
                GL.ReadPixels((int)p.X, (int)p.Y, outputWidth, outputHeight, OpenTK.Graphics.OpenGL.PixelFormat.Bgra, PixelType.UnsignedByte, bits.Scan0);
                mybitmap.UnlockBits(bits);
                //mybitmap.RotateFlip(RotateFlipType.Rotate180FlipX);
                mybitmap.Save(@"c:\Downloads\aaa\ReadPixels_" + DateTime.Now.ToString("HHmmss_fff") + ".png", System.Drawing.Imaging.ImageFormat.Png);
                pob.masterBitmap.Dispose();
                pob.masterBitmap = null;
                pob.masterBitmap = (Bitmap)mybitmap.Clone();
            
        

        // Unload and dispose the frame buffer
        GL.Ext.BindFramebuffer(FramebufferTarget.FramebufferExt, 0);
        
        // Clean up what we allocated before exiting
        if (ColorTexture != 0)
            GL.DeleteTextures(1, ref ColorTexture);
        ColorTexture = 0;

        if (DepthTexture != 0)
            GL.DeleteTextures(1, ref DepthTexture);
        DepthTexture = 0;

        if (FBOHandle != 0)
            GL.Ext.DeleteFramebuffers(1, ref FBOHandle);
        FBOHandle = 0;
    

【问题讨论】:

【参考方案1】:

即使当前未在屏幕上渲染,如何获取渲染图像?

创建一个新的帧缓冲区,绑定该帧缓冲区,设置视口,设置正射投影矩阵,将您想要的地图部分渲染到其中,调用 GL.ReadPixels,保存屏幕截图,卸载和处置帧缓冲区。

这里有一些示例代码:

// Create framebuffer
int fboId = GL.GenFramebuffer();
GL.BindFramebuffer(FramebufferTarget.Framebuffer, fboId);

// Set up a framebuffer attachment here
int width = ...
int height = ...
int textureId = GL.GenTexture();
GL.BindTexture(TextureTarget.Texture2D, textureId);
GL.TexImage2D(TextureTarget.Texture2D, 0, PixelInternalFormat.Rgba8, width, height, 0, PixelFormat.Rgba, PixelType.Float, IntPtr.Zero);

GL.FramebufferTexture2D(FramebufferTarget.Framebuffer, (FramebufferAttachment)fbAtt.AttachmentType, TextureTarget.Texture2D, textureId, 0);

GL.DrawBuffers(1, new int[]  textureId );

GL.Viewport(0, 0, width, height);

// Set up ortho modo. probably also want to disable depth testing and any active blend modes
....

// Render your map now
....

// Grab your screenshot
.... 

// Unload and dispose the frame buffer
...

// Reset everything (viewport, ortho mode, etc.) if you don't your normal map to flicker for a frame
....

你的另一个问题我不太明白

【讨论】:

@Tryron 我按照你的建议用代码编辑我的问题。但我得到空图像。我需要调用 GL.Ext.BindFramebuffer 还是 GL.BindFramebuffer? 您可能仍需要正确设置正交模式和视口。完成修复主屏幕“损坏”后,将它们重置为原始值。话虽如此,很多事情都会导致您的图像为空。也可能与混合、部门测试或其他原因有关。

以上是关于使用 openTK 绘图,保存到文件,显示在屏幕上的主要内容,如果未能解决你的问题,请参考以下文章

Gnuplot 输出设置为“在屏幕上”显示如何更改程序,以便将绘图输出定向到我在下面提供程序的文件夹

在previewLayer上绘图:AVCaptureVideoPreviewLayer

将 Matlab 输出保存到文件,而不是在屏幕上显示

OpenTK 屏幕外帧缓冲区扭曲并使用相同的材​​料不正确地渲染?

MFC双缓冲绘图实例

如何从同一资源运行两个函数(绘图图形并将无限数据保存到txt文件)