跳转到内容

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

除非另有说明,否则本页面上的所有示例源代码均授予公有领域。
华夏公益教科书