跳转到内容

GLSL 编程/GLUT/双面曲面

来自维基教科书,开放的书籍,面向开放的世界
一个代数曲面,中心包含一个球形体。渲染使用不同的颜色来表示曲面的两侧。

本教程涵盖了双面逐顶点光照

它是关于 OpenGL 中基本光照的系列教程的一部分。在本教程中,我们扩展了关于镜面高光教程,以渲染双面曲面。如果您还没有阅读关于镜面高光教程,现在是时候阅读它了。

曲面法向量 N 和观察者方向 V 通常位于曲面的同一侧,但也存在例外情况;因此,不应该依赖于此。

双面光照

[编辑 | 编辑源代码]

如左侧图所示,有时对曲面的两侧使用不同的颜色很有用。在关于切面的教程中,我们已经看到了片段着色器如何使用内置变量gl_FrontFacing来确定片段是属于正面三角形还是背面三角形。顶点着色器也能确定它是否是正面三角形还是背面三角形的一部分吗?答案是明确的:不能!原因之一是同一个顶点可以同时属于正面背面三角形的一部分;因此,无论在顶点着色器中做出什么决定,它都可能对某些三角形是错误的。如果您想记住一个简单的规则:“片段要么是正面三角形,要么是背面三角形。顶点是双面的”。

因此,双面逐顶点光照必须让片段着色器确定应该应用正面材质颜色还是背面材质颜色。例如,使用此片段着色器

varying vec4 frontColor; // color for front face
varying vec4 backColor; // color for back face

void main()
{
  if (gl_FrontFacing) // is the fragment part of a front face?
    {
      gl_FragColor = frontColor;
    }
  else // fragment is part of a back face
    {
      gl_FragColor = backColor;
    }
}

另一方面,这意味着顶点着色器必须计算两次曲面光照:一次用于正面,一次用于背面。幸运的是,这通常仍然比为每个片段计算曲面光照工作量少。

顶点着色器代码

[编辑 | 编辑源代码]

用于双面逐顶点光照的顶点着色器代码是对关于镜面高光教程中代码的简单扩展。但是,我们必须禁用背面剔除,如关于透明度的教程中所述。此外,着色器需要两组材质参数(正面和背面),它们可以作为gl_FrontMaterialgl_BackMaterial提供。使用这两组参数,顶点着色器可以计算两种颜色,一种用于正面,一种用于背面,其中背面必须使用反向法向量。两种颜色都被传递给片段着色器,片段着色器决定应用哪种颜色。顶点着色器代码如下:

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 frontColor; // color for front face
varying vec4 backColor; // color for back face

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
);
material backMaterial = material(
  vec4(0.2, 0.2, 0.2, 1.0),
  vec4(0.0, 0.0, 1.0, 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);
	    }
	}
    }

  // Computation of lighting for front faces

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

  frontColor = vec4(ambientLighting + diffuseReflection + specularReflection, 1.0);

  // Computation of lighting for back faces (uses negative normalDirection and back material colors)

  vec3 backAmbientLighting = vec3(scene_ambient) * vec3(backMaterial.ambient);

  vec3 backDiffuseReflection = attenuation
    * vec3(light0.diffuse) * vec3(backMaterial.diffuse)
    * max(0.0, dot(-normalDirection, lightDirection));

  vec3 backSpecularReflection;
  if (dot(-normalDirection, lightDirection) < 0.0) // light source on the wrong side?
    {
      backSpecularReflection = vec3(0.0, 0.0, 0.0); // no specular reflection
    }
  else // light source on the right side
    {
      backSpecularReflection = attenuation * vec3(light0.specular) * vec3(backMaterial.specular)
	* pow(max(0.0, dot(reflect(-lightDirection, -normalDirection), viewDirection)),
	      backMaterial.shininess);
    }

  backColor = vec4(backAmbientLighting + backDiffuseReflection + backSpecularReflection, 1.0);

  gl_Position = mvp * v_coord;
}

片段着色器代码在上面描述过。

恭喜您,您完成了这个包含很长着色器的简短教程。我们已经看到了

  • 为什么顶点着色器无法区分正面和背面顶点(因为同一个顶点可能是正面三角形和背面三角形的一部分)。
  • 如何在顶点着色器中计算正面和背面的光照。
  • 如何让片段着色器决定应用哪种颜色。

进一步阅读

[编辑 | 编辑源代码]

如果您还想了解更多


< GLSL 编程/GLUT

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