Webgl 优化查找表的分支

Webgl 优化查找表的分支,webgl,Webgl,WebGL中的分支似乎如下所示(摘自各种文章): 着色器并行执行其代码,如果需要在继续之前评估某个条件是否为真(例如,使用if语句),则它必须发散并以某种方式与其他线程通信,以便得出结论 也许这有点不对劲——但归根结底,着色器中分支的问题似乎在于每个线程可能看到不同的数据。因此,仅使用一致性进行分支通常是可以的,而使用动态数据进行分支则不是 问题1:这是否正确? 问题2:这与相当可预测但不统一的事物(如循环中的索引)有何关系? 具体来说,我有以下功能: vec4 getMorph(int mor

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);
  ...
}
for(int i=0;i<8;i++){
pos+=重量*getMorph(i);
正常+=重量*getMorph(i);
...
}
从技术上讲,它工作得很好——但我关心的是基于动态索引的所有if/else分支。在这种情况下,这会让事情变慢吗

为了便于比较,虽然在这里用几句简洁的话来解释很难——但我有另一个想法,总是对每个属性运行所有计算。这可能涉及每个顶点24次多余的
vec4+=float*vec4
计算。通常,这比在索引上分支8次更好还是更糟糕


注意:在我的实际代码中,还有几个级别的映射和间接寻址,虽然它确实可以归结为相同的
getmorp(i)
问题,但我的用例涉及从循环中的索引和在统一整数数组中查找该索引

我知道这不是对您的问题的直接回答,但。。。为什么不使用循环呢

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..
   `;
 }
只需编写两个着色器。一个有纹理,一个没有纹理

另一种避免分支的方法是在数学上发挥创意。例如,假设我们想要支持顶点颜色或纹理

varying vec4 vertexColor;
uniform sampler2D textureColor;

...

vec4 tcolor = texture2D(textureColor, ...);
gl_FragColor = tcolor * vertexColor;
现在,我们只需要将顶点颜色设置为1x1像素的白色纹理。当我们只需要一个纹理时,关闭
vertexColor
的属性,并将该属性设置为white
gl.VertexAttribute4F(vertexColorAttributeLocation,1,1,1)
;还有奖金!,我们可以通过同时提供纹理和顶点颜色,使用VertexColor对纹理进行调制

类似地,我们可以通过0或1将某些事物乘以0或1来消除它们的影响。在变形示例中,以性能为目标的3d引擎将为不同数量的变形生成着色器。一个不关心性能的3d引擎将有一个支持N个变形目标的着色器,只需将任何未使用目标的权重设置为0即可

另一种避免分支的方法是
步骤
函数,它被定义为

step(edge, x) {
  return x < edge ? 0.0 : 1.0;
}

哇,这里都有很棒的建议!有很多选择需要消化。如果你把这个添加到你的基础网站,那就太好了:)一个后续问题——“一个以性能为目标的3d引擎会为不同数量的变形生成着色器”。。。切换着色器不是很昂贵吗?当然一切都是相对的-我的意思是,切换着色器通常会比24个多余的vec4乘法更昂贵还是更便宜?(例如,对于非活动的渲染器,将权重设置为0)另外-只是为了添加,当然,将渲染器组织为每个着色器的第一个渲染器可能会降低在真实情况下切换的性能影响,但对于初学者,我只是试图掌握表上的内容。再次感谢!你在这里和通过你的网站提供的帮助是传奇的。我认为这真的取决于你的测试方式、驱动程序和GPU。我真的不知道切换着色器的成本。我知道AAA游戏拥有1000-3000个着色器并不少见,因为它们构建了所有需要的组合。至少在我的系统上。
step(edge, x) {
  return x < edge ? 0.0 : 1.0;
}
v = mix(a, b, step(edge, x));