如果我从一开始就调用
gl.useProgram
,我的代码就可以正常工作。另一方面,如果我从不调用 gl.useProgram
,则不会显示任何内容并在控制台中收到两种警告:
第一:
WebGL: INVALID_OPERATION: uniformMatrix4fv: location is not from current program
第二:
WebGL: INVALID_OPERATION: drawElements: no valid shader program in use
第二个警告对我来说很有意义,这是
drawElements
的签名
drawElements(mode: GLenum, count: GLsizei, type: GLenum, offset: GLintptr):
当您发出绘制调用时,您需要知道要运行哪个程序,但调用签名没有程序的提示,因此需要一个全局程序,所以我将
gl.useProgram
放在 gl.drawElements
之前,一切正常,但是第一种警告仍然存在,这对我来说毫无意义!让我们看看 set Uniform 的签名
vertexAttrib4fv(index: GLuint, values: Iterable<GLfloat>): void;
第一个实际上是程序在运行时可以引用的内存中的地址,并且应该从程序中获取该地址,例如
getUniformLocation(program: WebGLProgram, name: string): WebGLUniformLocation | null;
程序已经用来获取地址了,为什么在设置uniform时需要当前的全局状态呢?我是否遗漏了任何会导致问题的案例?
统一状态(您使用
gl.uniformXXX
设置的值)是程序状态,因此如果您没有使用gl.useProgram
设置程序,那么WebGL不知道您要为哪个程序设置状态。
我明白你的观点,但它应该知道哪个程序,因为它知道该位置来自哪个程序,所以为什么不能使用该信息来设置正确的程序状态。
问题是 WebGL 基于 OpenGL ES,虽然有些事情发生了变化,但它的目标是在网络的某些限制下尽可能接近原始 API。
在 OpenGL 中,统一位置是整数,而不是特殊对象。
int someLocation = glGetUniformLocation(someProgram, "myUniform");
printf("%d\n", someLocation);
上面的代码可能会打印
0
、1
、37
、99
等......只是一个数字。 WebGL 决定将 gl.getUniformLocation
更改为 WebGLUniformLocation
对象而不仅仅是整数的原因是网页需要可移植,而程序员经常假设他们不应该做的事情。所以,他们可能会宣布两套制服
uniform float a;
uniform float b;
然后对这样的位置进行数学计算
int locationA = glGetUniformLocation(prg, "a");
int locationB = locationA + 1;
这可能在他们的机器上工作,但无法移植,因为其他驱动程序上的其他机器将使用不同的号码。事实上,他们甚至可能不会查找位置,只是假设第一个制服的位置为 0,第二个制服的位置为 1,并且这可能再次发生在工作或他们的机器上。
此外,他们可能会制作一个程序
uniform float a;
uniform float b;
还有另一个程序
uniform float a;
uniform float b;
uniform float c;
并假设第一个程序中的
a
和 b
的位置在第二个程序中是相同的,并且这可能恰好在他们的机器上工作,但在其他地方不起作用。
但是,网络的设计是,理想情况下,您应该能够编写一次代码并让它在所有设备上的任何地方运行,至少尽可能地如此。这就是为什么 WebGL 决定将这些整数位置包装在不透明对象中并跟踪它们来自哪个程序。防止程序员用位置做不可移植的事情。
现在你问,好吧,所以我们有一个不透明的位置对象,并且该对象知道它来自哪个程序,那么为什么它不能尽一切努力在正确的程序上设置正确的制服。
原因是,WebGL 试图尽可能接近 OpenGL ES 2.0。这使得将 OpenGL ES 代码从其他语言移植到 Web 中变得相对容易。例如,Unity 能够导出到网页,这主要是通过他们已经为移动设备编写的 OpenGL ES 支持实现的。
将这些函数的功能更改为与 OpenGL ES 中的功能不同会破坏这种可移植性。
如果你真的想实现这个,那么
gl.uniformXXX
必须在内部执行此操作。
假设
WebGLUniformLocation
是这样的。 实际上由于原因更复杂,但为了保持简单
class WebGLUniformLocation {
public:
int location; // the location as returned from `glGetUniformLocation`
int program; // the program as from `gl.createProgram`
};
然后,要随时为任何程序设置制服而不必先调用
gl.useProgram
,您需要这样的代码
void uniform1f(const WebGLUnformLocation& loc, float v) {
// save the current program
int currentProgram;
glGetIntegerv(GL_CURRENT_PROGRAM, ¤tProgram);
// Set the program to the one needed by loc
glUseProgram(loc.program);
// Set the uniform
glUniform1fv(loc.location, v);
// Restore the current program
glUseProgram(currentProgram);
}
所以现在
uniform1f
调用 4 个函数而不是 1 个。很容易慢 4 倍。您可以添加优化,例如当前程序是否与 loc.program
相同,然后跳过对 glUseProgram
的前 2 次调用。您还可以跟踪当前的节目,这样您就不必调用 glGetIntegerv
。这些都是可以使其接近 1 次调用的优化,但仍然存在必须解决的问题。
例如,如果一个程序是当前程序,并且您重新链接它(调用
glLinkProgram
并且链接失败,则旧程序仍然是当前程序。但是,您不能再使用该值调用 glUseProgram
。这意味着如果有这样的代码,上面的代码会对规范进行不兼容的更改。
glUseProgram(somePrg); // somePrg is good
glLinkProgram(somePrg); // this link fails
glUniform1f(locationA); // This works, it sets the previous valid somePrg
glDrawArrays(); // This works, it uses the previous valid somePrg
但是随着 gl.uniform1f
的更改,我们上面建议的上述代码不再根据规范工作
glUseProgram(somePrg); // somePrg is good
glLinkProgram(somePrg); // this link fails
// glUniform1f(locationA); ---- impl
glGetIntegerv(GL_CURRENT_PROGRAM, ¤tProgram); // returns somePrg
glUseProgram(loc.program); // works
glUniform1fv(loc.location, v); // works
glUseProgram(currentProgram); // fails!!!!!! somePrg is not
// allowed to be set as the
// current program because the
// link failed
glDrawArrays(); // fails, because the current program is
// is still `loc.program` on `somePrg`