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

本教程涵蓋半透明物體。
它是關於光照的幾個教程之一,這些教程超出了 Phong 反射模型。但是,它基於每個畫素的光照,使用 Phong 反射模型,如 “平滑鏡面高光”部分 中所述。如果您還沒有閱讀本教程,請先閱讀。
Phong 反射模型沒有考慮半透明性,即光線穿過材料的可能性。雖然 “半透明表面”部分 處理了半透明表面,但本教程處理的是三維物體而不是薄表面。半透明材料的例子包括蠟、玉石、大理石、皮膚等。

不幸的是,半透明物體中的光線傳輸(即次表面散射)在即時遊戲引擎中相當具有挑戰性。從光源的角度渲染深度圖會有所幫助,但由於本教程僅限於 Unity 的免費版本,因此這種方法不可取。因此,我們將模擬次表面散射的一些效果。
第一個效果將被稱為“蠟狀”,描述了蠟的光滑、有光澤的外觀,它缺乏漫反射可以提供的強烈對比。理想情況下,我們希望在計算漫反射(但不計算鏡面反射)之前平滑表面法線,實際上,如果使用法線貼圖,這是可能的。然而,這裡我們採取了另一種方法。為了軟化漫反射的強烈對比,這是由 max(0, N·L) 項引起的(參見 “漫反射”部分),我們減少了該項的影響,因為蠟狀 從 0 增加到 1。更具體地說,我們將 max(0, N·L) 項乘以 。但是,這不僅會降低對比度,還會降低照明的整體亮度。為了避免這種情況,我們將蠟狀 新增到模擬由於次表面散射而產生的額外光線,該光線越“蠟狀”,強度就越強。
因此,代替漫反射的這個方程
我們得到
蠟狀 在 0(即常規漫反射)和 1(即不依賴於 N·L)之間。
這種方法易於實現,易於 GPU 計算,易於控制,並且它確實類似於蠟和玉石的外觀,特別是如果與具有高光澤度的鏡面高光相結合。

我們將要模擬的第二個效果是背光穿過物體並在物體可見的前部出射。這種效果越強,背部和前部之間的距離越小,即特別是在輪廓處,背部和前部之間的距離實際上變為零。因此,我們可以使用 “輪廓增強”部分 中討論的技術來在輪廓處生成更多照明。但是,如果我們將封閉網格背面的實際漫反射照明考慮在內,這種效果會更有說服力。為此,我們按以下步驟進行
- 我們只渲染背面,並計算漫反射,該漫反射用一個因子加權,該因子描述了該點(在背面)離輪廓的距離。我們將畫素標記為 0 的不透明度。(通常,幀緩衝區中的畫素具有 1 的不透明度。透過將畫素的不透明度設定為 0 來標記畫素的技術也被使用,並在 “鏡子”部分 中更詳細地解釋。)
- 我們只渲染正面(以黑色),並將所有不透明度為 1 的畫素的顏色設定為黑色(即我們在第一步中沒有光柵化的所有畫素)。如果另一個物體與網格相交,這將是必要的。
- 我們再次渲染正面,使用來自正面的照明,並將幀緩衝區中的顏色加上一個因子,該因子描述了該點(在正面)離輪廓的距離。
在第一步和第三步中,我們使用輪廓因子 1 - |N·L|,它在輪廓處為 1,如果觀察者直接看向表面,則為 0。(可以為點積引入指數以允許更多藝術控制。)因此,所有計算實際上都相當簡單。複雜的部分是混合。
該實現嚴重依賴於混合,這在 “透明度”部分 中討論。除了對應於上面提到的步驟的三次傳遞之外,我們還需要兩次額外的傳遞,用於在背面和正面的額外光源。有了這麼多傳遞,明確瞭解渲染傳遞應該做什麼是有意義的。為此,沒有 GLSL 程式碼的著色器框架非常有用
Shader "GLSL 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)
GLSLPROGRAM
[...]
ENDGLSL
}
Pass {
Tags { "LightMode" = "ForwardAdd" }
// pass for additional light sources on back faces
Cull Front // render back faces only
Blend One One // additive blending
GLSLPROGRAM
[...]
ENDGLSL
}
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
GLSLPROGRAM
#ifdef VERTEX
void main() {
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
}
#endif
#ifdef FRAGMENT
void main() { gl_FragColor = vec4(0.0); }
#endif
ENDGLSL
}
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
GLSLPROGRAM
[...]
ENDGLSL
}
Pass {
Tags { "LightMode" = "ForwardAdd" }
// pass for additional light sources on front faces
Cull Back // render front faces only
Blend One One // additive blending
GLSLPROGRAM
[...]
ENDGLSL
}
}
// The definition of a fallback shader should be commented out
// during development:
// Fallback "Specular"
}
這個骨架已經很長了; 然而,它很好地說明了整體著色器是如何組織的。
在以下完整的著色器程式碼中,請注意,在背面計算漫反射和環境光部分時,使用屬性 _TranslucentColor 而不是 _Color。另外還要注意,如何計算背面和正面上的“輪廓感”; 但是,它只直接乘以背面的片段顏色。在正面,它只通過片段顏色的 alpha 分量以及這個 alpha 與目標顏色(幀緩衝區中畫素的顏色)的混合進行間接乘法。最後,“蠟質”僅用於正面的漫反射。
Shader "GLSL 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)
GLSLPROGRAM
// User-specified properties
uniform vec4 _Color;
uniform float _Waxiness;
uniform vec4 _SpecColor;
uniform float _Shininess;
uniform vec4 _TranslucentColor;
// The following built-in uniforms (except _LightColor0)
// are also defined in "UnityCG.glslinc",
// i.e. one could #include "UnityCG.glslinc"
uniform vec3 _WorldSpaceCameraPos;
// camera position in world space
uniform mat4 _Object2World; // model matrix
uniform mat4 _World2Object; // inverse model matrix
uniform vec4 _WorldSpaceLightPos0;
// direction to or position of light source
uniform vec4 _LightColor0;
// color of light source (from "Lighting.cginc")
varying vec4 position;
// position of the vertex (and fragment) in world space
varying vec3 varyingNormalDirection;
// surface normal vector in world space
#ifdef VERTEX
void main()
{
mat4 modelMatrix = _Object2World;
mat4 modelMatrixInverse = _World2Object; // unity_Scale.w
// is unnecessary because we normalize vectors
position = modelMatrix * gl_Vertex;
varyingNormalDirection = normalize(vec3(
vec4(gl_Normal, 0.0) * modelMatrixInverse));
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
}
#endif
#ifdef FRAGMENT
void main()
{
vec3 normalDirection = normalize(varyingNormalDirection);
vec3 viewDirection =
normalize(_WorldSpaceCameraPos - vec3(position));
vec3 lightDirection;
float attenuation;
if (0.0 == _WorldSpaceLightPos0.w) // directional light?
{
attenuation = 1.0; // no attenuation
lightDirection = normalize(vec3(_WorldSpaceLightPos0));
}
else // point or spot light
{
vec3 vertexToLightSource =
vec3(_WorldSpaceLightPos0 - position);
float distance = length(vertexToLightSource);
attenuation = 1.0 / distance; // linear attenuation
lightDirection = normalize(vertexToLightSource);
}
vec3 ambientLighting =
vec3(gl_LightModel.ambient) * vec3(_TranslucentColor);
vec3 diffuseReflection = attenuation
* vec3(_LightColor0) * vec3(_TranslucentColor)
* max(0.0, dot(normalDirection, lightDirection));
float silhouetteness =
1.0 - abs(dot(viewDirection, normalDirection));
gl_FragColor = vec4(silhouetteness
* (ambientLighting + diffuseReflection), 0.0);
}
#endif
ENDGLSL
}
Pass {
Tags { "LightMode" = "ForwardAdd" }
// pass for additional light sources on back faces
Cull Front // render back faces only
Blend One One // additive blending
GLSLPROGRAM
// User-specified properties
uniform vec4 _Color;
uniform float _Waxiness;
uniform vec4 _SpecColor;
uniform float _Shininess;
uniform vec4 _TranslucentColor;
// The following built-in uniforms (except _LightColor0)
// are also defined in "UnityCG.glslinc",
// i.e. one could #include "UnityCG.glslinc"
uniform vec3 _WorldSpaceCameraPos;
// camera position in world space
uniform mat4 _Object2World; // model matrix
uniform mat4 _World2Object; // inverse model matrix
uniform vec4 _WorldSpaceLightPos0;
// direction to or position of light source
uniform vec4 _LightColor0;
// color of light source (from "Lighting.cginc")
varying vec4 position;
// position of the vertex (and fragment) in world space
varying vec3 varyingNormalDirection;
// surface normal vector in world space
#ifdef VERTEX
void main()
{
mat4 modelMatrix = _Object2World;
mat4 modelMatrixInverse = _World2Object; // unity_Scale.w
// is unnecessary because we normalize vectors
position = modelMatrix * gl_Vertex;
varyingNormalDirection = normalize(vec3(
vec4(gl_Normal, 0.0) * modelMatrixInverse));
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
}
#endif
#ifdef FRAGMENT
void main()
{
vec3 normalDirection = normalize(varyingNormalDirection);
vec3 viewDirection =
normalize(_WorldSpaceCameraPos - vec3(position));
vec3 lightDirection;
float attenuation;
if (0.0 == _WorldSpaceLightPos0.w) // directional light?
{
attenuation = 1.0; // no attenuation
lightDirection = normalize(vec3(_WorldSpaceLightPos0));
}
else // point or spot light
{
vec3 vertexToLightSource =
vec3(_WorldSpaceLightPos0 - position);
float distance = length(vertexToLightSource);
attenuation = 1.0 / distance; // linear attenuation
lightDirection = normalize(vertexToLightSource);
}
vec3 diffuseReflection = attenuation
* vec3(_LightColor0) * vec3(_TranslucentColor)
* max(0.0, dot(normalDirection, lightDirection));
float silhouetteness =
1.0 - abs(dot(viewDirection, normalDirection));
gl_FragColor =
vec4(silhouetteness * diffuseReflection, 0.0);
}
#endif
ENDGLSL
}
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
GLSLPROGRAM
#ifdef VERTEX
void main()
{
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
}
#endif
#ifdef FRAGMENT
void main()
{
gl_FragColor = vec4(0.0);
}
#endif
ENDGLSL
}
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
GLSLPROGRAM
// User-specified properties
uniform vec4 _Color;
uniform float _Waxiness;
uniform vec4 _SpecColor;
uniform float _Shininess;
uniform vec4 _TranslucentColor;
// The following built-in uniforms (except _LightColor0)
// are also defined in "UnityCG.glslinc",
// i.e. one could #include "UnityCG.glslinc"
uniform vec3 _WorldSpaceCameraPos;
// camera position in world space
uniform mat4 _Object2World; // model matrix
uniform mat4 _World2Object; // inverse model matrix
uniform vec4 _WorldSpaceLightPos0;
// direction to or position of light source
uniform vec4 _LightColor0;
// color of light source (from "Lighting.cginc")
varying vec4 position;
// position of the vertex (and fragment) in world space
varying vec3 varyingNormalDirection;
// surface normal vector in world space
#ifdef VERTEX
void main()
{
mat4 modelMatrix = _Object2World;
mat4 modelMatrixInverse = _World2Object; // unity_Scale.w
// is unnecessary because we normalize vectors
position = modelMatrix * gl_Vertex;
varyingNormalDirection = normalize(vec3(
vec4(gl_Normal, 0.0) * modelMatrixInverse));
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
}
#endif
#ifdef FRAGMENT
void main()
{
vec3 normalDirection = normalize(varyingNormalDirection);
vec3 viewDirection =
normalize(_WorldSpaceCameraPos - vec3(position));
vec3 lightDirection;
float attenuation;
if (0.0 == _WorldSpaceLightPos0.w) // directional light?
{
attenuation = 1.0; // no attenuation
lightDirection = normalize(vec3(_WorldSpaceLightPos0));
}
else // point or spot light
{
vec3 vertexToLightSource =
vec3(_WorldSpaceLightPos0 - position);
float distance = length(vertexToLightSource);
attenuation = 1.0 / distance; // linear attenuation
lightDirection = normalize(vertexToLightSource);
}
vec3 ambientLighting =
vec3(gl_LightModel.ambient) * vec3(_Color);
vec3 diffuseReflection =
attenuation * vec3(_LightColor0) * vec3(_Color)
* (_Waxiness + (1.0 - _Waxiness)
* 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(_LightColor0)
* vec3(_SpecColor) * pow(max(0.0, dot(
reflect(-lightDirection, normalDirection),
viewDirection)), _Shininess);
}
float silhouetteness =
1.0 - abs(dot(viewDirection, normalDirection));
gl_FragColor = vec4(ambientLighting + diffuseReflection
+ specularReflection, silhouetteness);
}
#endif
ENDGLSL
}
Pass {
Tags { "LightMode" = "ForwardAdd" }
// pass for additional light sources on front faces
Cull Back // render front faces only
Blend One One // additive blending
GLSLPROGRAM
// User-specified properties
uniform vec4 _Color;
uniform float _Waxiness;
uniform vec4 _SpecColor;
uniform float _Shininess;
uniform vec4 _TranslucentColor;
// The following built-in uniforms (except _LightColor0)
// are also defined in "UnityCG.glslinc",
// i.e. one could #include "UnityCG.glslinc"
uniform vec3 _WorldSpaceCameraPos;
// camera position in world space
uniform mat4 _Object2World; // model matrix
uniform mat4 _World2Object; // inverse model matrix
uniform vec4 _WorldSpaceLightPos0;
// direction to or position of light source
uniform vec4 _LightColor0;
// color of light source (from "Lighting.cginc")
varying vec4 position;
// position of the vertex (and fragment) in world space
varying vec3 varyingNormalDirection;
// surface normal vector in world space
#ifdef VERTEX
void main()
{
mat4 modelMatrix = _Object2World;
mat4 modelMatrixInverse = _World2Object; // unity_Scale.w
// is unnecessary because we normalize vectors
position = modelMatrix * gl_Vertex;
varyingNormalDirection = normalize(vec3(
vec4(gl_Normal, 0.0) * modelMatrixInverse));
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
}
#endif
#ifdef FRAGMENT
void main()
{
vec3 normalDirection = normalize(varyingNormalDirection);
vec3 viewDirection =
normalize(_WorldSpaceCameraPos - vec3(position));
vec3 lightDirection;
float attenuation;
if (0.0 == _WorldSpaceLightPos0.w) // directional light?
{
attenuation = 1.0; // no attenuation
lightDirection = normalize(vec3(_WorldSpaceLightPos0));
}
else // point or spot light
{
vec3 vertexToLightSource =
vec3(_WorldSpaceLightPos0 - position);
float distance = length(vertexToLightSource);
attenuation = 1.0 / distance; // linear attenuation
lightDirection = normalize(vertexToLightSource);
}
vec3 diffuseReflection =
attenuation * vec3(_LightColor0) * vec3(_Color)
* (_Waxiness + (1.0 - _Waxiness)
* 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(_LightColor0)
* vec3(_SpecColor) * pow(max(0.0, dot(
reflect(-lightDirection, normalDirection),
viewDirection)), _Shininess);
}
gl_FragColor =
vec4(diffuseReflection + specularReflection, 1.0);
}
#endif
ENDGLSL
}
}
// The definition of a fallback shader should be commented out
// during development:
// Fallback "Specular"
}
恭喜!你完成了關於半透明物體的本教程,它主要講述了
- 如何模擬蠟的外觀。
- 如何模擬背光照射的半透明材料的輪廓。
- 如何實現這些技術。
如果你還想了解更多
- 關於半透明表面,你應該閱讀 “半透明表面”部分.
- 關於 Phong 反射模型的漫反射項,你應該閱讀 “漫反射”部分.
- 關於 Phong 反射模型的環境光或鏡面反射項,你應該閱讀 “鏡面高光”部分.
- 關於使用 Phong 反射模型的逐畫素光照,你應該閱讀 “平滑鏡面高光”部分.
- 關於用於次表面散射的基本即時技術,你可以閱讀 Randima Fernando(編輯)2004 年由 Addison-Wesley 出版、名為 “GPU Gems” 的書中的 Simon Green 編寫的第 16 章 “即時次表面散射近似”,該書可以 線上獲取.