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

本教程介紹了**半透明物體**。
它是關於照明的一些教程之一,超出了 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 章“即時近似次表面散射”,該書可以在 網上 獲取。