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 迴圈在片段著色器中計算多個燈光的照明。
如果您還想了解更多
- 關於著色器程式碼的其他部分,您應該閱讀平滑鏡面高光教程。