跳轉到內容

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 部分
華夏公益教科書