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