跳转至内容

GLSL 编程/GLUT/平滑镜面高光

来自维基教科书,开放世界中的开放书籍
使用逐顶点光照渲染环面网格。
使用逐像素光照渲染环面网格。

本教程涵盖逐像素光照(也称为Phong 着色)。

它基于镜面高光教程。如果您还没有阅读过该教程,建议您先阅读。逐顶点光照(即为每个顶点计算表面光照,然后插值顶点颜色)的主要缺点是质量有限,特别是对于镜面高光而言,如左侧图所示。解决方法是逐像素光照,它根据插值的法线向量为每个片段计算光照。虽然生成的图像质量明显更高,但性能成本也很高。

逐像素光照(Phong 着色)

[编辑 | 编辑源代码]

逐像素光照也称为 Phong 着色(与逐顶点光照形成对比,逐顶点光照也称为 Gouraud 着色)。不要与 Phong 反射模型(也称为 Phong 光照)混淆,后者使用环境光、漫反射光和镜面光来计算表面光照,如镜面高光教程中所述。

逐像素光照的核心思想很容易理解:法线向量和位置为每个片段插值,并在片段着色器中计算光照。

着色器代码

[编辑 | 编辑源代码]

除了优化之外,基于逐顶点光照的着色器代码实现逐像素光照非常简单:光照计算从顶点着色器移至片段着色器,顶点着色器必须将光照计算所需的属性写入 varyings。然后,片段着色器使用这些 varyings 来计算光照(而不是顶点着色器使用的属性)。就是这样。

在本教程中,我们根据镜面高光教程中的着色器代码,将逐像素光照改造成逐顶点光照。最终的顶点着色器如下所示

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;
}

现在大部分工作都在片段着色器中完成

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;
};
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 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()
{
  vec3 normalDirection = normalize(varyingNormalDirection);
  vec3 viewDirection = normalize(vec3(v_inv * vec4(0.0, 0.0, 0.0, 1.0) - position));
  vec3 lightDirection;
  float attenuation;
  
  if (0.0 == light0.position.w) // directional light?
    {
      attenuation = 1.0; // no attenuation
      lightDirection = normalize(vec3(light0.position));
    } 
  else // point light or spotlight (or other kind of light) 
    {
      vec3 positionToLightSource = vec3(light0.position - position);
      float distance = length(positionToLightSource);
      lightDirection = normalize(positionToLightSource);
      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, 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(frontMaterial.ambient);
  
  vec3 diffuseReflection = attenuation 
    * vec3(light0.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(light0.specular) * vec3(frontMaterial.specular) 
	* pow(max(0.0, dot(reflect(-lightDirection, normalDirection), viewDirection)), frontMaterial.shininess);
    }
  
  gl_FragColor = vec4(ambientLighting + diffuseReflection + specularReflection, 1.0);
}

请注意,顶点着色器将归一化的向量写入 varyingNormalDirection,以确保所有方向在插值中都具有同等权重。片段着色器再次归一化它,因为插值后的方向不再是归一化的。

恭喜,现在您知道逐像素 Phong 光照是如何工作的。我们已经看到了

  • 为什么逐顶点光照提供的质量有时不够(特别是由于镜面高光)。
  • 逐像素光照是如何工作的,以及如何基于逐顶点光照的着色器实现它。

进一步阅读

[编辑 | 编辑源代码]

如果您还想了解更多


< GLSL 编程/GLUT

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