WebGL中的分支似乎类似于以下内容(从各种文章中解释):
着色器并行执行其代码,如果需要在继续之前评估条件是否为真(例如使用if
语句),那么它必须发散并以某种方式与之通信其他主题是为了得出结论。
也许这有点偏离 - 但最终,似乎着色器中分支的问题是每个线程可能看到不同的数据。因此,仅使用制服进行分支通常是可以的,而动态数据上的分支则不然。
问题1:这是正确的吗?
问题2:这与那些相当可预测但不是统一的内容有关,例如循环中的索引?
具体来说,我有以下功能:
vec4 getMorph(int morphIndex) {
/* doesn't work - can't access morphs via dynamic index
vec4 morphs[8];
morphs[0] = a_Morph_0;
morphs[1] = a_Morph_1;
...
morphs[7] = a_Morph_7;
return morphs[morphIndex];
*/
//need to do this:
if(morphIndex == 0) {
return a_Morph_0;
} else if(morphIndex == 1) {
return a_Morph_1;
}
...
else if(morphIndex == 7) {
return a_Morph_7;
}
}
我称之为:
for(int i = 0; i < 8; i++) {
pos += weight * getMorph(i);
normal += weight * getMorph(i);
...
}
从技术上讲,它运行正常 - 但我关心的是基于动态索引的所有if / else分支。在这样的情况下,这会减慢速度吗?
为了便于比较,虽然在这里用一些简洁的词语解释起来很棘手 - 但我有一个另类的想法是始终为每个属性运行所有计算。这将涉及每个顶点可能有24个多余的vec4 += float * vec4
计算。通常情况下,这会比索引分支8次更好还是更差?
注意:在我的实际代码中,有更多级别的映射和间接,虽然它归结为同一个getMorph(i)
问题,但我的用例涉及从两者获取该索引循环中的索引,以及在统一整数数组中查找该索引
答案 0 :(得分:2)
我知道这不是你问题的直接答案,但是......为什么不只是不使用循环?
vec3 pos = weight[0] * a_Morph_0 +
weight[1] * a_Morph_1 +
weight[2] * a_Morph_2 ...
如果您想要通用代码(即可以设置变形数量的位置),那么可以使用#if
,#else
,#endif
const numMorphs = ?
const shaderSource = `
...
#define NUM_MORPHS ${numMorphs}
vec3 pos = weight[0] * a_Morph_0
#if NUM_MORPHS >= 1
+ weight[1] * a_Morph_1
#endif
#if NUM_MORPHS >= 2
+ weight[2] * a_Morph_2
#endif
;
...
`;
或使用字符串操作在JavaScript中生成着色器。
function createMorphShaderSource(numMorphs) {
const morphStrs = [];
for (i = 1; i < numMorphs; ++i) {
morphStrs.push(`+ weight[${i}] * a_Morph_${i}`);
}
return `
..shader code..
${morphStrs.join('\n')}
..shader code..
`;
}
通过字符串操作生成着色器是正常的做法。你会发现所有主要的3D库都这样做(three.js,unreal,unity,pixi.js,playcanvas等...)
至于分支是否缓慢,它实际上取决于GPU,但一般规则是肯定的,无论它如何完成它都会变慢。
您通常可以通过编写自定义着色器而不是尝试通用来避免分支。
而不是
uniform bool haveTexture;
if (haveTexture) {
...
} else {
...
}
只需编写2个着色器。一个有纹理,一个没有。
避免分支的另一种方法是通过数学创造性。例如,假设我们想要支持顶点颜色或纹理
varying vec4 vertexColor;
uniform sampler2D textureColor;
...
vec4 tcolor = texture2D(textureColor, ...);
gl_FragColor = tcolor * vertexColor;
现在我们只想要一个顶点颜色集textureColor
到1x1像素的白色纹理。当我们只想要一个纹理时,请关闭vertexColor
的属性并将该属性设置为白色gl.vertexAttrib4f(vertexColorAttributeLocation, 1, 1, 1, 1)
;和 bonus!,我们可以通过提供纹理和顶点颜色来调整vertexColors的纹理。
类似地,我们可以传入0或1来将某些事物乘以0或1以消除它们的影响。在变形示例中,定位性能的3d引擎会为不同数量的变形生成着色器。不关心性能的3d引擎会有1个支持N个变形目标的着色器,只需将任何未使用目标的权重设置为0即可。
另一种避免分支的方法是step
函数,定义为
step(edge, x) {
return x < edge ? 0.0 : 1.0;
}
因此,您可以选择a
或b
v = mix(a, b, step(edge, x));