CSharpGL(29)初步封装Texture和Framebuffer


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

CSharpGL(29)初步封装Texture和Framebuffer

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

Texture和Framebuffer

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

volume rendering

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

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

 1     /// 
 2     /// texture's settings.
 3     /// 
 4     public partial class Sampler : SamplerBase, IDisposable
 5     {
 6         /// 
 7         /// sampler's Id.
 8         /// 
 9         public uint Id { get; private set; }
10 
11         /// 
12         /// texture's settings.
13         /// 
14         /// 
15         /// 
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         /// 
26         /// 
27         /// 
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         /// 
56         /// texture's settings.
57         /// 
58         /// OpenGL.GL_TEXTURE0 etc.
59         /// 
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     /// 
 2     /// texture's settings.
 3     /// 
 4     public class FakeSampler : SamplerBase
 5     {
 6 
 7         /// 
 8         /// texture's settings.
 9         /// 
10         /// 
11         /// 
12         public FakeSampler(SamplerParameters parameters, MipmapFilter mipmapFiltering)
13             : base(parameters, mipmapFiltering)
14         {
15         }
16 
17         /// 
18         /// texture's settings.
19         /// 
20         /// OpenGL.GL_TEXTURE0 etc.
21         /// 
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     /// 
 2     /// do nothing about sampling in building texture.
 3     /// 
 4     public class NullSampler : SamplerBase
 5     {
 6         /// 
 7         /// do nothing about sampling in building texture.
 8         /// 
 9         public NullSampler() : base(null, MipmapFilter.LinearMipmapLinear) { }
10 
11         /// 
12         /// do nothing.
13         /// 
14         /// OpenGL.GL_TEXTURE0 etc.
15         /// 
16         public override void Bind(uint unit, BindTextureTarget target)
17         {
18             // nothing to do.
19         }
20     }
+BIT祝威+悄悄在此留下版了个权的信息说:

ImageFiller

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

 1     /// 
 2     /// build texture's content.
 3     /// 
 4     public abstract class ImageFiller
 5     {
 6 
 7         /// 
 8         /// build texture's content.
 9         /// 
10         /// 
11         public abstract void Fill(BindTextureTarget target);
12     }

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

 1     /// 
 2     /// build texture's content with Bitmap.
 3     /// 
 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         /// 
14         /// build texture's content with Bitmap.
15         /// 
16         /// 
17         /// 0
18         /// OpenGL.GL_RGBA etc.
19         /// 0
20         /// OpenGL.GL_BGRA etc.
21         /// OpenGL.GL_UNSIGNED_BYTE etc.
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         /// 
34         /// build texture's content with Bitmap.
35         /// 
36         /// 
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 = 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             else
52             { throw new NotImplementedException(); }
53 
54             //  Unlock the image.
55             bitmap.UnlockBits(bitmapData);
56         }
57     }
BitmapFiller

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

 1     /// 
 2     /// 
 3     /// 
 4     public class TexStorageImageFiller : ImageFiller
 5     {
 6         private int levels;
 7         private uint internalFormat;
 8         private int width;
 9         private int height;
10 
11         /// 
12         /// 
13         /// 
14         /// 
15         /// 
16         /// 
17         /// 
18         public TexStorageImageFiller(int levels, uint internalFormat, int width, int height)
19         {
20             // TODO: Complete member initialization
21             this.levels = levels;
22             this.internalFormat = internalFormat;
23             this.width = width;
24             this.height = height;
25         }
26 
27         /// 
28         /// 
29         /// 
30         /// 
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         /// 
 2         /// get  from this texture.
 3         /// 
 4         /// 
 5         /// 
 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     /// 
 2     /// Create, update, use and delete a renderbuffer object.
 3     /// 
 4     public partial class Renderbuffer
 5     {
 6         uint[] renderbuffer = new uint[1];
 7         /// 
 8         /// Framebuffer Id.
 9         /// 
10         public uint Id { get { return renderbuffer[0]; } }
11 
12         /// 
13         /// Create, update, use and delete a renderbuffer object.
14         /// 
15         /// 
16         /// 
17         /// GL_DEPTH_COMPONENT, GL_RGBA etc.
18         /// 
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 RenderbufferType
37     {
38         DepthBuffer,
39         ColorBuffer,
40     }
+BIT祝威+悄悄在此留下版了个权的信息说:

创建Framebuffer

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

 1     /// 
 2     /// Create, update, use and delete a framebuffer object.
 3     /// 
 4     public partial class Framebuffer : IDisposable
 5     {
 6         uint[] frameBuffer = new uint[1];
 7         /// 
 8         /// Framebuffer Id.
 9         /// 
10         public uint Id { get { return frameBuffer[0]; } }
11 
12         /// 
13         /// Create an empty framebuffer object.
14         /// 
15         public Framebuffer()
16         {
17             glGenFramebuffers(1, frameBuffer);
18         }
19     }
20 
21     /// 
22     /// 
23     /// 
24     public enum FramebufferTarget : uint
25     {
26         /// 
27         /// used to draw(write only) something.
28         /// 
29         DrawFramebuffer = OpenGL.GL_DRAW_FRAMEBUFFER,
30         /// 
31         /// used to read from(read only).
32         /// 
33         ReadFramebuffer = OpenGL.GL_READ_FRAMEBUFFER,
34         /// 
35         /// both read/write.
36         /// 
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祝威+悄悄在此留下版了个权的信息说: