首页 > 代码库 > CSharpGL(29)初步封装Texture和Framebuffer

CSharpGL(29)初步封装Texture和Framebuffer

+BIT祝威+悄悄在此留下版了个权的信息说:

 

CSharpGL(29)初步封装Texture和Framebuffer

+BIT祝威+悄悄在此留下版了个权的信息说:

Texture和Framebuffer

Texture和Framebuffer是OpenGL进行3D渲染高级效果必不可少的利器。有了Texture和Framebuffer就可以实现体渲染(Volume Rendering)等效果。现在到了对Texture和Framebuffer的创建、修改、使用进行封装的时候。

技术分享

+BIT祝威+悄悄在此留下版了个权的信息说:

下载

CSharpGL已在GitHub开源,欢迎对OpenGL有兴趣的同学加入(https://github.com/bitzhuwei/CSharpGL)

+BIT祝威+悄悄在此留下版了个权的信息说:

封装Texture

过程式的Texture

首先观察一下平时是如何创建和使用Texture对象的。

+BIT祝威+悄悄在此留下版了个权的信息说:

创建Texture

以创建2D Texture为例。

技术分享
 1 uint CreateTexture(Bitmap bitmap) 2 { 3     glActiveTexture(OpenGL.GL_TEXTURE0); 4     var id = new uint[1]; 5     OpenGL.GenTextures(1, id); 6     OpenGL.BindTexture(target, id[0]); 7     OpenGL.TexParameteri(OpenGL.GL_TEXTURE_2D, OpenGL.GL_TEXTURE_WRAP_R, (int)OpenGL.GL_CLAMP_TO_EDGE); 8     OpenGL.TexParameteri(OpenGL.GL_TEXTURE_2D, OpenGL.GL_TEXTURE_WRAP_S, (int)OpenGL.GL_CLAMP_TO_EDGE); 9     OpenGL.TexParameteri(OpenGL.GL_TEXTURE_2D, OpenGL.GL_TEXTURE_WRAP_T, (int)OpenGL.GL_CLAMP_TO_EDGE);10     OpenGL.TexParameteri(OpenGL.GL_TEXTURE_2D, OpenGL.GL_TEXTURE_MIN_FILTER, (int)OpenGL.GL_REPEAT);11     OpenGL.TexParameteri(OpenGL.GL_TEXTURE_2D, OpenGL.GL_TEXTURE_MAG_FILTER, (int)OpenGL.GL_REPEAT);12     13     BitmapData bitmapData = http://www.mamicode.com/bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height),14         ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);15     OpenGL.TexImage2D(OpenGL.GL_TEXTURE_2D, 0, OpenGL.GL_RGBA, bitmap.Width, bitmap.Height, 0, OpenGL.GL_BGRA, OpenGL.GL_UNSIGNED_BYTE, bitmapData.Scan0);16     bitmap.UnlockBits(bitmapData);17     18     return id[0];19 }
CreateTexture 

使用Texture

使用上述Texture的方式:

 1 void UseTexture(string textureNameInShader, uint textureId) 2 { 3     uint target = OpenGL.GL_TEXTURE0; 4     glActiveTexture(target); 5     OpenGL.BindTexture(OpenGL.GL_TEXTURE_2D, textureId); 6     SetUniform("textureNameInShader", target - OpenGL.GL_TEXTURE0); 7 } 8  9 int SetUniform(string uniformName, uint v0)10 {11     int location = GetUniformLocation(uniformName);12     if (location >= 0)13     {14         glUniform1ui(GetUniformLocation(uniformName), v0);15     }16     return location;17 }

封装的Texture

从上述创建Texture的过程可知,创建Texture主要有2个步骤:设置Sampler填充Texture数据。Sampler就是各个滤波选项。填充数据就是用glTexImage2D()一类的命令指定Texture的内容。

 1 void Initialize() 2 { 3     glActiveTexture(this.ActiveTexture); 4     OpenGL.GenTextures(1, id); 5     BindTextureTarget target = this.Target; 6     OpenGL.BindTexture(target, id[0]); 7     this.Sampler.Bind(this.ActiveTexture - OpenGL.GL_TEXTURE0, target); 8     this.ImageFiller.Fill(target); 9     OpenGL.GenerateMipmap((MipmapTarget)((uint)target));// TODO: does this work?10     //this.SamplerBuilder.Unbind(OpenGL.GL_TEXTURE0 - OpenGL.GL_TEXTURE0, this.Target);11     OpenGL.BindTexture(this.Target, 0);12 }
+BIT祝威+悄悄在此留下版了个权的信息说:

Sampler

Sampler中主要就是那几个滤波选项。

 1     /// <summary> 2     /// texture‘s settings. 3     /// </summary> 4     public class SamplerParameters 5     { 6         public TextureWrapping wrapS = TextureWrapping.ClampToEdge; 7         public TextureWrapping wrapT = TextureWrapping.ClampToEdge; 8         public TextureWrapping wrapR = TextureWrapping.ClampToEdge; 9         public TextureFilter minFilter = TextureFilter.Linear;10         public TextureFilter magFilter = TextureFilter.Linear;11 12         public SamplerParameters() { }13     }
+BIT祝威+悄悄在此留下版了个权的信息说:

Sampler的唯一任务就是在创建Texture时指定某些滤波。

 1     /// <summary> 2     /// texture‘s settings. 3     /// </summary> 4     public abstract class SamplerBase 5     { 6         protected MipmapFilter mipmapFilter; 7         public SamplerParameters Parameters { get; protected set; } 8  9         /// <summary>10         /// texture‘s settings.11         /// </summary>12         /// <param name="parameters"></param>13         /// <param name="mipmapFilter"></param>14         public SamplerBase(SamplerParameters parameters, MipmapFilter mipmapFilter)15         {16             if (parameters == null)17             {18                 this.Parameters = new SamplerParameters();19             }20             else21             {22                 this.Parameters = parameters;23             }24 25             this.mipmapFilter = mipmapFilter;26         }27 28         /// <summary>29         /// 30         /// </summary>31         /// <param name="unit">OpenGL.GL_TEXTURE0 etc.</param>32         /// <param name="target"></param>33         public abstract void Bind(uint unit, BindTextureTarget target);34 35     }

实际上为了简化指定Sampler的操作,OpenGL提供了一个Sampler对象。这里顺便也把它封装了。

技术分享
 1     /// <summary> 2     /// texture‘s settings. 3     /// </summary> 4     public partial class Sampler : SamplerBase, IDisposable 5     { 6         /// <summary> 7         /// sampler‘s Id. 8         /// </summary> 9         public uint Id { get; private set; }10 11         /// <summary>12         /// texture‘s settings.13         /// </summary>14         /// <param name="parameters"></param>15         /// <param name="mipmapFiltering"></param>16         public Sampler(17             SamplerParameters parameters = null,18             MipmapFilter mipmapFiltering = MipmapFilter.LinearMipmapLinear)19             : base(parameters, mipmapFiltering)20         {21 22         }23 24         private bool initialized = false;25         /// <summary>26         /// 27         /// </summary>28         public void Initialize(uint unit, BindTextureTarget target)29         {30             if (!this.initialized)31             {32                 this.DoInitialize(unit, target);33                 this.initialized = true;34             }35         }36 37         private void DoInitialize(uint unit, BindTextureTarget target)38         {39             var ids = new uint[1];40             OpenGL.GenSamplers(1, ids);41             this.Id = ids[0];42             //OpenGL.BindSampler(unit, ids[0]);43             OpenGL.BindSampler(unit, ids[0]);44             /* Clamping to edges is important to prevent artifacts when scaling */45             OpenGL.SamplerParameteri(ids[0], OpenGL.GL_TEXTURE_WRAP_R, (int)this.parameters.wrapR);46             OpenGL.SamplerParameteri(ids[0], OpenGL.GL_TEXTURE_WRAP_S, (int)this.parameters.wrapS);47             OpenGL.SamplerParameteri(ids[0], OpenGL.GL_TEXTURE_WRAP_T, (int)this.parameters.wrapT);48             /* Linear filtering usually looks best for text */49             OpenGL.SamplerParameteri(ids[0], OpenGL.GL_TEXTURE_MIN_FILTER, (int)this.parameters.minFilter);50             OpenGL.SamplerParameteri(ids[0], OpenGL.GL_TEXTURE_MAG_FILTER, (int)this.parameters.magFilter);51             // TODO: mipmap not used yet.52 53             OpenGL.BindSampler(unit, 0);54         }55         /// <summary>56         /// texture‘s settings.57         /// </summary>58         /// <param name="unit">OpenGL.GL_TEXTURE0 etc.</param>59         /// <param name="target"></param>60         public override void Bind(uint unit, BindTextureTarget target)61         {62             if (!this.initialized) { this.Initialize(unit, target); }63 64             OpenGL.BindSampler(unit, this.Id);65         }66     }
Sampler
+BIT祝威+悄悄在此留下版了个权的信息说:

当然也可以不用这个OpenGL的Sampler对象,直接用glTexParameteri()等指令。这就像是一个假的Sampler对象在工作。

技术分享
 1     /// <summary> 2     /// texture‘s settings. 3     /// </summary> 4     public class FakeSampler : SamplerBase 5     { 6  7         /// <summary> 8         /// texture‘s settings. 9         /// </summary>10         /// <param name="parameters"></param>11         /// <param name="mipmapFiltering"></param>12         public FakeSampler(SamplerParameters parameters, MipmapFilter mipmapFiltering)13             : base(parameters, mipmapFiltering)14         {15         }16 17         /// <summary>18         /// texture‘s settings.19         /// </summary>20         /// <param name="unit">OpenGL.GL_TEXTURE0 etc.</param>21         /// <param name="target"></param>22         public override void Bind(uint unit, BindTextureTarget target)23         {24             /* Clamping to edges is important to prevent artifacts when scaling */25             OpenGL.TexParameteri((uint)target, OpenGL.GL_TEXTURE_WRAP_R, (int)this.parameters.wrapR);26             OpenGL.TexParameteri((uint)target, OpenGL.GL_TEXTURE_WRAP_S, (int)this.parameters.wrapS);27             OpenGL.TexParameteri((uint)target, OpenGL.GL_TEXTURE_WRAP_T, (int)this.parameters.wrapT);28             /* Linear filtering usually looks best for text */29             OpenGL.TexParameteri((uint)target, OpenGL.GL_TEXTURE_MIN_FILTER, (int)this.parameters.minFilter);30             OpenGL.TexParameteri((uint)target, OpenGL.GL_TEXTURE_MAG_FILTER, (int)this.parameters.magFilter);31             // TODO: mipmap filter not working yet.32 33         }34     }
FakeSampler

当然,有的时候根本不需要指定任何滤波选项。这可以用一个空的Sampler类型实现。

 1     /// <summary> 2     /// do nothing about sampling in building texture. 3     /// </summary> 4     public class NullSampler : SamplerBase 5     { 6         /// <summary> 7         /// do nothing about sampling in building texture. 8         /// </summary> 9         public NullSampler() : base(null, MipmapFilter.LinearMipmapLinear) { }10 11         /// <summary>12         /// do nothing.13         /// </summary>14         /// <param name="unit">OpenGL.GL_TEXTURE0 etc.</param>15         /// <param name="target"></param>16         public override void Bind(uint unit, BindTextureTarget target)17         {18             // nothing to do.19         }20     }

 

+BIT祝威+悄悄在此留下版了个权的信息说:

ImageFiller

填充数据就是用 glTexImage2D() 、 glTexStorage2D() 等指令设置Texture的内容。ImageFiller就是封装这一操作的。

 1     /// <summary> 2     /// build texture‘s content. 3     /// </summary> 4     public abstract class ImageFiller 5     { 6  7         /// <summary> 8         /// build texture‘s content. 9         /// </summary>10         /// <param name="target"></param>11         public abstract void Fill(BindTextureTarget target);12     }

对于常见的以 System.Drawing.Bitmap 为数据源填充Texture的情形,可以用下面的BitmapFiller。它可以作为1D/2D的Texture对象的填充器。

技术分享
 1     /// <summary> 2     /// build texture‘s content with Bitmap. 3     /// </summary> 4     public class BitmapFiller : ImageFiller 5     { 6         private System.Drawing.Bitmap bitmap; 7         private int level; 8         private uint internalformat; 9         private int border;10         private uint format;11         private uint type;12 13         /// <summary>14         /// build texture‘s content with Bitmap.15         /// </summary>16         /// <param name="bitmap"></param>17         /// <param name="level">0</param>18         /// <param name="internalformat">OpenGL.GL_RGBA etc.</param>19         /// <param name="border">0</param>20         /// <param name="format">OpenGL.GL_BGRA etc.</param>21         /// <param name="type">OpenGL.GL_UNSIGNED_BYTE etc.</param>22         public BitmapFiller(System.Drawing.Bitmap bitmap,23             int level, uint internalformat, int border, uint format, uint type)24         {25             this.bitmap = bitmap;26             this.level = level;27             this.internalformat = internalformat;28             this.border = border;29             this.format = format;30             this.type = type;31         }32 33         /// <summary>34         /// build texture‘s content with Bitmap.35         /// </summary>36         /// <param name="target"></param>37         public override void Fill(BindTextureTarget target)38         {39             // generate texture.40             //  Lock the image bits (so that we can pass them to OGL).41             BitmapData bitmapData = http://www.mamicode.com/bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height),42                 ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);43             if (target == BindTextureTarget.Texture1D)44             {45                 OpenGL.TexImage1D((uint)target, 0, this.internalformat, bitmap.Width, 0, this.format, this.type, bitmapData.Scan0);46             }47             else if (target == BindTextureTarget.Texture2D)48             {49                 OpenGL.TexImage2D((uint)target, 0, this.internalformat, bitmap.Width, bitmap.Height, 0, this.format, this.type, bitmapData.Scan0);50             }51             else52             { throw new NotImplementedException(); }53 54             //  Unlock the image.55             bitmap.UnlockBits(bitmapData);56         }57     }
BitmapFiller

还有一个常见的填充方式 glTexStorage2D() ,可以用下面的TexStorageImageFiller实现。

技术分享
 1     /// <summary> 2     ///  3     /// </summary> 4     public class TexStorageImageFiller : ImageFiller 5     { 6         private int levels; 7         private uint internalFormat; 8         private int width; 9         private int height;10 11         /// <summary>12         /// 13         /// </summary>14         /// <param name="levels"></param>15         /// <param name="internalFormat"></param>16         /// <param name="width"></param>17         /// <param name="height"></param>18         public TexStorageImageFiller(int levels, uint internalFormat, int width, int height)19         {20             // TODO: Complete member initialization21             this.levels = levels;22             this.internalFormat = internalFormat;23             this.width = width;24             this.height = height;25         }26 27         /// <summary>28         /// 29         /// </summary>30         /// <param name="target"></param>31         public override void Fill(BindTextureTarget target)32         {33             switch (target)34             {35                 case BindTextureTarget.Unknown:36                     break;37                 case BindTextureTarget.Texture1D:38                     break;39                 case BindTextureTarget.Texture2D:40                     OpenGL.TexStorage2D(TexStorage2DTarget.Texture2D, levels, internalFormat, width, height);41                     break;42                 case BindTextureTarget.Texture3D:43                     break;44                 case BindTextureTarget.TextureCubeMap:45                     break;46                 case BindTextureTarget.TextureBuffer:47                     break;48                 default:49                     break;50             }51         }52     }
TexStorageImageFiller

 

+BIT祝威+悄悄在此留下版了个权的信息说:

创建Texture

用封装的类型创建Texture的方式如下:

 1 Texture Create(Bitmap bitmap) 2 { 3     var texture = new Texture(BindTextureTarget.Texture2D, 4         new BitmapFiller(bitmap, 0, OpenGL.GL_RGBA32F, 0, OpenGL.GL_BGRA, OpenGL.GL_UNSIGNED_BYTE), 5         new SamplerParameters( 6             TextureWrapping.ClampToEdge, 7             TextureWrapping.ClampToEdge, 8             TextureWrapping.ClampToEdge, 9             TextureFilter.Linear,10             TextureFilter.Linear));11     texture.Initialize();12     13     return texture;14 }

使用Texture

Texutre.Id就是用 glGenTextures() 获得的id。Texture中记录了此Texture的ActiveTexture、Target等属性。配合CSharpGL中的 samplerValue ,我们有:

 1         /// <summary> 2         /// get <see cref="samplerValue"/> from this texture. 3         /// </summary> 4         /// <param name="texture"></param> 5         /// <returns></returns> 6         public static samplerValue ToSamplerValue(this Texture texture) 7         { 8             return new samplerValue( 9                 texture.Target,10                 texture.Id,11                 texture.ActiveTexture);12         }

这就可以用到设置shader中需要的Texture上:

this.SetUniform("tex", texture.ToSamplerValue());

 

封装Framebuffer

过程式的Framebuffer

首先观察一下平时是如何创建和使用Framebuffer对象的。

创建Framebuffer

为关注重点,这里直接传入Texture的Id。

 1 uint Create(int width, int height, uint textureId) 2 { 3     // create framebuffer. 4     var frameBufferId = new uint[1]; 5     glGenFramebuffers(1, frameBufferId); 6     glBindFramebuffer(OpenGL.GL_FRAMEBUFFER, frameBufferId); 7      8     // attach texture as a color buffer. 9     glFramebufferTexture2D(OpenGL.GL_FRAMEBUFFER, OpenGL.GL_COLOR_ATTACHMENT0, OpenGL.GL_TEXTURE_2D, textureId, 0);10     11     // create a depth buffer.12     var renderbufferId = new uint[1];13     glGenRenderbuffers(1, renderbufferId);14     glBindRenderbuffer(OpenGL.GL_RENDERBUFFER, renderbufferId[0]);15     glRenderbufferStorage(OpenGL.GL_RENDERBUFFER, OpenGL.GL_DEPTH_COMPONENT, width, height);16 17     // attach depth buffer.18     glFramebufferRenderbuffer(OpenGL.GL_RENDERBUFFER, OpenGL.GL_DEPTH_ATTACHMENT, OpenGL.GL_RENDERBUFFER, renderbufferId);19     20     glBindFramebuffer(OpenGL.GL_RENDERBUFFER, 0);21     22     return frameBufferId;23 }

使用Framebuffer

使用方式与Texture类似,只要绑定就可以了。

    glBindFramebuffer(OpenGL.GL_FRAMEBUFFER, frameBufferId);

用完再解绑。

    glBindFramebuffer(OpenGL.GL_FRAMEBUFFER, 0);

封装的Framebuffer

Framebuffer就是一个盒子,单独创建一个Framebuffer基本上是没什么用的。必须Attach一些colorbuffer/depthbuffer/texture才能发挥作用。

一个Framebuffer能够绑定多个texture和colorbuffer,只能绑定一个depthbuffer。

Renderbuffer

colorbuffer和depthbuffer都属于Renderbuffer的一种,其创建方式相同,只不过有一个标识其为colorbuffer还是depthbuffer的标志不同。

创建Renderbuffer很简单。

 1     /// <summary> 2     /// Create, update, use and delete a renderbuffer object. 3     /// </summary> 4     public partial class Renderbuffer 5     { 6         uint[] renderbuffer = new uint[1]; 7         /// <summary> 8         /// Framebuffer Id. 9         /// </summary>10         public uint Id { get { return renderbuffer[0]; } }11 12         /// <summary>13         /// Create, update, use and delete a renderbuffer object.14         /// </summary>15         /// <param name="width"></param>16         /// <param name="height"></param>17         /// <param name="internalformat">GL_DEPTH_COMPONENT, GL_RGBA etc.</param>18         /// <param name="bufferType"></param>19         public Renderbuffer(int width, int height, uint internalformat, RenderbufferType bufferType)20         {21             this.Width = width;22             this.Height = height;23             this.BufferType = bufferType;24 25             glGenRenderbuffers(1, renderbuffer);26             glBindRenderbuffer(OpenGL.GL_RENDERBUFFER, renderbuffer[0]);27             glRenderbufferStorage(OpenGL.GL_RENDERBUFFER,28                 internalformat, width, height);29         }30 31         public int Width { get; set; }32         public int Height { get; set; }33         public RenderbufferType BufferType { get; private set; }34     }35 36     public enum RenderbufferType37     {38         DepthBuffer,39         ColorBuffer,40     }
+BIT祝威+悄悄在此留下版了个权的信息说:

创建Framebuffer

创建Framebuffer也很简单,实际上只是调用了一个 glGenFramebuffers(1, frameBuffer); 命令。

 1     /// <summary> 2     /// Create, update, use and delete a framebuffer object. 3     /// </summary> 4     public partial class Framebuffer : IDisposable 5     { 6         uint[] frameBuffer = new uint[1]; 7         /// <summary> 8         /// Framebuffer Id. 9         /// </summary>10         public uint Id { get { return frameBuffer[0]; } }11 12         /// <summary>13         /// Create an empty framebuffer object.14         /// </summary>15         public Framebuffer()16         {17             glGenFramebuffers(1, frameBuffer);18         }19     }20 21     /// <summary>22     /// 23     /// </summary>24     public enum FramebufferTarget : uint25     {26         /// <summary>27         /// used to draw(write only) something.28         /// </summary>29         DrawFramebuffer = OpenGL.GL_DRAW_FRAMEBUFFER,30         /// <summary>31         /// used to read from(read only).32         /// </summary>33         ReadFramebuffer = OpenGL.GL_READ_FRAMEBUFFER,34         /// <summary>35         /// both read/write.36         /// </summary>37         Framebuffer = OpenGL.GL_FRAMEBUFFER,38     }

使用Framebuffer

使用方式与Texture类似,只要绑定就可以了。

    framebuffer.Bind();// glBindFramebuffer(OpenGL.GL_FRAMEBUFFER, framebufferId);

用完再解绑。

    framebuffer.Unbind();// glBindFramebuffer(OpenGL.GL_FRAMEBUFFER, 0);

这与未封装的使用方式没什么区别。

总结

基于目前我对Texture和Framebuffer的了解,现在只能封装到这个地步。

 

+BIT祝威+悄悄在此留下版了个权的信息说:

CSharpGL(29)初步封装Texture和Framebuffer