跳至内容

GLSL 编程/Blender/多个灯光

来自维基教科书,开放世界开放书籍
隧道中的多个范围有限的地铁灯。

本教程涵盖了**由多个光源产生的照明**。

本教程是平滑镜面高光教程的扩展。如果您还没有阅读过该教程,建议您先阅读。

多个灯光

[编辑 | 编辑源代码]

在之前关于漫反射等的教程中,我们只使用了一个光源,它由 uniform gl_LightSource[0] 指定。但是,gl_LightSource 实际上是一个最多包含 gl_MaxLights 个光源的数组。

在这里,我们将考虑一定数量的这些灯光(最多 gl_MaxLights 个),并将它们的贡献添加到总照明中。这可以使用平滑镜面高光教程中片段着色器的 for 循环来计算。结构如下

            const int numberOfLights = gl_MaxLights; 
               // up to gl_MaxLights (often 8)
            ...
            // initialize total lighting with ambient lighting
            vec3 totalLighting = vec3(gl_LightModel.ambient) 
               * vec3(gl_FrontMaterial.emission);
     
            for (int index = 0; index < numberOfLights; index++) 
               // for all light sources
            {
               ... compute diffuseReflection and specularReflection 
               for gl_LightSource[index] ...

               totalLighting = totalLighting + diffuseReflection 
                  + specularReflection;
            }
            gl_FragColor = vec4(totalLighting, 1.0);
            ...

所有灯光的总照明在 totalLighting 中累加,方法是将其初始化为环境照明,然后在 for 循环结束时将每个灯光的漫反射和镜面反射添加到 totalLighting 的前一个值。for 循环应该对任何 C/C++/Java/JavaScript 程序员来说都熟悉。请注意,for 循环有时受到严格限制。具体来说,有时您甚至无法使用 uniforms 来确定限制。(限制的技术原因是,为了“展开”循环,限制必须在编译时已知。gl_MaxLights 是 uniform 不允许作为限制的规则的例外,因为它是在特定 GPU 上所有着色器都保持不变的内置 uniforms 之一,即,如果着色器针对特定 GPU 编译,则它的值已知。)

完整着色器代码

[编辑 | 编辑源代码]

顶点着色器与平滑镜面高光教程中的相同。

         varying vec4 position; 
            // position of the vertex (and fragment) in view space 
         varying vec3 varyingNormalDirection; 
            // surface normal vector in view space
 
         void main()
         {                              
            position = gl_ModelViewMatrix * gl_Vertex; 
            varyingNormalDirection = 
               normalize(gl_NormalMatrix * gl_Normal);             
 
            gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
         }

片段着色器中唯一棘手的事情是,循环和条件语句的嵌套在某些 GPU 和/或图形驱动程序上似乎有限制。事实上,我必须通过避免 if-else 语句来减少嵌套,以便在我的计算机上编译着色器(OpenGL API 报告了内部错误)。原则上,GLSL 中允许条件语句和循环的嵌套;但是,在多个系统上测试广泛使用嵌套循环和条件语句的着色器可能是一个好主意。

确保通过 Blender 中的“首选项”禁用尽可能多的默认灯光。这将防止在使用少于三个光源时出现混淆。

         const int numberOfLights = gl_MaxLights; 
            // up to gl_MaxLights (often 8)

         varying vec4 position; 
            // position of the vertex (and fragment) in view space 
         varying vec3 varyingNormalDirection; 
            // surface normal vector in view space
 
         void main()
         {
            vec3 normalDirection = normalize(varyingNormalDirection);
            vec3 viewDirection = -normalize(vec3(position)); 
            vec3 lightDirection = vec3(0.0, 0.0, 0.0);
            float attenuation = 1.0;

            // initialize total lighting with ambient lighting
            vec3 totalLighting = vec3(gl_LightModel.ambient) 
               * vec3(gl_FrontMaterial.emission);
     
            for (int index = 0; index < numberOfLights; index++) 
               // for all light sources
            {
               if (0.0 == gl_LightSource[index].position.w) 
                  // directional light?
               {
                  attenuation = 1.0; // no attenuation
                  lightDirection = 
                     normalize(vec3(gl_LightSource[index].position));
               } 
               if (0.0 != gl_LightSource[index].position.w 
                  && gl_LightSource[index].spotCutoff > 90.0) 
                  // point light? 
               {
                  vec3 positionToLightSource = 
                     vec3(gl_LightSource[index].position - position);
                  float distance = length(positionToLightSource);
                  attenuation = 1.0 / distance; // linear attenuation                    
                  lightDirection = normalize(positionToLightSource);
               }
               if (0.0 != gl_LightSource[index].position.w 
                 && gl_LightSource[index].spotCutoff <= 90.0) 
                 // spotlight?
               {
                  vec3 positionToLightSource = 
                     vec3(gl_LightSource[index].position - position);
                  float distance = length(positionToLightSource);
                  attenuation = 1.0 / distance; // linear attenuation 
                  lightDirection = normalize(positionToLightSource);
                   
                  float clampedCosine = max(0.0, dot(-lightDirection, 
                     gl_LightSource[0].spotDirection));
                  if (clampedCosine < gl_LightSource[0].spotCosCutoff) 
                     // outside of spotlight cone?
                  {
                     attenuation = 0.0;
                  }
                  else
                  {
                     attenuation = attenuation * pow(clampedCosine, 
                        gl_LightSource[0].spotExponent);   
                  }
               }
     
               vec3 diffuseReflection = attenuation 
                  * vec3(gl_LightSource[index].diffuse) 
                  * vec3(gl_FrontMaterial.emission)
                  * max(0.0, dot(normalDirection, lightDirection));
    
               vec3 specularReflection;
               if (dot(normalDirection, lightDirection) < 0.0) 
                  // light source on the wrong side?
               {
                  specularReflection = vec3(0.0, 0.0, 0.0); 
                     // no specular reflection
               }
               else // light source on the right side
               {
                  specularReflection = attenuation 
                     * vec3(gl_LightSource[index].specular) 
                     * vec3(gl_FrontMaterial.specular) 
                     * pow(max(0.0, dot(reflect(-lightDirection, 
                     normalDirection), viewDirection)),  
                     gl_FrontMaterial.shininess);
               }
 
               totalLighting = totalLighting + diffuseReflection 
                  + specularReflection;
            }
            gl_FragColor = vec4(totalLighting, 1.0);
         }

恭喜您已完成本教程。我们已经看到了

  • 如何在 GLSL 中使用 for 循环在片段着色器中计算多个灯光的照明。

进一步阅读

[编辑 | 编辑源代码]

如果您还想了解更多


< GLSL 编程/Blender

除非另有说明,否则本页上的所有示例源代码均归属公有领域。
华夏公益教科书