为啥禁用顶点属性数组零时OpenGL绘图失败?

Posted

技术标签:

【中文标题】为啥禁用顶点属性数组零时OpenGL绘图失败?【英文标题】:Why does OpenGL drawing fail when vertex attrib array zero is disabled?为什么禁用顶点属性数组零时OpenGL绘图失败? 【发布时间】:2012-11-01 04:22:01 【问题描述】:

让我的顶点着色器在 ATI 驱动程序的 OpenGL 3.3 核心下运行时遇到了极大的麻烦:

#version 150

uniform mat4 graph_matrix, view_matrix, proj_matrix;
uniform bool align_origin;

attribute vec2 graph_position;
attribute vec2 screen_position;
attribute vec2 texcoord0;
attribute vec4 color;
varying vec2 texcoord0_px;
varying vec4 color_px;

void main() 
    // Pick the position or the annotation position
    vec2 pos = graph_position;

    // Transform the coordinates
    pos = vec2(graph_matrix * vec4(pos, 0.0, 1.0));

    if( align_origin )
        pos = floor(pos + vec2(0.5, 0.5)) + vec2(0.5, 0.5);

    gl_Position = proj_matrix * view_matrix * vec4(pos + screen_position, 0.0, 1.0);
    texcoord0_px = texcoord0;
    color_px = color;

我使用 glVertexAttrib4f 来指定颜色属性,并关闭属性数组。根据 3.3 核心规范的第 33 页,应该可以:

如果与顶点着色器所需的通用属性对应的数组未启用,则从当前通用属性状态中获取相应的元素(参见第 2.7 节)。

但是(大多数情况下,取决于配置文件和驱动程序)如果我访问禁用的颜色属性,着色器要么根本没有运行,要么使用黑色。将其替换为常量即可运行。

大量搜索产生了this page of tips regarding WebGL,其中有以下内容:

始终启用顶点属性 0 数组。如果您在禁用顶点属性 0 数组的情况下进行绘制,您将强制浏览器在桌面 OpenGL(例如 Mac OSX)上运行时进行复杂的仿真。这是因为在桌面 OpenGL 中,如果顶点属性 0 未启用数组,则不会绘制任何内容。您可以使用 bindAttribLocation() 强制顶点属性使用位置 0,并使用 enableVertexAttribArray() 使其启用数组。

果然,不仅颜色属性被分配给索引零,而且如果我将一个不同的、启用数组的属性强制绑定为零,代码就会运行并产生正确的颜色。

我在任何地方都找不到任何其他提及此规则的内容,当然也不是在 ATI 硬件上。有谁知道这条规则来自哪里?或者这是 Mozilla 人员注意到并警告过的实施中的错误?

【问题讨论】:

【参考方案1】:

tl;dr:这是一个驱动程序错误。 Core OpenGL 3.3 应该允许您不使用属性 0,但兼容性配置文件不允许,并且某些驱动程序没有正确实现该开关。只需确保使用属性 0 即可避免任何问题。

实际内容:

让我们来上一堂关于 OpenGL 规范是如何形成的历史课。

在最古老的 OpenGL 时代,只有一种渲染方式:立即模式(即:glBegin/glVertex/glColor/glEtc/glEnd)。显示列表存在,但它们总是被定义为简单地再次发送捕获的命令。因此,虽然实现实际上并没有进行所有这些函数调用,但实现的行为仍然与它们一样。

在 OpenGL 1.1 中,客户端顶点数组被添加到规范中。现在请记住:规范是指定行为的文档,而不是实现。因此,ARB 简单地定义了客户端数组的工作方式与立即模式调用完全一样,使用对当前数组指针的适当访问。显然,实现实际上不会这样做,但它们的行为就像它们那样。

基于缓冲区对象的顶点数组以相同的方式定义,但语言稍微复杂,因为从服务器存储而不是客户端存储中提取。

然后发生了一些事情:ARB_vertex_program(不是 ARB_vertex_shader。我说的是汇编程序)。

你看,一旦你有了着色器,你就想开始定义你自己的属性,而不是使用内置的属性。这一切都说得通。但是,有一个问题。

即时模式的工作原理如下:

glBegin(...);
glTexCoord(...);
glColor(...);
glVertex(...);
glTexCoord(...);
glColor(...);
glVertex(...);
glTexCoord(...);
glColor(...);
glVertex(...);
glEnd();

每次调用glVertex 时,都会将所有当前属性状态用于单个顶点。所有其他立即模式函数只是将值设置到上下文中;这个函数实际上发送顶点到OpenGL进行处理。这在即时模式下非常重要。而且由于每个顶点都必须在固定函数区域中具有位置,因此使用此函数来决定何时处理顶点是有意义的。

一旦您不再使用 OpenGL 的固定函数顶点语义,立即模式就会出现问题。也就是说,你如何决定何时真正发送顶点?

按照惯例,他们将其固定在属性 0 上。因此,所有即时模式渲染都必须使用属性 0 或 glVertex 来发送顶点。

但是,由于所有其他渲染都是基于立即模式渲染的语言,所有其他渲染都具有与立即模式渲染相同的限制。立即模式需要属性 0 或 glVertex,因此客户端数组等也是如此。尽管这对他们来说没有意义,但他们需要它,因为规范如何定义他们的行为。

然后 OpenGL 3.0 出现了。他们弃用了立即模式。已弃用并不意味着已删除;规范中仍然包含这些函数,并且所有顶点数组渲染仍然根据它们定义

OpenGL 3.1 实际上淘汰了旧的东西。这带来了一些语言问题。毕竟,每个阵列绘制命令总是以立即模式定义的。但是一旦立即模式不再存在......你如何定义它?

因此他们不得不为核心 OpenGL 3.1+ 开发新语言。在这样做的同时,他们消除了需要使用属性 0 的无意义限制。

但兼容性配置文件没有。

因此,OpenGL 3.2+ 的规则是这样的。如果您有核心 OpenGL 配置文件,则不必使用属性 0。如果您有兼容性 OpenGL 配置文件,则必须使用属性 0(或glVertex)。规范就是这么说的。

但这不是实现所实现的。

一般来说,NVIDIA 从来不关心“必须使用属性 0”规则,只是按照您的预期去做,即使在兼容性配置文件中也是如此。从而违反规范的字母。 AMD 通常更有可能坚持规范。但是,他们忘记了正确实现 core 行为。所以 NVIDIA 在兼容性上过于宽松,而 AMD 在核心上过于严格。

要解决这些驱动程序错误,只需始终使用属性 0。

顺便说一句,如果您想知道,NVIDIA 赢了。在 OpenGL 4.3 中,兼容性配置文件对其数组渲染命令使用与核心相同的措辞。因此,您可以不在核心和兼容性上使用属性 0。

【讨论】:

谢谢。很高兴知道我没有完全疯了。我猜想如果在 OpenGL ES 模拟器的上下文中提到了since 这可能是一个错误,但我不明白为什么我没有在网上看到任何提及它,或者为什么这种公然违反规范的行为会被忽视。希望下一个遇到这个问题的人能找到这个问题和你的解释。幸运的是,我确实有一个我一直使用的属性,我只是将它绑定到零来解决这个问题。感谢您的信息。 哇,只有 6 个赞,太糟糕了。 :) 很棒的答案,谢谢。 让我补充一点,另一个问题是 OpenGL 最长的时间没有一致性测试,所以所有这些在实现中的差异只是运气和突发奇想。直到最近几年,他们才终于开始编写一致性测试(从 OpenGL ES 移植并经常从 WebGLs 测试中获取想法和/或错误报告)。 A 把这部分作为一个教训。如果您希望多个实现就行为达成一致,则需要进行广泛的一致性测试。每个 API 缺失测试都会有很多此类问题。

以上是关于为啥禁用顶点属性数组零时OpenGL绘图失败?的主要内容,如果未能解决你的问题,请参考以下文章

OpenGL实例化数组绘图

由于OpenGL可以执行内置的背面剔除,因此它必须计算顶点法线;可以访问这些而不是将其作为属性发送吗?

OpenGL:顶点数组与 glBegin()

我的OpenGL学习进阶之旅顶点属性顶点数组

我的OpenGL学习进阶之旅顶点属性顶点数组

Modern OpenGL - 顶点数组、属性与绑定点(OpenGL 4.5+)