跳轉至內容

GLSL 程式設計/Blender/雙面曲面

來自華夏公益教科書
一個代數曲面,其中包含一箇中心為環面的曲面。渲染使用不同的顏色表示曲面的兩側。

本教程介紹了雙面逐頂點光照

它是關於 Blender 中基本光照的系列教程的一部分。在本教程中,我們將擴充套件鏡面高光教程來渲染雙面曲面。如果您還沒有閱讀鏡面高光教程,現在是一個很好的時機來閱讀它。

曲面法向量 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使用。使用這兩組引數,頂點著色器可以計算出兩種顏色,一種用於正面,一種用於背面,其中對於背面必須使用取反的法向量。兩種顏色都傳遞給片段著色器,由它決定應用哪種顏色。不幸的是,Blender 似乎沒有辦法為正面和背面指定不同的材質。相反,gl_FrontMaterialgl_BackMaterial始終包含相同的資料。因此,在 Blender 中,您將不得不引入新的統一變數或常量來為正面和背面材質指定不同的引數。但是,在本教程中,我們只是假設 Blender 在gl_FrontMaterialgl_BackMaterial中使用不同的引數。頂點著色器程式碼如下

         varying vec4 frontColor; // the lighting of front faces 
            // that was computed in the vertex shader
         varying vec4 backColor; // the lighting of back faces 
            // that was computed in the vertex shader
 
         void main()
         {                              
            vec3 normalDirection = 
               normalize(gl_NormalMatrix * gl_Normal);
            vec3 viewDirection = 
               -normalize(vec3(gl_ModelViewMatrix * gl_Vertex)); 
            vec3 lightDirection;
            float attenuation;
 
            if (0.0 == gl_LightSource[0].position.w) 
               // directional light?
            {
               attenuation = 1.0; // no attenuation
               lightDirection = 
                  normalize(vec3(gl_LightSource[0].position));
            } 
            else // point light or spotlight (or other kind of light) 
            {
               vec3 vertexToLightSource = 
                  vec3(gl_LightSource[0].position 
                  - gl_ModelViewMatrix * gl_Vertex);
               float distance = length(vertexToLightSource);
               attenuation = 1.0 / distance; // linear attenuation 
               lightDirection = normalize(vertexToLightSource);
 
               if (gl_LightSource[0].spotCutoff <= 90.0) // spotlight?
               {
                  float clampedCosine = max(0.0, dot(-lightDirection, 
                     gl_LightSource[0].spotDirection));
                  if (clampedCosine < gl_LightSource[0].spotCosCutoff) 
                     // outside of spotlight cone?
                  {
                     attenuation = 0.0;
                  }
                  else
                  {
                     attenuation = attenuation * pow(clampedCosine, 
                        gl_LightSource[0].spotExponent);
                  }
               }
            }

            // Computation of lighting for front faces
           
            vec3 ambientLighting = vec3(gl_LightModel.ambient) 
               * vec3(gl_FrontMaterial.emission);
              
            vec3 diffuseReflection = attenuation 
               * vec3(gl_LightSource[0].diffuse) 
               * vec3(gl_FrontMaterial.emission)
               * 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(gl_LightSource[0].specular) 
                  * vec3(gl_FrontMaterial.specular) 
                  * pow(max(0.0, dot(reflect(-lightDirection, 
                  normalDirection), viewDirection)), 
                  gl_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(gl_LightModel.ambient) 
               * vec3(gl_BackMaterial.emission);
              
            vec3 backDiffuseReflection = attenuation 
               * vec3(gl_LightSource[0].diffuse) 
               * vec3(gl_BackMaterial.emission)
               * 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(gl_LightSource[0].specular) 
                  * vec3(gl_BackMaterial.specular) 
                  * pow(max(0.0, dot(reflect(-lightDirection, 
                  -normalDirection), viewDirection)), 
                  gl_BackMaterial.shininess);
            }

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

            gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
         }

片段著色器程式碼如上所述。

恭喜您完成了本篇簡短教程,其中包含一個很長的著色器。我們已經瞭解到

  • 為什麼頂點著色器無法區分正面頂點和背面頂點(因為同一個頂點可能既是正面三角形的一部分,又是背面三角形的一部分)。
  • 如何在頂點著色器中計算正面和背面的光照。
  • 如何讓片段著色器決定應用哪種顏色。

進一步閱讀

[編輯 | 編輯原始碼]

如果您還想了解更多


< GLSL 程式設計/Blender

除非另有說明,否則本頁上的所有示例原始碼均授予公有領域。
華夏公益教科書