文章出處

+BIT祝威+悄悄在此留下版了個權的信息說:

CSharpGL(22)實現順序無關的半透明渲染(Order-Independent-Transparency)

在 GL.Enable(GL_BLEND); 后渲染半透明物體時,由于頂點被渲染的順序固定,渲染出來的結果往往很奇怪。紅寶書里提到一個OIT(Order-Independent-Transparency)的渲染方法,很好的解決了這個問題。這個功能太有用了。于是我把這個方法加入CSharpGL中。

+BIT祝威+悄悄在此留下版了個權的信息說:

效果圖

如下圖所示,左邊是常見的blend方法,右邊是OIT渲染的結果。可以看到左邊的渲染結果有些詭異,右邊的就正常了。

網絡允許的話可以看一下視頻,更直觀。

或者也可以看紅寶書里的例子:左邊是常見的blend方法,右邊是OIT渲染的結果。

 

+BIT祝威+悄悄在此留下版了個權的信息說:

下載

CSharpGL已在GitHub開源,歡迎對OpenGL有興趣的同學加入(https://github.com/bitzhuwei/CSharpGL

 

+BIT祝威+悄悄在此留下版了個權的信息說:

實現原理

+BIT祝威+悄悄在此留下版了個權的信息說:

源頭

為什么通常的blend方式會有問題?因為blend的結果是與source、dest兩個顏色的出現順序相關的。就是說blend(blend(ColorA, ColorB), ColorC)≠blend(ColorA,blend(ColorB, ColorC))。

那么很顯然的一個想法是,分兩遍渲染,第一遍:把這個位置上的所有Color都存到一個鏈表里,第二遍:根據每個Color的深度值進行排序,然后進行blend操作。這就解決了blend順序的問題。

+BIT祝威+悄悄在此留下版了個權的信息說:

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那樣用于給模型貼圖,而是用作記錄頭結點的信息。★

+BIT祝威+悄悄在此留下版了個權的信息說:

初始化

初始化工作主要包含這幾項:創建和清空頭結點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         }
DoInitialize
+BIT祝威+悄悄在此留下版了個權的信息說:

2遍渲染

渲染過程主要有3步:重置鏈表、頭結點、計數器等;1遍渲染填充鏈表;2遍渲染排序+blend。

 1         protected override void DoRender(RenderEventArgs arg)
 2         {
 3             this.depthTestSwitch.On();
 4             this.cullFaceSwitch.On();
 5 
 6             // Reset atomic counter
 7             GL.GetDelegateFor<GL.glBindBufferBase>()(GL.GL_ATOMIC_COUNTER_BUFFER, 0, atomic_counter_buffer[0]);
 8             IntPtr data = GL.MapBuffer(BufferTarget.AtomicCounterBuffer, MapBufferAccess.WriteOnly);
 9             unsafe
10             {
11                 var array = (uint*)data.ToPointer();
12                 array[0] = 0;
13             }
14             GL.UnmapBuffer(BufferTarget.AtomicCounterBuffer);
15             GL.GetDelegateFor<GL.glBindBufferBase>()(GL.GL_ATOMIC_COUNTER_BUFFER, 0, 0);
16 
17             // Clear head-pointer image
18             GL.BindBuffer(BufferTarget.PixelUnpackBuffer, head_pointer_clear_buffer[0]);
19             GL.BindTexture(GL.GL_TEXTURE_2D, head_pointer_texture[0]);
20             GL.TexSubImage2D(TexSubImage2DTarget.Texture2D, 0, 0, 0, arg.CanvasRect.Width, arg.CanvasRect.Height, TexSubImage2DFormats.RedInteger, TexSubImage2DType.UnsignedByte, IntPtr.Zero);
21             GL.BindTexture(GL.GL_TEXTURE_2D, 0);
22             GL.BindBuffer(BufferTarget.PixelUnpackBuffer, 0);
23             //
24 
25             // Bind head-pointer image for read-write
26             GL.GetDelegateFor<GL.glBindImageTexture>()(0, head_pointer_texture[0], 0, false, 0, GL.GL_READ_WRITE, GL.GL_R32UI);
27 
28             // Bind linked-list buffer for write
29             GL.GetDelegateFor<GL.glBindImageTexture>()(1, linked_list_texture[0], 0, false, 0, GL.GL_WRITE_ONLY, GL.GL_RGBA32UI);
30 
31             mat4 model = mat4.identity();
32             mat4 view = arg.Camera.GetViewMat4();
33             mat4 projection = arg.Camera.GetProjectionMat4();
34             this.buildListsRenderer.SetUniformValue("model_matrix", model);
35             this.buildListsRenderer.SetUniformValue("view_matrix", view);
36             this.buildListsRenderer.SetUniformValue("projection_matrix", projection);
37             this.resolve_lists.SetUniformValue("model_matrix", model);
38             this.resolve_lists.SetUniformValue("view_matrix", view);
39             this.resolve_lists.SetUniformValue("projection_matrix", projection);
40 
41             // first pass
42             this.buildListsRenderer.Render(arg);
43             // second pass
44             this.resolve_lists.Render(arg);
45 
46             GL.GetDelegateFor<GL.glBindImageTexture>()(1, 0, 0, false, 0, GL.GL_WRITE_ONLY, GL.GL_RGBA32UI);
47             GL.GetDelegateFor<GL.glBindImageTexture>()(0, 0, 0, false, 0, GL.GL_READ_WRITE, GL.GL_R32UI);
48 
49             this.cullFaceSwitch.Off();
50             this.depthTestSwitch.Off();
51         }
protected override void DoRender(RenderEventArgs arg)
+BIT祝威+悄悄在此留下版了個權的信息說:

Shader:填充鏈表

1遍渲染時,用一個fragment shader來填充鏈表:

 1 #version 420 core
 2 
 3 layout (early_fragment_tests) in;
 4 
 5 layout (binding = 0, r32ui) uniform uimage2D head_pointer_image;
 6 layout (binding = 1, rgba32ui) uniform writeonly uimageBuffer list_buffer;
 7 
 8 layout (binding = 0, offset = 0) uniform atomic_uint list_counter;
 9 
10 layout (location = 0) out vec4 color;
11 
12 in vec4 surface_color;
13 
14 uniform vec3 light_position = vec3(40.0, 20.0, 100.0);
15 
16 void main(void)
17 {
18     uint index;
19     uint old_head;
20     uvec4 item;
21 
22     index = atomicCounterIncrement(list_counter);
23 
24     old_head = imageAtomicExchange(head_pointer_image, ivec2(gl_FragCoord.xy), uint(index));
25 
26     item.x = old_head;
27     item.y = packUnorm4x8(surface_color);
28     item.z = floatBitsToUint(gl_FragCoord.z);
29     item.w = 255 / 4;
30 
31     imageStore(list_buffer, int(index), item);
32 
33     color = surface_color;
34 }

 

+BIT祝威+悄悄在此留下版了個權的信息說:

Shader:排序、blend

2遍渲染時,用另一個fragment shader來排序和blend。

 1 #version 420 core
 2 
 3 /*
 4  * OpenGL Programming Guide - Order Independent Transparency Example
 5  *
 6  * This is the resolve shader for order independent transparency.
 7  */
 8 
 9 // The per-pixel image containing the head pointers
10 layout (binding = 0, r32ui) uniform uimage2D head_pointer_image;
11 // Buffer containing linked lists of fragments
12 layout (binding = 1, rgba32ui) uniform uimageBuffer list_buffer;
13 
14 // This is the output color
15 layout (location = 0) out vec4 color;
16 
17 // This is the maximum number of overlapping fragments allowed
18 #define MAX_FRAGMENTS 40
19 
20 // Temporary array used for sorting fragments
21 uvec4 fragment_list[MAX_FRAGMENTS];
22 
23 void main(void)
24 {
25     uint current_index;
26     uint fragment_count = 0;
27 
28     current_index = imageLoad(head_pointer_image, ivec2(gl_FragCoord).xy).x;
29 
30     while (current_index != 0 && fragment_count < MAX_FRAGMENTS)
31     {
32         uvec4 fragment = imageLoad(list_buffer, int(current_index));
33         fragment_list[fragment_count] = fragment;
34         current_index = fragment.x;
35         fragment_count++;
36     }
37 
38     uint i, j;
39 
40     if (fragment_count > 1)
41     {
42 
43         for (i = 0; i < fragment_count - 1; i++)
44         {
45             for (j = i + 1; j < fragment_count; j++)
46             {
47                 uvec4 fragment1 = fragment_list[i];
48                 uvec4 fragment2 = fragment_list[j];
49 
50                 float depth1 = uintBitsToFloat(fragment1.z);
51                 float depth2 = uintBitsToFloat(fragment2.z);
52 
53                 if (depth1 < depth2)
54                 {
55                     fragment_list[i] = fragment2;
56                     fragment_list[j] = fragment1;
57                 }
58             }
59         }
60 
61     }
62 
63     vec4 final_color = vec4(0.0);
64 
65     for (i = 0; i < fragment_count; i++)
66     {
67         vec4 modulator = unpackUnorm4x8(fragment_list[i].y);
68 
69         final_color = mix(final_color, modulator, modulator.a + fragment_list[i].w / 255);
70     }
71 
72     color = final_color;
73     // color = vec4(float(fragment_count) / float(MAX_FRAGMENTS));
74 }
resolve_lists.frag
+BIT祝威+悄悄在此留下版了個權的信息說:

總結

經過這個OIT問題的練習,我忽然明白了一些modern opengl的設計思想。

在modern opengl看來,Texture雖然名為Texture,告訴我們它是用于給模型貼圖的,但是,Texture實際上可以用作各種各樣的事(例如OIT里用作記錄各個頭結點)。

一個VBO不僅僅可以用于存儲頂點位置、法線、顏色等信息,也可以用作各種各樣的事(例如OIT里用作存儲W*H個鏈表,這讓我想起了我的(小型單文件NoSQL數據庫SharpFileDB)里的文件鏈表,兩者何其相似)。

為什么會這樣?

因為modern opengl是用GLSL shader來實施渲染的。Shader是一段程序,程序的創造力是無窮無盡的,你可以以任何方式使用opengl提供的資源(Texture,VBO等等)。唯一固定不變的,就是modern opengl的渲染管道(pipeline,"管道"太玄幻了,其實就是渲染過程的意思)。

記住pipeline的工作流程,認識opengl的各種資源,發揮想象力。

2遍渲染的成功試驗,從側面印證了上述推斷,也說明了opengl只是負責渲染,至于渲染之后是不是畫到畫布或者其他什么地方,都是可以控制的。

甚至,“渲染”的目的本就不必是為了畫圖。這就是compute shader的由來了吧。

 

原CSharpGL的其他功能(3ds解析器、TTF2Bmp、CSSL等),我將逐步加入新CSharpGL。

歡迎對OpenGL有興趣的同學關注(https://github.com/bitzhuwei/CSharpGL

+BIT祝威+悄悄在此留下版了個權的信息說:

文章列表


不含病毒。www.avast.com
arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()