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

Posted

技术标签:

【中文标题】OpenTK 屏幕外帧缓冲区扭曲并使用相同的材​​料不正确地渲染?【英文标题】:OpenTK offscreen framebuffer distorted and rendering improperly with identical material? 【发布时间】:2020-10-13 08:26:42 【问题描述】:

我正在尝试实现阴影映射:将所有相关对象的深度渲染到屏幕外纹理,然后在我的主通道中使用该纹理来确定哪些片段被光源遮挡。

但在此之前,我只是尝试正确渲染任何类型的帧缓冲区。我创建了一个包含 2056x2056 4 通道图像的 FBO,然后创建了另一个没有纹理的 FBO,它只表示并绑定回默认帧缓冲区(opentk 窗口)。

我正在使用一个简单的着色器,它在屏幕外缓冲区上使用 uvs 作为 R、G 颜色,在主屏幕纹理上使用一个 phong 着色器,并且我正在发送相同的制服。唯一的区别是我没有清除屏幕外缓冲区,也没有深度附件。但是由于某种原因,屏幕外的帧缓冲区变得超级扭曲和小,就像它只占用纹理大小的 1/4 一样,扭曲了,而且还会出现一些奇怪的嘈杂剪辑。

这是一张图片 captured by NVIDIA Nsight Graphics: the offscreen buffer on the left, the default framebuffer on the right.

任何帮助/见解将不胜感激非常。 谢谢!

我的 FBO 课程。

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;
    

我的纹理类(fbo 包含)

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
        


    

我在哪里设置了我的两个 fbo(嗯……一个更像是一个接口)

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

我的绘制循环:

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);
                
            
            
        

【问题讨论】:

您是否将视口矩形调整为当前绑定的帧缓冲区的大小? (glViewport) 顺便说一下,TextureParameterName.TextureMagFilter 被设置了两次,但是TextureParameterName.TextureMinFilter 却不见了。因此,您必须生成 mipmap。默认的缩小函数是NEAREST_MIPMAP_LINEAR。因此,如果没有生成 mipmap,则纹理是不完整的。无论如何,在ApplyTextureSettings 中生成一次 mipmap 就足够了。 我更改了视口大小以匹配帧缓冲区的大小,我错误地将视口大小与窗口大小相关联,没有意识到这是影响着色器程序的 openGL 问题。我还添加了 TextureMinFiltering here's the new image, now using normals > uvs to visualize。似乎还有一些奇怪的事情发生。为什么法线的颜色没有逐渐变化,颜色似乎有一些随机变化,就像我从纹理而不是 fragColor 的法线采样一样。可能与深度测试无关? 【参考方案1】:

最终的问题是:

我没有更改 GL.viewport 大小以匹配帧缓冲区。 我没有启用面部剔除,因此当我基于 uvs/法线(没有深度附件)进行着色时,有时球体多边形的内壳会穿过而不是外壳,导致颜色不连续屏幕外的纹理。

我也无意中没有正确设置纹理过滤,但这不是我担心的任何主要图形故障的根源。

谢谢你的帮助!

【讨论】:

我认为问题不在于面部剔除。问题是,帧缓冲区没有深度缓冲区。因此,当您绘制到帧缓冲区时,深度测试不起作用。面剔除可以解决一个网格的问题,但如果多个网格相互覆盖,则无法解决问题。您必须向帧缓冲区添加深度缓冲区附件。 我明白了,但对我来说这不是一个问题,只要我知道它为什么会发生。我不明白没有面部剔除和没有深度测试的组合会导致奇怪,我认为这可能是一些失真。话虽如此,我最终需要为阴影贴图附加一个深度缓冲区

以上是关于OpenTK 屏幕外帧缓冲区扭曲并使用相同的材​​料不正确地渲染?的主要内容,如果未能解决你的问题,请参考以下文章

C# OpenTK - 不渲染的简单示例

OpenTK - 累积缓冲区的可用性

OpenGL 中的离屏帧缓冲区

我正在尝试在 opentk 中实现索引缓冲区对象,但不知道在绘图时如何实际使用它

OpenTK (OpenGL) 正确使用 BufferData 和 BufferSubData

OpenTK - 模型视图投影问题