Blinn-Phong Shading Model
- 冯氏光照模型虽然能很好且高效的模拟光照,但是模型的镜面反射在特定条件下却会失效,特别是可能在亮度属性很小时产生一个很大的镜面区域。出现这种的情况的原因是视角和反射矢量的角度不能超过90度。如果角度大于90度,点积的结果为负导致镜面光分量值为0.0。(图片取自书中)
- 从上面的图中我们可以看出,右侧视角与反射矢量角度超过90,导致镜面光贡献被消除。这通常不是个问题,因为视角方向与反射方向距离很远,但是如果我们使用较小的镜面光指数,那么镜面光的半径将大到足够对场景产生影响(见后面的渲染结果对比图)。
-
Blinn-Phong模型与冯氏模型很相似,但是对镜面光采用了不同的处理方式。Blinn-Phong模型将反射矢量替换为一个半角向量或中间向量(halfway vector):一个刚好处于视角方向矢量与光照方向矢量中间的单位向量。半角向量与表面法向量越接近则镜面光的贡献越大。(图片取自书中)
- 从图中可知,无论从那个方向进行观看,半角向量和法向量的角度都不会超过90(除非光照方向远低于表面)。计算半角向量不难,我们将光线方向矢量与视角方向矢量相加并标准化:
- 上述计算公式转换为GLSL如下:
vec3 lightDir = normalize(lightPos - FragPos);
vec3 viewDir = normalize(viewPos- FragPos);
vec3 halfwayDir = normalize(lightDir + viewDir);
- 实际的镜面光计算则变为表面法向量和半角向量之间,同时应用镜面光的亮度指数。
float spec = pow(max(dot(normal, halfwayDir), 0.0), shininess);
vec3 specular = lightColor * spec;
-
下面是两种模型使用镜面光指数为0.5的效果。(注意冯氏模型光照区域及其边缘)
-
Blinn-Phong与冯氏模型的另外一个细微差别就是半角向量与法向量的角度比视角方向矢量与反射方向矢量的角度小。结果就是Blinn-Phong模型想要获得相近的可视化效果需要设置更大的镜面光亮度指数,通常使用冯氏摸的2到4倍。下面是冯氏模型采用8.0指数,Blinn-Phong模型采用32.0的效果。
- 一个能够在冯氏模型和Blinn-Phong模型间切换的片元的着色器。
#version 330 core
out vec4 FragColor;
uniform bool blinn;
uniform vec3 lightPos;
uniform vec3 viewPos;
in vec2 TexCoords;
in vec3 Normal;
in vec3 FragPos;
uniform sampler2D texture1;
void main()
{
vec3 objectColor = vec3(texture(texture1, TexCoords));
float ambientStrength = 0.05;
vec3 ambient = ambientStrength * objectColor;
vec3 norm = normalize(Normal);
vec3 lightDir = normalize(lightPos - FragPos);
float diff = max(dot(norm, lightDir), 0.0);
vec3 diffuse = diff * objectColor;
vec3 viewDir = normalize(viewPos - FragPos);
float spec = 0.0;
if(blinn)
{
vec3 halfwayDir = normalize(lightDir + viewDir);
spec = pow(max(dot(Normal, halfwayDir), 0.0), 32.0);
}
else
{
vec3 reflectDir = reflect(-lightDir, Normal);
spec = pow(max(dot(viewDir, reflectDir), 0.0), 8.0);
}
vec3 specular = spec * vec3(0.3);
vec3 result = (ambient + diffuse + specular);
FragColor = vec4(result, 1.0);
}
- 通过按键
B
进行模型切换。
bool blinn = false;
bool blinnKeyPressed = false;
...
void processInput(GLFWwindow* window)
{
...
if (glfwGetKey(window, GLFW_KEY_B) == GLFW_PRESS && !blinnKeyPressed)
{
blinn = !blinn;
blinnKeyPressed = true;
}
if (glfwGetKey(window, GLFW_KEY_B) == GLFW_RELEASE)
{
blinnKeyPressed = false;
}
}