问题描述
我用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。