CSharpGL(30)用条件渲染(Conditional Rendering)来提升OpenGL的渲染效率


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         /// 
 2         /// 
 3         /// 
 4         /// 
 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     /// 
  2     /// demostrates how to perform conditional rendering.
  3     /// 
  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> coupleList = new List>();
 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(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, 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 demo. 

条件渲染效果

下面让蓝色的墙遮挡住彩色点云。

总结

条件渲染(Conditional Rendering)是一项非常厉害的技术。当要渲染一个数据量很大的模型时,用条件渲染技术能够显著提升渲染效率,因为这个技术能够少画一些被遮挡的内容。