CSharpGL(22)实现顺序无关的半透明渲染(Order-Independent-Transparency)
在 GL.Enable(GL_BLEND); 后渲染半透明物体时,由于顶点被渲染的顺序固定,渲染出来的结果往往很奇怪。红宝书里提到一个OIT(Order-Independent-Transparency)的渲染方法,很好的解决了这个问题。这个功能太有用了。于是我把这个方法加入CSharpGL中。
如下图所示,左边是常见的blend方法,右边是OIT渲染的结果。可以看到左边的渲染结果有些诡异,右边的就正常了。
网络允许的话可以看一下视频,更直观。
或者也可以看红宝书里的例子:左边是常见的blend方法,右边是OIT渲染的结果。
下载
CSharpGL已在GitHub开源,欢迎对OpenGL有兴趣的同学加入(https://github.com/bitzhuwei/CSharpGL)
实现原理
源头
为什么通常的blend方式会有问题?因为blend的结果是与source、dest两个颜色的出现顺序相关的。就是说blend(blend(ColorA, ColorB), ColorC)≠blend(ColorA,blend(ColorB, ColorC))。
那么很显然的一个想法是,分两遍渲染,第一遍:把这个位置上的所有Color都存到一个链表里,第二遍:根据每个Color的深度值进行排序,然后进行blend操作。这就解决了blend顺序的问题。
W*H个链表
显然,为了对宽度、高度分别为Width、Height的窗口实施OIT渲染,必须为此窗口上的每个像素分别设置一个链表,用于存储投影到此像素上的各个Color。这就是W*H个链表的由来。
当然了,这个链表要由GLSL shader来操作 。shader本身似乎没有操作链表的功能。那么就用一个大大的VBO来代替。这个VBO存储了所有的W*H个链表。
你可以想象,在第一遍渲染时,这个VBO的第二个位置上可能是第一个像素的第二个Color,也可能是第二个像素的第一个Color。这就意味着我们还需要一个数组来存放W*H个链表的头结点。这个数组我们用一个2DTexture实现,其大小正好等于Width*Height就可以了。
★这个Texture不像一般的Texture那样用于给模型贴图,而是用作记录头结点的信息。★
初始化
初始化工作主要包含这几项:创建和清空头结点Texture、链表VBO。
1 protected override void DoInitialize() 2 { 3 // Create head pointer texture 4 GL.GetDelegateFor<GL.glActiveTexture>()(GL.GL_TEXTURE0); 5 GL.GenTextures(1, head_pointer_texture); 6 GL.BindTexture(GL.GL_TEXTURE_2D, head_pointer_texture[0]); 7 GL.TexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MIN_FILTER, (int)GL.GL_NEAREST); 8 GL.TexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MAG_FILTER, (int)GL.GL_NEAREST); 9 GL.TexImage2D(GL.GL_TEXTURE_2D, 0, GL.GL_R32UI, MAX_FRAMEBUFFER_WIDTH, MAX_FRAMEBUFFER_HEIGHT, 0, GL.GL_RED_INTEGER, GL.GL_UNSIGNED_INT, IntPtr.Zero); 10 GL.BindTexture(GL.GL_TEXTURE_2D, 0); 11 12 GL.GetDelegateFor<GL.glBindImageTexture>()(0, head_pointer_texture[0], 0, true, 0, GL.GL_READ_WRITE, GL.GL_R32UI); 13 14 // Create buffer for clearing the head pointer texture 15 GL.GetDelegateFor<GL.glGenBuffers>()(1, head_pointer_clear_buffer); 16 GL.BindBuffer(BufferTarget.PixelUnpackBuffer, head_pointer_clear_buffer[0]); 17 GL.GetDelegateFor<GL.glBufferData>()(GL.GL_PIXEL_UNPACK_BUFFER, MAX_FRAMEBUFFER_WIDTH * MAX_FRAMEBUFFER_HEIGHT * sizeof(uint), IntPtr.Zero, GL.GL_STATIC_DRAW); 18 IntPtr data = GL.MapBuffer(BufferTarget.PixelUnpackBuffer, MapBufferAccess.WriteOnly); 19 unsafe 20 { 21 var array = (uint*)data.ToPointer(); 22 for (int i = 0; i < MAX_FRAMEBUFFER_WIDTH * MAX_FRAMEBUFFER_HEIGHT; i++) 23 { 24 array[i] = 0; 25 } 26 } 27 GL.UnmapBuffer(BufferTarget.PixelUnpackBuffer); 28 GL.BindBuffer(BufferTarget.PixelUnpackBuffer, 0); 29 30 // Create the atomic counter buffer 31 GL.GetDelegateFor<GL.glGenBuffers>()(1, atomic_counter_buffer); 32 GL.BindBuffer(BufferTarget.AtomicCounterBuffer, atomic_counter_buffer[0]); 33 GL.GetDelegateFor<GL.glBufferData>()(GL.GL_ATOMIC_COUNTER_BUFFER, sizeof(uint), IntPtr.Zero, GL.GL_DYNAMIC_COPY); 34 GL.BindBuffer(BufferTarget.AtomicCounterBuffer, 0); 35 36 // Create the linked list storage buffer 37 GL.GetDelegateFor<GL.glGenBuffers>()(1, linked_list_buffer); 38 GL.BindBuffer(BufferTarget.TextureBuffer, linked_list_buffer[0]); 39 GL.GetDelegateFor<GL.glBufferData>()(GL.GL_TEXTURE_BUFFER, MAX_FRAMEBUFFER_WIDTH * MAX_FRAMEBUFFER_HEIGHT * 3 * Marshal.SizeOf(typeof(vec4)), IntPtr.Zero, GL.GL_DYNAMIC_COPY); 40 GL.BindBuffer(BufferTarget.TextureBuffer, 0); 41 42 // Bind it to a texture (for use as a TBO) 43 GL.GenTextures(1, linked_list_texture); 44 GL.BindTexture(GL.GL_TEXTURE_BUFFER, linked_list_texture[0]); 45 GL.GetDelegateFor<GL.glTexBuffer>()(GL.GL_TEXTURE_BUFFER, GL.GL_RGBA32UI, linked_list_buffer[0]); 46 GL.BindTexture(GL.GL_TEXTURE_BUFFER, 0); 47 48 GL.GetDelegateFor<GL.glBindImageTexture>()(1, linked_list_texture[0], 0, false, 0, GL.GL_WRITE_ONLY, GL.GL_RGBA32UI); 49 50 GL.ClearDepth(1.0f); 51 }