跳轉到內容

Cg 程式設計/Unity/半透明物體

來自華夏公益教科書,開放的書籍,開放的世界
中國漢代玉馬(公元前 206 年 - 公元 220 年)。注意馬鼻孔周圍幾乎像蠟一樣的照明。

本教程介紹了**半透明物體**。

它是關於照明的一些教程之一,超出了 Phong 反射模型。但是,它基於逐畫素照明,使用 Phong 反射模型,如“平滑鏡面高光”部分中所述。如果您尚未閱讀該教程,請先閱讀。

Phong 反射模型沒有考慮半透明性,即光線透過材料的可能性。雖然“半透明表面”部分處理了半透明表面,但本教程處理的是三維物體而不是薄表面。半透明材料的例子包括蠟、玉、大理石、皮膚等。

蠟制偶像。注意漫射光照的對比度降低。

蠟質感

[編輯 | 編輯原始碼]

不幸的是,半透明物體中的光線傳輸(即次表面散射)在即時遊戲引擎中非常具有挑戰性。從光源的角度渲染深度圖會有所幫助,但這超出了本教程的範圍。因此,我們將模擬次表面散射的一些效果。

第一個效果被稱為“蠟質感”,它描述了蠟的平滑、光澤的外觀,缺乏漫射反射所能提供的強烈對比度。理想情況下,我們希望在計算漫射反射(但不包括鏡面反射)之前平滑表面法線,事實上,如果使用法線貼圖,這是可能的。然而,這裡我們採取另一種方法。為了軟化漫射反射的強烈對比度,這是由 max(0, N·L) 項引起的(見“漫射反射”部分),隨著蠟質感 從 0 增加到 1,我們減少該項的影響。更具體地說,我們將 max(0, N·L) 項乘以 。然而,這不僅會降低對比度,還會降低整體亮度。為了避免這種情況,我們將蠟質感 新增到模擬由於次表面散射而產生的額外光線,該光線越強,材料的“蠟質感”越強。

因此,代替用於漫射反射的這個方程式

我們得到

其中蠟質感 在 0(即常規漫射反射)和 1(即不依賴於 N·L)之間。

這種方法易於實現、易於為 GPU 計算、易於控制,並且它確實類似於蠟和玉的外觀,尤其是在與具有高光澤度的鏡面高光結合使用時。

棋子在背光下。注意白色棋子的半透明性。

背光透射

[編輯 | 編輯原始碼]

我們要模擬的第二個效果是背光穿過物體並在物體可見的前部射出。這種效果越強,背部和前部的距離越小,即在輪廓處,背部和前部的距離實際上變為零。因此,我們可以使用“輪廓增強”部分中討論的技術來在輪廓處生成更多照明。然而,如果我們考慮閉合網格背面的實際漫射照明,這種效果會更有說服力。為此,我們按如下步驟進行

  • 我們只渲染背面,並計算以描述點(在背面)與輪廓的距離的因子加權的漫射反射。我們用 0 的不透明度標記畫素。(通常,幀緩衝區中的畫素具有 1 的不透明度。用將不透明度設定為 0 來標記畫素的技術基於在以後的通道中使用幀緩衝區中的 alpha 值進行混合的能力;參見“透明度”部分。)
  • 我們只渲染正面(以黑色),並將所有不透明度為 1 的畫素的顏色設定為黑色(即我們在第一步中未光柵化的所有畫素)。這是必要的,以防另一個物體與網格相交。
  • 我們再次使用來自正面的照明渲染正面,並將幀緩衝區中的顏色乘以一個因子,該因子描述點(在正面)與輪廓的距離。

在第一步和第三步中,我們使用輪廓因子 1 - |N·L|,它在輪廓處為 1,如果觀察者直視表面則為 0。(可以引入點積的指數以允許更多藝術控制。)因此,所有計算實際上都相當簡單。複雜的部分是混合。

該實現高度依賴於混合,這在 “透明度”部分 中進行了討論。除了對應於上述步驟的三個通道外,我們還需要兩個額外的通道來處理背面和正面上的額外光源。由於通道數量眾多,因此需要清楚地瞭解渲染通道應該做什麼。為此,沒有 Cg 程式碼的著色器骨架非常有用。

Shader "Cg translucent bodies" {
   Properties {
      _Color ("Diffuse Color", Color) = (1,1,1,1) 
      _Waxiness ("Waxiness", Range(0,1)) = 0
      _SpecColor ("Specular Color", Color) = (1,1,1,1) 
      _Shininess ("Shininess", Float) = 10
      _TranslucentColor ("Translucent Color", Color) = (0,0,0,1)
   }
   SubShader {
      Pass {      
         Tags { "LightMode" = "ForwardBase" } // pass for 
            // ambient light and first light source on back faces
         Cull Front // render back faces only
         Blend One Zero // mark rasterized pixels in framebuffer 
            // with alpha = 0 (usually they have alpha = 1)

         CGPROGRAM
         [...]
         ENDCG
      }
 
      Pass {      
         Tags { "LightMode" = "ForwardAdd" } 
            // pass for additional light sources on back faces
         Cull Front // render back faces only
         Blend One One // additive blending 
 
         CGPROGRAM
         [...]
         ENDCG
      }

      Pass {	
         Tags { "LightMode" = "ForwardBase" } // pass for 
            // setting pixels that were not rasterized to black
         Cull Back // render front faces only (default behavior)
         Blend Zero OneMinusDstAlpha // set colors of pixels 
            // with alpha = 1 to black by multiplying with 1-alpha

         CGPROGRAM

         #pragma vertex vert 
         #pragma fragment frag
 
         float4 vert(float4 vertexPos : POSITION) : SV_POSITION 
         {
            return mul(UNITY_MATRIX_MVP, vertexPos);
         }

         float4 frag(void) : COLOR 
         {
            return float4(0.0, 0.0, 0.0, 0.0); 
         }
         ENDCG  
      }

      Pass {      
         Tags { "LightMode" = "ForwardBase" } // pass for 
            // ambient light and first light source on front faces
         Cull Back // render front faces only
         Blend One SrcAlpha // multiply color in framebuffer 
            // with silhouetteness in fragment's alpha and add colors

         CGPROGRAM
         [...]
         ENDCG 
      }
 
      Pass {      
         Tags { "LightMode" = "ForwardAdd" } 
            // pass for additional light sources on front faces
         Cull Back // render front faces only
         Blend One One // additive blending 
 
         CGPROGRAM
         [...]
         ENDCG 
      }
   } 
   Fallback "Specular"
}

這個骨架已經相當長了;但是,它很好地說明了整個著色器是如何組織的。

完整的著色器程式碼

[編輯 | 編輯原始碼]

在以下完整的著色器程式碼中,請注意屬性 _TranslucentColor 而不是 _Color 用於計算背面的漫射和環境部分。還要注意“輪廓”是如何在背面和正面計算的;但是,它只直接乘以背面的片段顏色。在正面,它只是透過片段顏色的 alpha 分量以及該 alpha 與目標顏色(幀緩衝區中畫素的顏色)的混合間接地乘以。最後,“蠟質”僅用於正面的漫反射。

Shader "Cg translucent bodies" {
   Properties {
      _Color ("Diffuse Color", Color) = (1,1,1,1) 
      _Waxiness ("Waxiness", Range(0,1)) = 0
      _SpecColor ("Specular Color", Color) = (1,1,1,1) 
      _Shininess ("Shininess", Float) = 10
      _TranslucentColor ("Translucent Color", Color) = (0,0,0,1)
   }
   SubShader {
      Pass {      
         Tags { "LightMode" = "ForwardBase" } // pass for 
            // ambient light and first light source on back faces
         Cull Front // render back faces only
         Blend One Zero // mark rasterized pixels in framebuffer 
            // with alpha = 0 (usually they should have alpha = 1)
 
         CGPROGRAM
 
         #pragma vertex vert  
         #pragma fragment frag 
 
         #include "UnityCG.cginc"
         uniform float4 _LightColor0; 
            // color of light source (from "Lighting.cginc")
 
         // User-specified properties
         uniform float4 _Color; 
         uniform float _Waxiness;
         uniform float4 _SpecColor; 
         uniform float _Shininess;
         uniform float4 _TranslucentColor; 
 
         struct vertexInput {
            float4 vertex : POSITION;
            float3 normal : NORMAL;
         };
         struct vertexOutput {
            float4 pos : SV_POSITION;
            float4 posWorld : TEXCOORD0;
            float3 normalDir : TEXCOORD1;
         };
 
         vertexOutput vert(vertexInput input) 
         {
            vertexOutput output;
 
            float4x4 modelMatrix = _Object2World;
            float4x4 modelMatrixInverse = _World2Object; 
 
            output.posWorld = mul(modelMatrix, input.vertex);
            output.normalDir = normalize(
               mul(float4(input.normal, 0.0), modelMatrixInverse).xyz);
            output.pos = mul(UNITY_MATRIX_MVP, input.vertex);
            return output;
         }
 
         float4 frag(vertexOutput input) : COLOR
         {
            float3 normalDirection = normalize(input.normalDir);
 
            float3 viewDirection = normalize(
               _WorldSpaceCameraPos - input.posWorld.xyz);
            float3 lightDirection;
            float attenuation;
 
            if (0.0 == _WorldSpaceLightPos0.w) // directional light?
            {
               attenuation = 1.0; // no attenuation
               lightDirection = normalize(_WorldSpaceLightPos0.xyz);
            } 
            else // point or spot light
            {
               float3 vertexToLightSource = 
                  _WorldSpaceLightPos0.xyz - input.posWorld.xyz;
               float distance = length(vertexToLightSource);
               attenuation = 1.0 / distance; // linear attenuation 
               lightDirection = normalize(vertexToLightSource);
            }
 
            float3 ambientLighting = _TranslucentColor.rgb
               * UNITY_LIGHTMODEL_AMBIENT.rgb;
 
            float3 diffuseReflection = _TranslucentColor.rgb
               * attenuation * _LightColor0.rgb
               * max(0.0, dot(normalDirection, lightDirection));
 
 
            float silhouetteness = 
               1.0 - abs(dot(viewDirection, normalDirection));
 
            return float4(silhouetteness 
               * (ambientLighting + diffuseReflection), 0.0);
         }
 
         ENDCG
      }
 
      Pass {      
         Tags { "LightMode" = "ForwardAdd" } 
            // pass for additional light sources on back faces
         Cull Front // render back faces only
         Blend One One // additive blending 
 
         CGPROGRAM
 
         #pragma vertex vert  
         #pragma fragment frag 
 
         #include "UnityCG.cginc"
         uniform float4 _LightColor0; 
            // color of light source (from "Lighting.cginc")
 
         // User-specified properties
         uniform float4 _Color; 
         uniform float _Waxiness;
         uniform float4 _SpecColor; 
         uniform float _Shininess;
         uniform float4 _TranslucentColor; 
 
         struct vertexInput {
            float4 vertex : POSITION;
            float3 normal : NORMAL;
         };
         struct vertexOutput {
            float4 pos : SV_POSITION;
            float4 posWorld : TEXCOORD0;
            float3 normalDir : TEXCOORD1;
         };
 
         vertexOutput vert(vertexInput input) 
         {
            vertexOutput output;
 
            float4x4 modelMatrix = _Object2World;
            float4x4 modelMatrixInverse = _World2Object;
 
            output.posWorld = mul(modelMatrix, input.vertex);
            output.normalDir = normalize(
               mul(float4(input.normal, 0.0), modelMatrixInverse).xyz);
            output.pos = mul(UNITY_MATRIX_MVP, input.vertex);
            return output;
         }
 
         float4 frag(vertexOutput input) : COLOR
         {
            float3 normalDirection = normalize(input.normalDir);
 
            float3 viewDirection = normalize(
               _WorldSpaceCameraPos - input.posWorld.xyz);
            float3 lightDirection;
            float attenuation;
 
            if (0.0 == _WorldSpaceLightPos0.w) // directional light?
            {
               attenuation = 1.0; // no attenuation
               lightDirection = normalize(_WorldSpaceLightPos0.xyz);
            } 
            else // point or spot light
            {
               float3 vertexToLightSource = 
                  _WorldSpaceLightPos0.xyz - input.posWorld.xyz;
               float distance = length(vertexToLightSource);
               attenuation = 1.0 / distance; // linear attenuation 
               lightDirection = normalize(vertexToLightSource);
            }
 
            float3 diffuseReflection = _TranslucentColor.rgb
               * attenuation * _LightColor0.rgb
               * max(0.0, dot(normalDirection, lightDirection));
 
            float silhouetteness = 
               1.0 - abs(dot(viewDirection, normalDirection));
 
            return float4(silhouetteness * diffuseReflection, 0.0);
         }
 
         ENDCG
      }
 
      Pass {	
         Tags { "LightMode" = "ForwardBase" } // pass for 
            // setting pixels that were not rasterized to black
         Cull Back // render front faces only (default behavior)
         Blend Zero OneMinusDstAlpha // set colors of pixels 
            // with alpha = 1 to black by multiplying with 1-alpha
 
         CGPROGRAM 
 
         #pragma vertex vert 
         #pragma fragment frag
 
         float4 vert(float4 vertexPos : POSITION) : SV_POSITION 
         {
            return mul(UNITY_MATRIX_MVP, vertexPos);
         }
 
         float4 frag(void) : COLOR 
         {
            return float4(0.0, 0.0, 0.0, 0.0); 
         }
         ENDCG  
      }
 
      Pass {      
         Tags { "LightMode" = "ForwardBase" } // pass for 
            // ambient light and first light source on front faces
         Cull Back // render front faces only
         Blend One SrcAlpha // multiply color in framebuffer 
            // with silhouetteness in fragment's alpha and add colors
 
         CGPROGRAM
 
         #pragma vertex vert  
         #pragma fragment frag 
 
         #include "UnityCG.cginc"
         uniform float4 _LightColor0; 
            // color of light source (from "Lighting.cginc")
 
         // User-specified properties
         uniform float4 _Color; 
         uniform float _Waxiness;
         uniform float4 _SpecColor; 
         uniform float _Shininess;
         uniform float4 _TranslucentColor; 
 
         struct vertexInput {
            float4 vertex : POSITION;
            float3 normal : NORMAL;
         };
         struct vertexOutput {
            float4 pos : SV_POSITION;
            float4 posWorld : TEXCOORD0;
            float3 normalDir : TEXCOORD1;
         };
 
         vertexOutput vert(vertexInput input) 
         {
            vertexOutput output;
 
            float4x4 modelMatrix = _Object2World;
            float4x4 modelMatrixInverse = _World2Object;
 
            output.posWorld = mul(modelMatrix, input.vertex);
            output.normalDir = normalize(
               mul(float4(input.normal, 0.0), modelMatrixInverse).xyz);
            output.pos = mul(UNITY_MATRIX_MVP, input.vertex);
            return output;
         }
 
         float4 frag(vertexOutput input) : COLOR
         {
            float3 normalDirection = normalize(input.normalDir);
 
            float3 viewDirection = normalize(
               _WorldSpaceCameraPos - input.posWorld.xyz);
            float3 lightDirection;
            float attenuation;
 
            if (0.0 == _WorldSpaceLightPos0.w) // directional light?
            {
               attenuation = 1.0; // no attenuation
               lightDirection = normalize(_WorldSpaceLightPos0.xyz);
            } 
            else // point or spot light
            {
               float3 vertexToLightSource = 
                  _WorldSpaceLightPos0.xyz - input.posWorld.xyz;
               float distance = length(vertexToLightSource);
               attenuation = 1.0 / distance; // linear attenuation 
               lightDirection = normalize(vertexToLightSource);
            }
 
            float3 ambientLighting = 
               UNITY_LIGHTMODEL_AMBIENT.rgb * _Color.rgb;
 
            float3 diffuseReflection = 
               attenuation * _LightColor0.rgb * _Color.rgb
               * (_Waxiness + (1.0 - _Waxiness) 
               * max(0.0, dot(normalDirection, lightDirection)));
 
            float3 specularReflection;
            if (dot(normalDirection, lightDirection) < 0.0) 
               // light source on the wrong side?
            {
               specularReflection = float3(0.0, 0.0, 0.0); 
                  // no specular reflection
            }
            else // light source on the right side
            {
               specularReflection = attenuation * _LightColor0.rgb 
                  * _SpecColor.rgb * pow(max(0.0, dot(
                  reflect(-lightDirection, normalDirection), 
                  viewDirection)), _Shininess);
            }
 
            float silhouetteness = 
               1.0 - abs(dot(viewDirection, normalDirection));
 
            return float4(ambientLighting + diffuseReflection 
               + specularReflection, silhouetteness);
         }
 
         ENDCG 
      }
 
      Pass {      
         Tags { "LightMode" = "ForwardAdd" } 
            // pass for additional light sources on front faces
         Cull Back // render front faces only
         Blend One One // additive blending 
 
         CGPROGRAM
 
         #pragma vertex vert  
         #pragma fragment frag 
 
         #include "UnityCG.cginc"
         uniform float4 _LightColor0; 
            // color of light source (from "Lighting.cginc")
 
         // User-specified properties
         uniform float4 _Color; 
         uniform float _Waxiness;
         uniform float4 _SpecColor; 
         uniform float _Shininess;
         uniform float4 _TranslucentColor; 
 
         struct vertexInput {
            float4 vertex : POSITION;
            float3 normal : NORMAL;
         };
         struct vertexOutput {
            float4 pos : SV_POSITION;
            float4 posWorld : TEXCOORD0;
            float3 normalDir : TEXCOORD1;
         };
 
         vertexOutput vert(vertexInput input) 
         {
            vertexOutput output;
 
            float4x4 modelMatrix = unity_ObjectToWorld;
            float4x4 modelMatrixInverse = unity_WorldToObject;
 
            output.posWorld = mul(modelMatrix, input.vertex);
            output.normalDir = normalize(
               mul(float4(input.normal, 0.0), modelMatrixInverse).xyz);
            output.pos = mul(UNITY_MATRIX_MVP, input.vertex);
            return output;
         }
 
         float4 frag(vertexOutput input) : COLOR
         {
            float3 normalDirection = normalize(input.normalDir);
 
            float3 viewDirection = normalize(
               _WorldSpaceCameraPos - input.posWorld.xyz);
            float3 lightDirection;
            float attenuation;
 
            if (0.0 == _WorldSpaceLightPos0.w) // directional light?
            {
               attenuation = 1.0; // no attenuation
               lightDirection = normalize(_WorldSpaceLightPos0.xyz);
            } 
            else // point or spot light
            {
               float3 vertexToLightSource = 
                  _WorldSpaceLightPos0.xyz - input.posWorld.xyz;
               float distance = length(vertexToLightSource);
               attenuation = 1.0 / distance; // linear attenuation 
               lightDirection = normalize(vertexToLightSource);
            }
 
            float3 diffuseReflection = 
               attenuation * _LightColor0.rgb * _Color.rgb
               * (_Waxiness + (1.0 - _Waxiness) 
               * max(0.0, dot(normalDirection, lightDirection)));
 
            float3 specularReflection;
            if (dot(normalDirection, lightDirection) < 0.0) 
               // light source on the wrong side?
            {
               specularReflection = float3(0.0, 0.0, 0.0); 
                  // no specular reflection
            }
            else // light source on the right side
            {
               specularReflection = attenuation * _LightColor0.rgb 
                  * _SpecColor.rgb * pow(max(0.0, dot(
                  reflect(-lightDirection, normalDirection), 
                  viewDirection)), _Shininess);
            }
 
            float silhouetteness = 
               1.0 - abs(dot(viewDirection, normalDirection));
 
            return float4(diffuseReflection 
               + specularReflection, silhouetteness);
         }
 
         ENDCG 
      }
   } 
   Fallback "Specular"
}

恭喜!您完成了本關於半透明物體的教程,該教程主要關於

  • 如何模擬蠟的外觀。
  • 如何模擬背光照射的半透明材料的輪廓外觀。
  • 如何實現這些技術。

進一步閱讀

[編輯 | 編輯原始碼]

如果您還想了解更多

  • 關於半透明表面,您應該閱讀 “半透明表面”部分.
  • 關於 Phong 反射模型的漫射項,您應該閱讀 “漫反射”部分.
  • 關於 Phong 反射模型的環境項或鏡面項,您應該閱讀 “鏡面高光”部分.
  • 關於使用 Phong 反射模型的逐畫素照明,您應該閱讀 “平滑鏡面高光”部分.
  • 關於用於次表面散射的基本即時技術,您可以閱讀 Randima Fernando(編輯)於 2004 年出版的“GPU Gems”一書中 Simon Green 編寫的第 16 章“即時近似次表面散射”,該書可以在 網上 獲取。

< Cg 程式設計/Unity

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