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 循环在片段着色器中计算多个灯光的照明。
如果您还想了解更多
- 关于着色器代码的其他部分,您应该阅读平滑镜面高光教程。