跳转到内容

GLSL 编程/GLUT/镜面高光

来自维基教科书,开放的书籍,为了一个开放的世界
“弹奏鲁特琴的阿波罗”(巴德明顿庄园版本)由米开朗基罗·梅里西·达·卡拉瓦乔创作,约 1596 年。

本教程涵盖逐顶点光照(也称为Gouraud 着色)使用Phong 反射模型

它扩展了漫反射教程中的着色器代码,添加了两个附加项:环境光和镜面反射。这三个项共同构成了 Phong 反射模型。如果你还没有阅读漫反射教程,现在是一个很好的机会去阅读它。

环境光

[编辑 | 编辑源代码]

考虑左侧卡拉瓦乔的画作。虽然白衬衫的大部分都在阴影中,但没有一部分是完全黑色的。光线从墙壁、桌子和其他物体反射,间接照亮了场景中的所有东西。在 Phong 反射模型中,这种效果通过环境光来考虑,它依赖于一般的环境光强度和漫反射的材质颜色。在环境光强度的公式中

类似于漫反射教程中的漫反射方程,该方程也可以解释为光线红、绿、蓝分量的向量方程。

在 OpenGL 兼容性配置文件中,此颜色可用作gl_LightModel.ambient,它是视图空间着色教程中提到的预定义统一变量之一。我们将把它指定为scene_ambient

镜面反射的计算需要表面法向量 N、光源方向 L、反射光源方向 R 和观察者方向 V。

镜面高光

[编辑 | 编辑源代码]

如果你仔细观察卡拉瓦乔的画作,你会看到几个镜面高光:在鼻子上、头发上、嘴唇上、鲁特琴上、小提琴上、弓上、水果上等等。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反射模型中,RV之间角度的余弦被提升到 次方来生成不同光泽度的亮点。类似于 漫反射 的情况,我们应该将负余弦钳制到 0。此外,镜面项需要镜面反射的材质颜色 ,它通常是白色,这样所有亮点都具有入射光 的颜色。例如,卡拉瓦乔的所有绘画中的亮点都是白色的。Phong反射模型的镜面项如下

类似于 漫反射 的情况,如果光源位于表面的“错误”一侧,则应忽略镜面项;即如果点积 N·L 为负。

着色器代码

[edit | edit source]

环境光照的着色器代码很简单,只需进行逐分量向量-向量乘积

  vec3 ambientLighting = vec3(scene_ambient) * vec3(mymaterial.ambient);

为了实现镜面反射,我们需要世界空间中的观察者方向,我们可以将其计算为相机位置和顶点位置(都位于世界空间)之间的差。视空间中的相机位置相当简单:它位于视空间中的原点 ,并可以通过应用逆视图矩阵将其转换为世界空间;见 “顶点变换”。顶点位置可以如 漫反射教程 中所述转换为世界空间。世界空间中镜面项的方程可以这样实现

  vec3 viewDirection = normalize(vec3(v_inv * vec4(0.0, 0.0, 0.0, 1.0) - m * v_coord));

  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
    {
      specularReflection = attenuation * vec3(light0.specular) * vec3(mymaterial.specular)
	* pow(max(0.0, dot(reflect(-lightDirection, normalDirection), viewDirection)),
	      mymaterial.shininess);
    }

此代码片段使用了与 漫反射教程 中的着色器代码相同的变量,以及变量 mymaterial.specularmymaterial.shininesspow(a, b) 计算

如果环境光照和镜面反射被添加到 漫反射教程 的完整顶点着色器中,它看起来像这样

attribute vec4 v_coord;
attribute vec3 v_normal;
uniform mat4 m, v, p;
uniform mat3 m_3x3_inv_transp;
uniform mat4 v_inv;
varying vec4 color;

struct lightSource
{
  vec4 position;
  vec4 diffuse;
  vec4 specular;
  float constantAttenuation, linearAttenuation, quadraticAttenuation;
  float spotCutoff, spotExponent;
  vec3 spotDirection;
};
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)
);
vec4 scene_ambient = vec4(0.2, 0.2, 0.2, 1.0);

struct material
{
  vec4 ambient;
  vec4 diffuse;
  vec4 specular;
  float shininess;
};
material mymaterial = 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(void)
{
  mat4 mvp = p*v*m;
  vec3 normalDirection = normalize(m_3x3_inv_transp * v_normal);
  vec3 viewDirection = normalize(vec3(v_inv * vec4(0.0, 0.0, 0.0, 1.0) - m * v_coord));
  vec3 lightDirection;
  float attenuation;

  if (light0.position.w == 0.0) // directional light
    {
      attenuation = 1.0; // no attenuation
      lightDirection = normalize(vec3(light0.position));
    }
  else // point or spot light (or other kind of light)
    {
      vec3 vertexToLightSource = vec3(light0.position - m * v_coord);
      float distance = length(vertexToLightSource);
      lightDirection = normalize(vertexToLightSource);
      attenuation = 1.0 / (light0.constantAttenuation
			   + light0.linearAttenuation * distance
			   + light0.quadraticAttenuation * distance * distance);

      if (light0.spotCutoff <= 90.0) // spotlight
	{
	  float clampedCosine = max(0.0, dot(-lightDirection, normalize(light0.spotDirection)));
	  if (clampedCosine < cos(radians(light0.spotCutoff))) // outside of spotlight cone
	    {
	      attenuation = 0.0;
	    }
	  else
	    {
              attenuation = attenuation * pow(clampedCosine, light0.spotExponent);
	    }
	}
    }

  vec3 ambientLighting = vec3(scene_ambient) * vec3(mymaterial.ambient);

  vec3 diffuseReflection = attenuation
    * vec3(light0.diffuse) * vec3(mymaterial.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(light0.specular) * vec3(mymaterial.specular)
	* pow(max(0.0, dot(reflect(-lightDirection, normalDirection), viewDirection)),
	      mymaterial.shininess);
    }

  color = vec4(ambientLighting + diffuseReflection + specularReflection, 1.0);
  gl_Position = mvp * v_coord;
}

片元着色器仍然是

varying vec4 color;

void main(void)
{
  gl_FragColor = color;
}

在您的 C++ 代码中,更新 v_inv 统一变量

  glm::mat4 v_inv = glm::inverse(world2camera);
  glUniformMatrix4fv(uniform_v_inv, 1, GL_FALSE, glm::value_ptr(v_inv));

总结

[edit | edit source]

恭喜,您刚刚学习了如何实现Phong反射模型。特别是,我们已经看到了

  • Phong反射模型中的环境光照是什么。
  • Phong反射模型中的镜面反射项是什么。
  • 如何在 GLSL 中实现这些项。

进一步阅读

[edit | edit source]

如果您还想了解更多



< GLSL编程/GLUT

除非另有说明,否则本页上的所有示例源代码均授予公有领域。
返回 OpenGL 编程 - 光照部分 返回 GLSL 编程 - GLUT 部分
华夏公益教科书