CSharpGL(30)用條件渲染(Conditional Rendering)來提升OpenGL的渲染效率
當場景中有比較復雜的模型時,條件渲染能夠加速對復雜模型的渲染。
條件渲染(Conditional Rendering)
當我們能夠斷定一個模型被其他模型擋住(因此不會被Camera看到)時,我們就可以跳過對此模型的渲染。這就是條件渲染的根本。
那么如何去判斷?方法就是用一個簡單的包圍盒(比如一個立方體)去渲染一下,看看fragment是不是有變化(即包圍盒上的某些部分通過了depth test,最終渲染到Framebuffer上了)。如果沒有任何一個fragment發生改變,就說明這個包圍盒是被擋住了,那么被包圍起來的模型也必然是被擋住了。
下載
CSharpGL已在GitHub開源,歡迎對OpenGL有興趣的同學加入(https://github.com/bitzhuwei/CSharpGL)
原理
本篇需要用到2個知識點。
遮面查詢(Occlusion Query)
這里OpenGL提供了一個Query Object。類似Buffer Object,Vertex Array Object等,也是通過glGen*等方式使用的。
Query Object的作用就是記錄一個包圍盒是否改變了某些fragment。
如下代碼所示,在通常的渲染前后用glBeginQuery和glEndQuery包圍起來,Query就會記錄是否有fragment被改變了。
1 glBeginQuery(GL_SAMPLES_PASSED, queryId); 2 glDrawArrays(GL_TRIANGLES, 0, 3); 3 glEndQuery(GL_SAMPLES_PASSED);
之后用下述方式即可獲知是否有fragment被改變了。只要SampleRendered()返回值為false,那么這個模型就不用渲染了。
1 /// <summary> 2 /// 3 /// </summary> 4 /// <returns></returns> 5 public bool SampleRendered() 6 { 7 var result = new int[1]; 8 int count = 1000; 9 while (result[0] == 0 && count-- > 0) 10 { 11 glGetQueryObjectiv(this.Id, OpenGL.GL_QUERY_RESULT_AVAILABLE, result); 12 } 13 14 if (result[0] != 0) 15 { 16 glGetQueryObjectiv(this.Id, OpenGL.GL_QUERY_RESULT, result); 17 } 18 else 19 { 20 result[0] = 1; 21 } 22 23 return result[0] != 0; 24 }
條件渲染(Conditional Rendering)
上述Query對象使用時的一個缺點是,CPU必須用循環等待GPU的Query結果。這就拖延了后續渲染步驟,降低了FPS。
為避免CPU循環等待,OpenGL提供了下面2個指令,他們的作用就是用GPU代替了CPU循環的功能。
1 glBeginConditionalRender(uint id, uint mode); 2 glEndConditionalRender();
其使用方式也像Query對象一樣,把通常的渲染指令包圍起來即可。
1 glBeginConditionalRender(queryId, GL_QUERY_WAIT); 2 glDrawArrays(GL_TRIANGLE_FAN, 0, numVertices); 3 glEndConditionalRender();
示例
一個特別的3D模型
我們需要設計一個頂點多而又被一個簡單的模型遮擋住的模型。這個復雜模型就用點云表示,用于遮擋的模型就用一個簡單的Cube就可以了。
運用CSharpGL封裝的功能,很快就可以做出這個模型來。
1 /// <summary> 2 /// demostrates how to perform conditional rendering. 3 /// </summary> 4 internal class ConditionalRenderer : RendererBase 5 { 6 private const int xside = 5, yside = 5, zside = 5; 7 private const int pointCount = 10000; 8 private static readonly vec3 unitLengths = new vec3(1, 1, 1); 9 private const float scaleFactor = 1.0f; 10 11 private List<Tuple<CubeRenderer, RendererBase, Query>> coupleList = new List<Tuple<CubeRenderer, RendererBase, Query>>(); 12 private DepthMaskSwitch depthMaskSwitch = new DepthMaskSwitch(false); 13 private ColorMaskSwitch colorMaskSwitch = new ColorMaskSwitch(false, false, false, false); 14 15 private bool enableConditionalRendering = true; 16 17 public bool ConditionalRendering 18 { 19 get { return enableConditionalRendering; } 20 set { enableConditionalRendering = value; } 21 } 22 23 private bool renderBoundingBox = false; 24 25 public bool RenderBoundingBox 26 { 27 get { return renderBoundingBox; } 28 set { renderBoundingBox = value; } 29 } 30 31 private bool renderTargetModel = true; 32 33 public bool RenderTargetModel 34 { 35 get { return renderTargetModel; } 36 set { renderTargetModel = value; } 37 } 38 39 public static ConditionalRenderer Create() 40 { 41 var result = new ConditionalRenderer(); 42 { 43 var wallRenderer = CubeRenderer.Create(new Cube(new vec3(unitLengths.x * 2, unitLengths.y * 2, 0.1f) * new vec3(xside, yside, zside))); 44 wallRenderer.WorldPosition = new vec3(0, 0, 6); 45 var boxRenderer = CubeRenderer.Create(new Cube(new vec3(unitLengths.x * 2, unitLengths.y * 2, 0.1f) * new vec3(xside, yside, zside))); 46 boxRenderer.WorldPosition = new vec3(0, 0, 6); 47 var query = new Query(); 48 result.coupleList.Add(new Tuple<CubeRenderer, RendererBase, Query>(boxRenderer, wallRenderer, query)); 49 } 50 for (int x = 0; x < xside; x++) 51 { 52 for (int y = 0; y < yside; y++) 53 { 54 for (int z = 0; z < zside; z++) 55 { 56 var model = new RandomPointsModel(unitLengths, pointCount); 57 RandomPointsRenderer renderer = RandomPointsRenderer.Create(model); 58 renderer.PointColor = Color.FromArgb( 59 (int)((float)(x + 1) / (float)xside * 255), 60 (int)((float)(y + 1) / (float)yside * 255), 61 (int)((float)(z + 1) / (float)zside * 255)); 62 renderer.WorldPosition = 63 (new vec3(x, y, z) * unitLengths * scaleFactor) 64 - (new vec3(xside - 1, yside - 1, zside - 1) * unitLengths * scaleFactor * 0.5f); 65 var cubeRenderer = CubeRenderer.Create(new Cube(unitLengths)); 66 cubeRenderer.WorldPosition = renderer.WorldPosition; 67 var query = new Query(); 68 result.coupleList.Add(new Tuple<CubeRenderer, RendererBase, Query>(cubeRenderer, renderer, query)); 69 } 70 } 71 } 72 73 result.Lengths = new vec3(xside + 1, yside + 1, zside + 1) * unitLengths * scaleFactor; 74 75 return result; 76 } 77 78 private ConditionalRenderer() 79 { } 80 81 protected override void DoInitialize() 82 { 83 foreach (var item in this.coupleList) 84 { 85 item.Item1.Initialize(); 86 item.Item2.Initialize(); 87 item.Item3.Initialize(); 88 } 89 } 90 91 protected override void DoRender(RenderEventArgs arg) 92 { 93 if (this.ConditionalRendering) 94 { 95 this.depthMaskSwitch.On(); 96 this.colorMaskSwitch.On(); 97 foreach (var item in this.coupleList) 98 { 99 item.Item3.BeginQuery(QueryTarget.AnySamplesPassed); 100 item.Item1.Render(arg); 101 item.Item3.EndQuery(QueryTarget.AnySamplesPassed); 102 } 103 this.colorMaskSwitch.Off(); 104 this.depthMaskSwitch.Off(); 105 var result = new int[1]; 106 foreach (var item in this.coupleList) 107 { 108 item.Item3.BeginConditionalRender(ConditionalRenderMode.QueryByRegionWait); 109 //if (item.Item3.SampleRendered()) 110 { 111 if (this.renderTargetModel) { item.Item2.Render(arg); } 112 if (this.renderBoundingBox) { item.Item1.Render(arg); } 113 } 114 item.Item3.EndConditionalRender(); 115 } 116 } 117 else 118 { 119 foreach (var item in this.coupleList) 120 { 121 if (this.renderTargetModel) { item.Item2.Render(arg); } 122 if (this.renderBoundingBox) { item.Item1.Render(arg); } 123 } 124 } 125 } 126 }
條件渲染效果
下面讓藍色的墻遮擋住彩色點云。
總結
條件渲染(Conditional Rendering)是一項非常厲害的技術。當要渲染一個數據量很大的模型時,用條件渲染技術能夠顯著提升渲染效率,因為這個技術能夠少畫一些被遮擋的內容。
文章列表
留言列表