OpenTK offscreen framebuffer distorted and rendering improperly with identical material?

391 Views Asked by At

I'm trying to implement shadow mapping: rendering the depth of all relevant objects to an offscreen texture, then using that texture in my main pass to figure out what fragments are obscured by a light source.

But before that, I'm just trying to get frame buffers of any sort rendering correctly. I've created an FBO containing a 2056x2056 4 channel image and then another which has no texture and just represents and binds back to the default framebuffer (the opentk window).

I'm using a simple shader which uses uvs as R,G color on the offscreen buffer, and a phong shader on the mainscreen texture, and I'm sending identical uniforms. The only difference is that I'm not clearing the offscreen buffer and there's also no depth attachment. But for some reason the offscreen framebuffer is coming super warped and small, like it only takes up about 1/4 the size of the texture, is warped, and there's also some weird noisy clipping going on.

Here's an image captured by NVIDIA Nsight Graphics: the offscreen buffer on the left, the default framebuffer on the right.

Any help/insights would be greatly appreciated. Thanks!

My FBO class.

namespace MaterialRelated
{
    public class FBO : ITypeID
    {
        public readonly int Handle = 0;
        public readonly CreateFBOs.FBOType Type;
        public Texture Texture { get; private set; }

        public FBO(CreateFBOs.FBOType type, int width, int height, FramebufferAttachment attachment, PixelInternalFormat internalFormat, TextureUnit textureUnit)
        {
            Handle = GL.GenFramebuffer();
            Type = type;
            Use();
            AssignTexture(Texture.Empty(width, height, internalFormat, textureUnit), attachment);
            var fboStatus = GL.CheckFramebufferStatus(FramebufferTarget.Framebuffer);
            if (fboStatus != FramebufferErrorCode.FramebufferComplete)
                throw new Exception($"Frame Buffer Exception! {fboStatus}");
        }

        /// <summary>
        /// Creates default fbo
        /// </summary>
        public FBO()
        {
            Type = CreateFBOs.FBOType.Default;
            Handle = 0;
            Texture = null;
        }

        public void Use()
        {
            GL.BindFramebuffer(FramebufferTarget.Framebuffer, Handle);
        }

        public void SetDrawingStates()
        {
            GL.BindFramebuffer(FramebufferTarget.Framebuffer, Handle);
        }

        public static void UseDefaultBuffer()
        {
            GL.BindFramebuffer(FramebufferTarget.Framebuffer, 0);
        }
    
        
        public void AssignTexture(Texture texture, FramebufferAttachment attachment)
        {
            Texture = texture;
             GL.FramebufferTexture2D(FramebufferTarget.Framebuffer, attachment, TextureTarget.Texture2D, texture.Handle, 0); //todo fix assign tex
        }

        public int GetTypeID() => (int) Type;
    }
}

My Texture class (which an fbo contains)

namespace MaterialRelated
{
    public class Texture
    {

        public TextureUnit TextureUnit { get; private set; }
        public PixelInternalFormat InternalPixelFormat { get; private set; }
        
        public readonly int Handle;
        
        public byte[] Colors; //todo make array for perf
        
        public Image<Rgba32> LaborsImage;
        public int Width { get; private set; }
        public int Height { get; private set; }
        
        public Texture()
        {
            Handle = GL.GenTexture();
        }
  
        /// <summary>
        /// loads file using sixlabors library... turn cookOnLoad off if you're going to manipulate image on cpu before uploading to openGL
        /// </summary>
        public static Texture FromFile(string fileName, TextureUnit textureUnit) //turn cookOnLoad off if you're going to manipulate image on cpu before uploading to openGL
        {
            var texture = new Texture();
            texture.InternalPixelFormat = PixelInternalFormat.Rgba;
            texture.TextureUnit = textureUnit;
            texture.Use();
            texture.ApplyTextureSettings();
            texture.LoadImage(fileName);
            texture.CookSixLaborsImageToByteArray();

            texture.UploadToShader();

            return texture;
        }
        
        public static Texture Empty(int width, int height, PixelInternalFormat internalFormat, TextureUnit textureUnit)
        {
            var texture = new Texture();

            texture.InternalPixelFormat = internalFormat;
            texture.TextureUnit = textureUnit;
            texture.Use();
            texture.ApplyTextureSettings();
            texture.Width = width;
            texture.Height = height;
            texture.CreateEmptyByteArray();

            texture.UploadToShader();
            
            return texture;
        }
        

        public void LoadImage(string fileName)
        {
            string path = SerializationManager.TexturePath + fileName;
            LaborsImage = Image.Load<Rgba32>(path);
            LaborsImage.Mutate(x => x.Flip(FlipMode.Vertical)); //ImageSharp loads from the top-left pixel, whereas OpenGL loads from the bottom-left, causing the texture to be flipped vertically.
            Width  = LaborsImage.Width;
            Height = LaborsImage.Height;
        }

        public void CreateEmptyByteArray()
        {
            int area = Width * Height;
            Colors = new byte[area * 4];
        }
        
        public void CookSixLaborsImageToByteArray()
        {
            if (!LaborsImage.TryGetSinglePixelSpan(out var tempPixels))
                throw new Exception("Image Loading Error: Is Texture Corrupt?");

            int area = Width * Height;
            if (Colors == null || Colors.Length != area * 4) //init colors to proper length if not already
                Colors = new byte[area * 4];

            for (int i = 0; i < tempPixels.Length; i++)
            {
                int indexStart = i * 4;
                Colors[indexStart + 0] = tempPixels[i].R;
                Colors[indexStart + 1] = tempPixels[i].G;
                Colors[indexStart + 2] = tempPixels[i].B;
                Colors[indexStart + 3] = tempPixels[i].A;
            }
        }

        public void Use()
        {
            GL.ActiveTexture(TextureUnit);
            GL.BindTexture(TextureTarget.Texture2D, Handle);
        }
        
        public void UploadToShader()
        {
            Use();
            GL.TexImage2D(TextureTarget.Texture2D, 0, InternalPixelFormat, Width, Height, 0, PixelFormat.Rgba, PixelType.UnsignedByte, Colors);
            GL.GenerateMipmap(GenerateMipmapTarget.Texture2D);
        }

        private void ApplyTextureSettings() //todo make it so doesn't always have to be rgba image
        {
            Use();
            GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapS, (int) TextureWrapMode.Repeat); //tex wrap mode
            GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapT, (int) TextureWrapMode.Repeat);
            GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int) TextureMagFilter.Linear); //scaling up, tex interp
            GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int) TextureMagFilter.Linear); //scaling down
        }


    }
}

where i set up my two fbos (well... one is more an interface)

ShadowBuffer = new FBO(FBOType.Shadow, 2560,2560, FramebufferAttachment.ColorAttachment0, PixelInternalFormat.Rgba, TextureUnit.Texture3);
            var defaultBuffer = new FBO();

My Draw Loop:

public void RenderFrame()
        {
            for (int fboIndex = 0; fboIndex < BatchHierachies.Count; fboIndex++)
            {
                FBOBatch fboBatch = BatchHierachies[fboIndex];
                fboBatch.FBO.SetDrawingStates();
                
                for (int materialIndex = 0; materialIndex < fboBatch.MaterialBatches.Count; materialIndex++)
                {
                    MaterialBatch materialBatch = fboBatch.MaterialBatches[materialIndex];
                    materialBatch.Material.SetDrawingStates();

                    for (int entityIndex = 0; entityIndex < materialBatch.Entities.Count; entityIndex++)
                    {
                        Entity entity = materialBatch.Entities[entityIndex];
                        
              
                         entity.SendUniformsPerObject(materialBatch.Material);

                        GL.DrawArrays(PrimitiveType.Triangles, 0, materialBatch.Material.VAO.VerticesCount);
                    }
                }

                if (fboBatch.FBO.Type != CreateFBOs.FBOType.Default)
                {
                    GL.BindTexture(TextureTarget.Texture2D, fboBatch.FBO.Texture.Handle);
                    GL.GenerateMipmap(GenerateMipmapTarget.Texture2D);
                }
            }
            
        }
1

There are 1 best solutions below

2
On

In the end the problems were:

  • I didn't change the GL.viewport size to match the framebuffer.
  • I didn't enable faceface culling so when I was shading based on uvs/normals (without a depth attachment) sometimes the inner shell of the sphere's polygons would come through instead of the outter shell, leading to a discontinuity in the colour of the offscreen's texture.

I also was accidently not setting texture filtering properly, but that wasn't the source of any of the major graphical glitches which I was concerned with.

Thank-you the help!