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 章“实时近似次表面散射”,该书可以在 网上 获取。