首页 > 代码库 > CSharpGL(29)初步封装Texture和Framebuffer
CSharpGL(29)初步封装Texture和Framebuffer
CSharpGL(29)初步封装Texture和Framebuffer
Texture和Framebuffer
Texture和Framebuffer是OpenGL进行3D渲染高级效果必不可少的利器。有了Texture和Framebuffer就可以实现体渲染(Volume Rendering)等效果。现在到了对Texture和Framebuffer的创建、修改、使用进行封装的时候。
下载
CSharpGL已在GitHub开源,欢迎对OpenGL有兴趣的同学加入(https://github.com/bitzhuwei/CSharpGL)
封装Texture
过程式的Texture
首先观察一下平时是如何创建和使用Texture对象的。
创建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 }
使用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 }
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 }
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 }
当然也可以不用这个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 }
当然,有的时候根本不需要指定任何滤波选项。这可以用一个空的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 }
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 }
还有一个常见的填充方式 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 }
创建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 }
创建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的了解,现在只能封装到这个地步。
CSharpGL(29)初步封装Texture和Framebuffer