OpenGL:如何使用深度排序的顶点数组设计高效的渲染系统?
Posted
技术标签:
【中文标题】OpenGL:如何使用深度排序的顶点数组设计高效的渲染系统?【英文标题】:OpenGL: How to design efficient rendering system using vertex arrays with depth sorting? 【发布时间】:2011-05-24 10:06:21 【问题描述】:人们经常告诉我至少要使用顶点数组。但我认为这不是一个好主意,因为我使用 glPushMatrix()
和 glTranslatef/glRotatef
在 3d 世界中定位对象。
那么,我是否应该停止使用glPushMatrix()
并“手动”计算世界中旋转/移动的顶点位置,然后将它们的顶点数据推送到顶点数组中,然后一次全部渲染?
但是,当我为屏幕上的所有对象使用不同的纹理表面时,这一切都会变得更加混乱。
所以:
-
我还必须使用纹理表面 ID 存储每个对象。
我会按 Z 位置对所有可见对象进行排序(游戏只能自上而下查看,所有对象都是平面的)。
我会遍历这个排序数组:
创建新缓冲区并仅将顶点/texcoord/color/normal 复制到此缓冲区中:
每次纹理表面 ID 与之前的 ID 发生变化时,我都会绑定到正确的纹理 ID:
上传我收集的顶点数据。
释放用于临时顶点数组的缓冲区。
重复步骤 4-7,直到我完成了我首先排序的所有数据。
释放排序后的数组数据,然后重复步骤 1-9。
我做得对吗?
另外,我应该如何为要排序的对象设计数据结构?例如,使用std::vector
存储每个对象的顶点数据好不好?还是有更好的选择?我在想存储所有这些数据的std::vector
看起来像:
struct GameObject
int TexID;
float z; // we will sort by this
vector<VTCNStruct> VertexData; // store each point of the object here (including color/normal/texcoord points).
;
vector<GameObject> GameObjectBuffer; // push all sortable objects here
另外,在第 4 步:在这种情况下是否可以使用已经存在的std::vector
?我的想法是我必须使用原始数组,例如new float[100]
将顶点数组发送到我的GPU,或者我可以以某种方式(有效地)在这里使用我已经存在的排序std::vector
,而无需每次都创建新缓冲区纹理 ID 改变了?
【问题讨论】:
【参考方案1】:现在请放弃 glBegin/glEnd,它已被弃用,并已从 OpenGL-4 中删除。你绝对应该使用顶点数组,如果你使用顶点缓冲区对象会更好。
使用立即模式 (glBegin/glEnd),OpenGL 驱动程序必须从函数调用中构建一个顶点数组。由于不清楚会到达多少顶点,它最终会多次重新分配内存(GPU直接执行glBegin/glEnd之间调用的时间已经过去了)。
使用顶点数组的性能永远不会低于立即模式。可以将多个对象的几何图形放入单个顶点数组中,您可以通过顶点索引列表将它们分开。顶点索引列表也可以存储在缓冲区对象中。
然后在绘制对象之间调整模型视图矩阵。
排序顺序应如下所示:
-
分为不透明和半透明对象。对不透明进行排序
按 GL 对象(纹理、着色器、材质等)对 opqaues 进行排序,因为切换 GL 对象的开销最大。
对于每个不同的 GL 对象组,从近到远排序并按该顺序绘制
将半透明物体从远到近排序并按此顺序绘制
您不应该尝试摆弄缓冲区向上/下载。只需上传一次顶点数据,然后调整索引数组及其提交顺序即可。无需担心可用的 OpenGL 内存,没有强制限制。如果您的数据不适合 GPU RAM,则驱动程序负责将其交换到系统内存——这也是您应该首先按 OpenGL 对象排序的原因,因为每次切换 OpenGL 对象(glBindTexture、glBindBuffer 等)时,驱动程序可能需要交换,因此您希望将这些交换操作保持在最低限度。以索引数组的形式发送到 GPU 的数据量至少会比立即模式调用发送的数据量少得多:
索引数组:每个顶点 16 位(16 位大小的索引性能最高)。
立即模式调用:每个顶点 4*32 位
【讨论】:
Index array: 16 bit per vertex
VS。 Immediate mode call: 4*32 bit per vertex
是不是计算错误,因为使用顶点数组,您还必须发送顶点数据。假设我发送 glVertex3f(1,1,1) 函数地址需要 3*32 位 + 32 位?但是对于顶点数组,我还必须发送 x,y,z 需要 3*32 位 + 索引,需要 16 位,总保存位 = 16?或者我错过了什么?据我所知,您无法将顶点数组存储到 GPU 中,因此您必须每帧一次又一次地发送顶点数据,即使它没有改变......?
使用顶点缓冲区对象顶点数组确实存储在 GPU 内存中。大多数现代驱动程序在不明确使用 VBO 的情况下倾向于在原位创建它们,因为现代 GPU 就是这样工作的。
就地?你是什么意思,现代GPU自动将所有顶点数组转换为VBO??
不是 GPU,而是驱动程序。现代 GPU 处理驻留在自己内存中的大块几何数据。因此,任何要绘制的几何图形都必须首先传输到 GPU 内存。因此立即模式调用必须动态创建一个顶点数组。然而,API 不会反映这一点,因此使用不使用 VBO 的顶点数组将不会导致可访问的 VBO。这一切都发生在 OpenGL API 的幕后。【参考方案2】:
您应该为每个对象保留一个顶点数组。不要使用 glTranslatef/glRotatef 等。而是自己将变换折叠成一个矩阵。使用该矩阵对对象进行深度排序。然后通过推送对象变换矩阵,绘制顶点数组,然后弹出变换矩阵,从前到后绘制对象。
这种方法意味着您不必经常创建和释放数组来存储顶点数据。
至于使用 std::vector,大多数实现都在内部使用原始 C 数组,因此,您可以执行 &myVec[0] 来获取该数组。但是,不要尝试持久化该指针,因为 std::vector 可能会更改 realloc 其数组,然后你的持久化指针将不再有效。
【讨论】:
“你应该为每个对象保留一个顶点数组”——这很聪明吗?因为我的大多数对象只是四边形,当我不仅向 GPU 发送更多数据(以颜色/法线数组的方式)时,这不会使用更多带宽,而且我还需要在每个对象之间发送顶点数组上传命令. 如何将我的变换折叠成一个矩阵?另外,你不是说从后往前吗?所以,基本上你建议我保留glPushMatrix()
,而不是glBegin/glEnd
,我会使用顶点数组?如果我想改用 VBO,我应该保持你建议的方法吗?
新手-我不是 OpenGL 程序员,但我相信 Josh 的意思是你应该先绘制最前面(例如,屏幕上的壁橱)的对象,最后绘制最远的对象。编辑——通过将变换折叠成一个矩阵,我相信你可以使用一些数学技巧来获得一个变换来移动/旋转/等。一口气。以上是关于OpenGL:如何使用深度排序的顶点数组设计高效的渲染系统?的主要内容,如果未能解决你的问题,请参考以下文章