GLSL 程式設計/GLUT/多光源
外觀
本教程涵蓋了**多光源照明**。
本教程是平滑鏡面高光教程的擴充套件。如果你還沒有閱讀過該教程,你應該先閱讀它。
在之前的關於漫反射等教程中,我們只使用了一個光源,它由變數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 迴圈在片段著色器中計算多個光源的照明。
如果你還想了解更多
- 關於著色器程式碼的其他部分,你應該閱讀平滑鏡面高光教程。
除非另有說明,本頁上的所有示例原始碼均歸屬公有領域。
| 返回OpenGL 程式設計 - 照明部分 | 返回GLSL 程式設計 - GLUT 部分 |