显然我的 1 方法不是最优的,场景中的每个静态表面是否应该有一个 VAO?
绝对不。切换 VAO 的成本很高。如果在场景中为每个对象分配一个 VAO,则需要在渲染此类对象之前切换 VAO。将其扩展到当前可见的数百或数千个对象,您将获得同样多的 VAO 更改。问题是,如果您有多个共享公共内存布局的对象,即元素的大小/类型/规范化/步幅相同,为什么要定义多个存储相同信息的 VAO?您可以使用相应的绘制调用直接控制要开始从中提取顶点属性的偏移量。
对于非索引几何体,这是微不足道的,因为您向 gl[Multi]DrawArrays*() 提供了第一个(或在多重绘制情况下的偏移量数组)参数,它定义了关联 ARRAY_BUFFER 数据存储的偏移量。
对于索引几何,并且如果您将多个对象的索引存储在单个 ELEMENT_ARRAY_BUFFER 中,则可以使用 gl[Multi]DrawElementsBaseVertex 为索引提供恒定偏移量,或者通过在将索引上传到缓冲区对象之前添加恒定偏移量来手动偏移索引。
能够为缓冲存储提供偏移量还意味着您可以将多个不同的对象存储在单个 ARRAY_BUFFER 中,并将相应的索引存储在单个 ELEMENT_ARRAY_BUFFER 中。但是,缓冲区对象的大小应取决于您的硬件,并且供应商的建议有所不同。
我正在为每个顶点写入一个统一变量,对吗?我读到统一着色器变量不应该在中帧改变,如果我能够将不同的值写入我的统一变量,那么统一与顶点着色器中的简单变量有何不同?
首先,声明为in/out的制服和着色器输入/输出变量在各种情况下有所不同:
输入/输出变量定义着色器阶段之间的接口,即一个着色器阶段中的输出变量由下一阶段中相应且同名的输入变量支持。如果使用相同的名称声明,则统一在所有阶段都可用,并且在应用程序更改之前是不变的。
顶点着色器中的输入变量由 ARRAY_BUFFER 填充。统一块内的统一支持一个 UNIFORM_BUFFER。
输入变量也可以直接使用 glVertexAttrib*() 系列函数编写。单一制服是使用 glUniform*() 系列函数编写的。
制服的价值是程序状态。输入变量的值不是。
语义上的差异也应该很明显:制服,顾名思义,在一组图元中通常是恒定的,而输入变量通常在每个顶点或片段中变化(由于插值)。
编辑:澄清和考虑 Nicol Bolas 的评论:应用程序不能更改由单个绘图调用提交的一组顶点的制服,也不能通过调用 glVertexAttrib*() 来更改顶点属性。由缓冲区对象支持的顶点着色器输入将在每个顶点或以 glVertexAttribDivisor 设置的某个特定速率更改一次。
EDIT2:为了阐明 VAO 在理论上如何存储多个布局,您可以简单地定义具有不同索引但语义相同的多个数组。例如,
glVertexAttribPointer(0, 4, ....);
和
glVertexAttribPointer(1, 3, ....);
可以定义两个数组,索引为 0 和 1,组件大小为 3 和 4,并且都引用顶点的位置属性。但是,根据您要渲染的内容,您可以绑定一个假设的顶点着色器输入
// if you have GL_ARB_explicit_attrib_location or GL3.3 available, use explicit
// locations
/*layout(location = 0)*/ in vec4 Position;
或者
/*layout(location = 1)*/ in vec3 Position;
显式索引 0 或 1 或 glBindAttribLocation() 并仍然使用相同的 VAO。AFAIK,规范没有说明如果启用了属性但不是由当前着色器提供的属性会发生什么,但我怀疑在这种情况下实现只是忽略该属性。
是否从相同或不同的缓冲区对象获取所述属性的数据是另一个问题,但当然可能。
就我个人而言,我倾向于每个布局使用一个 VBO 和 VAO,即如果我的数据由具有相同属性的相同数量的属性组成,我将它们放入一个 VBO 和一个 VAO 中。
一般来说:你可以用这些东西做很多实验。去做吧!