跳转到内容

GLSL 编程/GLUT/多光源

来自 Wikibooks,开放世界中的开放书籍
隧道中多个有限范围的地下铁灯光。

本教程涵盖了**多光源照明**。

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

多光源

[编辑 | 编辑源代码]

在之前的关于漫反射等教程中,我们只使用了一个光源,它由变量light0指定。然而,我们可以处理一组光源。

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

            const int numberOfLights = 8;
            ...
            // initialize total lighting with ambient lighting
            vec3 totalLighting = vec3(scene_ambient) * vec3(frontMaterial.ambient);
     
            for (int index = 0; index < numberOfLights; index++) // for all light sources
            {
               ... compute diffuseReflection and specularReflection for lights[index] ...

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

所有光源的总照明在totalLighting中累积,方法是将其初始化为环境光,然后在 for 循环结束时将每个光源的漫反射和镜面反射添加到totalLighting的前一个值。任何 C/C++/Java/JavaScript 程序员都应该熟悉 for 循环。请注意,GLSL 中的 for 循环通常受到严格限制。通常(例如,在 OpenGL ES 2.0 中),限制(这里:0 和numberOfLights)必须是常量,也就是说,你甚至不能使用制服来确定限制。(限制的技术原因是在编译时需要知道限制才能“展开”循环。)

完整着色器代码

[编辑 | 编辑源代码]

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

attribute vec4 v_coord;
attribute vec3 v_normal;
varying vec4 position;  // position of the vertex (and fragment) in world space
varying vec3 varyingNormalDirection;  // surface normal vector in world space
uniform mat4 m, v, p;
uniform mat3 m_3x3_inv_transp;

void main()
{
  position = m * v_coord;
  varyingNormalDirection = normalize(m_3x3_inv_transp * v_normal);

  mat4 mvp = p*v*m;
  gl_Position = mvp * v_coord;
}

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

varying vec4 position;  // position of the vertex (and fragment) in world space
varying vec3 varyingNormalDirection;  // surface normal vector in world space
uniform mat4 m, v, p;
uniform mat4 v_inv;

struct lightSource
{
  vec4 position;
  vec4 diffuse;
  vec4 specular;
  float constantAttenuation, linearAttenuation, quadraticAttenuation;
  float spotCutoff, spotExponent;
  vec3 spotDirection;
};
const int numberOfLights = 2;
lightSource lights[numberOfLights];
lightSource light0 = lightSource(
  vec4(0.0,  1.0,  2.0, 1.0),
  vec4(1.0,  1.0,  1.0, 1.0),
  vec4(1.0,  1.0,  1.0, 1.0),
  0.0, 1.0, 0.0,
  180.0, 0.0,
  vec3(0.0, 0.0, 0.0)
);
lightSource light1 = lightSource(
    vec4(0.0, -2.0,  0.0, 1.0),
    vec4(2.0,  0.0,  0.0, 1.0),
    vec4(0.1,  0.1,  0.1, 1.0),
    0.0, 1.0, 0.0,
    80.0, 10.0,
    vec3(0.0, 1.0, 0.0)
);
vec4 scene_ambient = vec4(0.2, 0.2, 0.2, 1.0);
 
struct material
{
  vec4 ambient;
  vec4 diffuse;
  vec4 specular;
  float shininess;
};
material frontMaterial = material(
  vec4(0.2, 0.2, 0.2, 1.0),
  vec4(1.0, 0.8, 0.8, 1.0),
  vec4(1.0, 1.0, 1.0, 1.0),
  5.0
);

void main()
{
  lights[0] = light0;
  lights[1] = light1;

  vec3 normalDirection = normalize(varyingNormalDirection);
  vec3 viewDirection = normalize(vec3(v_inv * vec4(0.0, 0.0, 0.0, 1.0) - position));
  vec3 lightDirection;
  float attenuation;
  
  // initialize total lighting with ambient lighting
  vec3 totalLighting = vec3(scene_ambient) * vec3(frontMaterial.ambient);
  
  for (int index = 0; index < numberOfLights; index++) // for all light sources
    {
      if (0.0 == lights[index].position.w) // directional light?
	{
	  attenuation = 1.0; // no attenuation
	  lightDirection = normalize(vec3(lights[index].position));
	} 
      else // point light or spotlight (or other kind of light) 
	{
	  vec3 positionToLightSource = vec3(lights[index].position - position);
	  float distance = length(positionToLightSource);
	  lightDirection = normalize(positionToLightSource);
	  attenuation = 1.0 / (lights[index].constantAttenuation
			       + lights[index].linearAttenuation * distance
			       + lights[index].quadraticAttenuation * distance * distance);
	  
	  if (lights[index].spotCutoff <= 90.0) // spotlight?
	    {
	      float clampedCosine = max(0.0, dot(-lightDirection, normalize(lights[index].spotDirection)));
	      if (clampedCosine < cos(radians(lights[index].spotCutoff))) // outside of spotlight cone?
		{
		  attenuation = 0.0;
		}
	      else
		{
		  attenuation = attenuation * pow(clampedCosine, lights[index].spotExponent);   
		}
	    }
	}
      
      vec3 diffuseReflection = attenuation 
	* vec3(lights[index].diffuse) * vec3(frontMaterial.diffuse)
	* 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(lights[index].specular) * vec3(frontMaterial.specular) 
	    * pow(max(0.0, dot(reflect(-lightDirection, normalDirection), viewDirection)), frontMaterial.shininess);
	}

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

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

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

进一步阅读

[编辑 | 编辑源代码]

如果你还想了解更多


< GLSL 编程/GLUT

除非另有说明,本页上的所有示例源代码均归属公有领域。
返回OpenGL 编程 - 照明部分 返回GLSL 编程 - GLUT 部分
华夏公益教科书