避免两个 glsl 着色器之间共享的函数的代码重复

Avoiding code duplication for a function shared between two glsl shaders

我的HTML页面(pipad.org/tmp/fourier02.html)包含两个着色器:

<script type="application/glsl" id="shaderA">
    uniform vec4 a;
    vec4 f(vec4 x, vec4 y){ ... } // DUP
    vec4 main(vec4 u, vec4 v) { return f(a,u); }
</script>

<script type="application/glsl" id="shaderB">
    uniform vec4 a;
    vec4 f(vec4 x, vec4 y){ ... } // DUP
    vec4 main(vec4 u) { return f(a,u); } // notice main's sig is different
</script>  

我希望我没有过于简单化,如果是的话我可能需要修改。这些着色器以不同的方式使用(shaderB 是 RTT)。

可以看出,f 在这两种情况下是相同的。

有什么办法可以避免写两次?

我能看到的唯一方法是将着色器保留为字符串,这很麻烦,因为语法突出显示不再有效,您必须这样做:

:
var
f = 
    "vec4 f(vec4 x, vec4 y){\n" +
    "...\n" +
    "}\n",

shaderA = f + 
    "uniform vec4 a;\n" +
    "vec4 main(vec4 u, vec4 v) { return f(a,u); }\n",

shaderB = f + 
    "uniform vec4 a;\n" +
    "vec4 main(vec4 u) { return f(a,u); }\n"
;

等等

这是……嗯。与原版相比没有明显优势。我们刚刚将重复换成了恶心。

有没有更好的方法?

听起来您要找的是 template strings。默认支持多行字符串。

如果兼容性是个问题,shader = ["blah", "blah"].join('\n'); 在我看来比字符串连接更干净。

最后我将我的常用函数移到了一个单独的 <script type="application/glsl" id="common"> 标签中并做了:

<script> 
    :
    function joinElements(A,B) {
        return document.getElementById(A).innerHTML 
             + document.getElementById(B).innerHTML;
    }
    :

... 然后将 "#shaderA" 替换为 joinElements("#common", "shaderA");

正如您已经发现的那样,字符串操作是制作着色器的常用方法。几乎所有大型引擎都使用大量字符串替换来在运行时构建着色器。 WaclawJasper 指出了模板字符串。它们是 JavaScript 的新功能,但由于几乎所有支持 WebGL 的浏览器都会定期更新,因此您可以非常安全地使用它们,或者您可以使用 polyfill。

例子

var t = {
  PI: '3.14159',
  plusToPlusMinus: `
    float plusToPlusMinus(float v) {
       return v * 2.0 - 1.0;
    }
  `,
};

var shader = `
  ${t.plusToPlusMinus}
  ...
  void main() {
   a = b * ${t.PI};
  }
`;

console.log(shader);

输出:

  float plusToPlusMinus(float v) {
     return v * 2.0 - 1.0;
  }

...
void main() {
 a = b * 3.14159;
}