Javascript 多光源的WebGL片段着色器?

Javascript 多光源的WebGL片段着色器?,javascript,webgl,Javascript,Webgl,我希望能够将多个光源附加到场景图的每个节点上,但我不知道如何做 从上的教程中,我学会了使用定向照明或位置照明,但我找不到关于如何实现多个光源的好解释 因此,目标应该是,可以选择向每个节点附加任意数量的光源,类型可以是定向照明或位置照明,如果可能和可取,这应该通过仅使用一个着色器程序来实现(如果这不是唯一的可能性),因为我会根据每个节点的具体需要自动创建程序(除非堆栈上已经有具有相同设置的程序) 基于learningwebgl.com上的教程,我的片段着色器源对于使用照明的节点对象而言,没有预设绑

我希望能够将多个光源附加到场景图的每个节点上,但我不知道如何做

从上的教程中,我学会了使用定向照明或位置照明,但我找不到关于如何实现多个光源的好解释

因此,目标应该是,可以选择向每个节点附加任意数量的光源,类型可以是定向照明或位置照明,如果可能和可取,这应该通过仅使用一个着色器程序来实现(如果这不是唯一的可能性),因为我会根据每个节点的具体需要自动创建程序(除非堆栈上已经有具有相同设置的程序)

基于learningwebgl.com上的教程,我的片段着色器源对于使用照明的节点对象而言,没有预设绑定到其中一种照明类型,可能如下所示

precision highp float;

uniform bool uUsePositionLighting;
uniform bool uUseDirectionalLighting;

uniform vec3 uLightPosition;
uniform vec3 uLightDirection;

uniform vec3 uAmbientColor;
uniform vec3 uDirectionalColor;

uniform float uAlpha;

varying vec4 vPosition;
varying vec3 vTransformedNormal;
varying vec3 vColor;


void main (void) {

  float directionalLightWeighting;

  if (uUseDirectionalLighting) {

    directionalLightWeighting = max(dot(vTransformedNormal, uLightDirection), 0.0);

  else if (uUsePositionLighting) {

    vec3 lightDirection = normalize(uLightPosition, vPosition.xyz);

    directionalLightWeighting = max(dot(normalize(vTransformedNormal, lightDirection), 0.0);

  }

  vec3 lightWeighting = uAmbientColor + uDirectionalColor * directionalLightWeighting;

  gl_FragColor = vec4(vColor * lightWeighting, uAlpha);

}
…所以,这基本上是我对这门学科知识的贫乏状态

我还问自己,添加更多光源会如何影响照明颜色:


我的意思是,
uAmbientColor
uddirectionalcolor
是否必须合计为
1.0
?在这种情况下(特别是当使用多个光源时),在将这些值传递给着色器之前预先计算这些值肯定会很好,不是吗?

将灯光放入一个数组中,并为每个片段循环。从固定光源阵列开始,直到OpenGL 4.3才支持无界阵列,而且使用起来更复杂

大致如下:

uniform vec3 uLightPosition[16];
uniform vec3 uLightColor[16];
uniform vec3 uLightDirection[16];
uniform bool uLightIsDirectional[16];

 ....

 void main(void) {
   vec3 reflectedLightColor;

   // Calculate incoming light for all light sources
   for(int i = 0; i < 16; i++) {
     vec3 lightDirection = normalize(uLightPosition[i], vPosition.xyz);
     if (lightIsDirectional[i]) {
       reflectedLightColor += max(dot(vTransformedNormal, uLightDirection[i]), 0.0) * uLightColor[i];
     }
     else  {
       reflectedLightColor += max(dot(normalize(vTransformedNormal, lightDirection), 0.0) * uLightColor[i];
     }
   }

   glFragColor = vec4(uAmbientColor + reflectedLightColor * vColor, uAlpha);
 }
uniformvec3-uLightPosition[16];
均匀vec3-uLightColor[16];
均匀矢量方向[16];
均匀布尔图方向[16];
....
真空总管(真空){
vec3反射光颜色;
//计算所有光源的入射光
对于(int i=0;i<16;i++){
vec3 lightDirection=规格化(uLightPosition[i],vpposition.xyz);
if(光的方向[i]){
反射光颜色+=最大值(点(v变换正常,uLightDirection[i]),0.0)*uLightColor[i];
}
否则{
反射光颜色+=最大值(点(归一化(VTTransformedNormal,lightDirection),0.0)*uLightColor[i];
}
}
glFragColor=vec4(uAmbientColor+反射光颜色*vColor,uAlpha);
}
然后,对于不使用的条目,可以通过将uLightColor设置为(0,0,0)来启用/禁用光源

环境光和方向光不必加起来等于1,实际上光源的强度可以比1.0强得多,但是你需要进行色调映射,以返回屏幕上可以显示的一系列值,我建议你四处玩,以了解发生了什么(例如,当光源具有负颜色或颜色高于1.0时会发生什么情况?)

uAmbientColor
只是一种(糟糕的)模拟场景中多次反弹的灯光的方法。否则阴影中的东西会变成完全黑色,看起来不现实


反射率通常应介于0和1之间(在本例中,它将是“max”计算返回的部分),否则,当通过材料观察时,光源会变得更强。

@ErikMan的答案很好,但可能会涉及GPU部分的大量额外工作,因为你要检查每个片段的每个灯光,这并不是严格必要的

我建议构建一个剪辑空间四叉树,而不是一个数组。(如果目标平台/GL版本支持,可以在计算着色器中执行此操作。)

节点可能具有以下结构(my JS生锈时的伪代码):

构建树时,只需对照隐式节点边界检查每个灯光的剪辑空间边界框。(根从(-1,-1)到(+1,+1)。每个维度上的每个子节点的大小都是该尺寸的一半。因此,实际上不需要存储节点边界。)

如果灯光接触到节点,请在node.LightMask中设置与灯光对应的位。如果灯光完全包含节点,请停止递归。如果灯光与节点相交,请细分并继续

在片段着色器中,找到包含片段的叶节点,并应用其位在叶节点的遮罩中设置的所有灯光

如果你认为你的树是稠密的,你也可以将它存储在一个mipmap金字塔中

将瓷砖的尺寸保持在32的倍数,最好是正方形

vec2 minNodeSize = vec2(2.f / 32);
现在,如果您有少量灯光,这可能会有点过头。您可能必须有大量灯光才能看到任何真正的性能优势。此外,正常循环可能有助于减少着色器中的数据发散,并使消除分支更容易


这是实现简单平铺渲染器的一种方法,并为拥有数百个灯光打开了大门。

非常感谢您的回答Erik Man!但我有一个问题:在着色器中使用for循环真的是一个好主意吗?我的意思是,这不会导致一些性能问题吗?是的,循环会消耗一些性能,但它会降低性能是处理多个光源的唯一方法。GLSL编译器可能会展开循环,因为大小是固定的,所以将“16”替换为尽可能低的数字。在桌面上,在有大量光源之前,您不应该注意到任何性能问题,移动设备更有限,因此最好将一些光源移动到顶点着色器会变慢。好的,谢谢!至于自动为每个节点创建着色器程序,取决于它们各自的要求,我只想将数组项的数量设置为绑定到节点的光源数量,以避免不必要的循环。随时由您决定。确保这对gen来说是常见的运行所需的着色器。是在初始化时生成它们,还是根据需要生成并缓存它们,实际上取决于您,还取决于在
vec2 minNodeSize = vec2(2.f / 32);