GLSL 编程/Blender/镜面高光
本教程介绍了使用Phong 反射模型的逐顶点光照(也称为Gouraud 着色)。
它在漫反射教程的基础上扩展了着色器代码,增加了两个额外的项:环境光和镜面反射。这三个项共同构成了 Phong 反射模型。如果你还没有阅读漫反射教程,现在是一个很好的机会。
仔细观察左侧卡拉瓦乔的这幅画。虽然白色衬衫的大部分区域都在阴影中,但没有一个部分是完全黑色的。显然,始终有一些光从墙壁和其他物体反射出来,照亮场景中的所有物体——至少在一定程度上。在 Phong 反射模型中,这种效应是由环境光来考虑的,它取决于一个普遍的环境光强度 和漫反射的材质颜色 。环境光强度的公式
与漫反射教程中漫反射公式类似,此公式也可以解释为光线红、绿、蓝分量的向量方程。
在 Blender 中,环境光在属性窗口的世界选项卡中指定。对于特定材质,该颜色会与属性窗口的材质选项卡中的着色>环境相乘,以便控制每种材质对环境色的影响。在 Blender 中的 GLSL 着色器中,该乘积以 gl_LightModel.ambient
的形式提供,它是视空间着色教程中提到的 OpenGL 兼容性配置文件的预定义统一变量之一。
如果你仔细观察卡拉瓦乔的这幅画,你会看到几个镜面高光:在鼻子、头发、嘴唇、鲁特琴、小提琴、弓、水果等等。Phong 反射模型包含一个镜面反射项,可以模拟光滑表面上的这种高光;它甚至包含一个参数 来指定材质的光泽度。光泽度指定高光的大小:光泽度越高,高光越小。
一个完全光滑的表面只会将来自光源的光反射到几何反射方向R。对于光滑度低于完全光滑的表面,光线会反射到R周围的方向:光泽度越低,扩散范围越广。在数学上,标准化的反射方向R 由以下公式定义:
对于标准化的表面法向量N 和标准化的指向光源方向L。在 GLSL 中,函数 vec3 reflect(vec3 I, vec3 N)
(或 vec4 reflect(vec4 I, vec4 N)
)计算相同的反射向量,但针对从光源到表面上的点的方向 I
。因此,我们必须取反我们的方向L 以使用此函数。
镜面反射项计算指向观察者方向V 的镜面反射。如上所述,如果V 接近R,则强度应该很大,其中“接近度”由光泽度 参数化。在 Phong 反射模型中,R 和V 之间角度的余弦值被取到 次方,以生成不同光泽度的亮点。与漫反射的情况类似,我们应该将负余弦值钳制到 0。此外,镜面项需要一个材质颜色 用于镜面反射,它通常是白色,这样所有高光都具有入射光 的颜色。例如,卡拉瓦乔这幅画中的所有高光都是白色的。Phong 反射模型的镜面项为:
类似于漫反射的情况,如果光源位于表面的“错误”一侧,则应忽略镜面反射项;即,如果点积N·L为负值。
着色器代码
[edit | edit source]环境光照的着色器代码可以用逐分量向量-向量乘积来实现,这很简单
vec3 ambientLighting = vec3(gl_LightModel.ambient)
* vec3(gl_FrontMaterial.diffuse);
但是,正如在视图空间着色教程中所提到的,Blender不会设置gl_FrontMaterial.diffuse
(也不设置gl_FrontMaterial.ambient
);因此,我们使用gl_FrontMaterial.emission
vec3 ambientLighting = vec3(gl_LightModel.ambient)
* vec3(gl_FrontMaterial.emission);
为了实现镜面反射,我们需要在视图空间中获得观察者方向,我们可以通过计算相机位置和顶点位置(都在视图空间中)之间的差来获得。相机在视图空间中的位置很简单,因为视图空间的定义是相机位置位于原点;参见“顶点变换”。顶点位置可以按照漫反射教程中讨论的方式转换为视图空间。然后,可以在世界空间中像这样实现镜面反射项的方程
vec3 viewDirection =
-normalize(vec3(gl_ModelViewMatrix * gl_Vertex));
// == vec3(0.0, 0.0, 0.0) - ...
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[0].specular)
* vec3(gl_FrontMaterial.specular)
* pow(max(0.0, dot(reflect(-lightDirection,
normalDirection), viewDirection)),
gl_FrontMaterial.shininess);
}
此代码片段使用了与漫反射教程中的着色器代码相同的变量,另外还使用了内置的uniform变量gl_FrontMaterial.specular
和gl_FrontMaterial.shininess
。(正如在视图空间着色教程中提到的那样,这些变量由用户在Blender中指定。)pow(a, b)
计算.
如果将环境光照和镜面反射添加到漫反射教程中的完整顶点着色器中,它将如下所示
varying vec4 color;
void main()
{
vec3 normalDirection =
normalize(gl_NormalMatrix * gl_Normal);
vec3 viewDirection =
-normalize(vec3(gl_ModelViewMatrix * gl_Vertex));
vec3 lightDirection;
float attenuation;
if (0.0 == gl_LightSource[0].position.w)
// directional light?
{
attenuation = 1.0; // no attenuation
lightDirection =
normalize(vec3(gl_LightSource[0].position));
}
else // point light or spotlight (or other kind of light)
{
vec3 vertexToLightSource =
vec3(gl_LightSource[0].position
- gl_ModelViewMatrix * gl_Vertex);
float distance = length(vertexToLightSource);
attenuation = 1.0 / distance; // linear attenuation
lightDirection = normalize(vertexToLightSource);
if (gl_LightSource[0].spotCutoff <= 90.0) // spotlight?
{
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 ambientLighting = vec3(gl_LightModel.ambient)
* vec3(gl_FrontMaterial.emission);
vec3 diffuseReflection = attenuation
* vec3(gl_LightSource[0].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[0].specular)
* vec3(gl_FrontMaterial.specular)
* pow(max(0.0, dot(reflect(-lightDirection,
normalDirection), viewDirection)),
gl_FrontMaterial.shininess);
}
color = vec4(ambientLighting + diffuseReflection
+ specularReflection, 1.0);
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
}
片段着色器仍然是
varying vec4 color;
void main()
{
gl_FragColor = color;
}
总结
[edit | edit source]恭喜你,你刚学会了如何实现Phong反射模型。特别是,我们看到了
- Phong反射模型中的环境光照是什么。
- Phong反射模型中的镜面反射项是什么。
- 如何在Blender中使用GLSL实现这些项。
进一步阅读
[edit | edit source]如果你还想了解更多
- 关于着色器代码,你应该阅读漫反射教程.