跳转到内容

Cg 编程/Unity/拉丝金属

来自维基教科书,开放世界中的开放书籍
拉丝铝。注意镜面高光的形状,它远非圆形。

本教程涵盖各向异性镜面高光

它是关于光照的几个教程之一,超出了 Phong 反射模型。然而,它基于 Phong 反射模型的光照,如“镜面高光”部分(用于每个顶点光照)和“平滑镜面高光”部分(用于每个像素光照)中所述。如果您还没有阅读这些教程,您应该先阅读它们。

虽然 Phong 反射模型对于纸张、塑料和一些其他具有各向同性反射(即圆形高光)的材料来说相当不错,但本教程特别关注具有各向异性反射(即非圆形高光)的材料,例如左侧照片中的拉丝铝。

除了 Phong 反射模型使用的大多数向量之外,我们还需要归一化的中间向量 H,它是观察者方向 V 和光源方向 L 之间的精确方向。

Ward 的各向异性反射模型

[编辑 | 编辑源代码]

Gregory Ward 在其作品“测量和建模各向异性反射”中发表了一个合适的各向异性反射模型,计算机图形学(SIGGRAPH ’92 论文集),第 265-272 页,1992 年 7 月。(该论文的副本可在网上获得。)该模型根据 BRDF(双向反射分布函数)来描述反射,它是一个四维函数,描述了来自任何方向的光线是如何反射到任何其他方向的。他的 BRDF 模型包含两项:一个漫反射项,它是,以及一个更复杂的镜面反射项。

让我们先看看漫反射项只是一个常数(大约 3.14159),而指定了漫反射率。原则上,每个波长都需要一个反射率;然而,通常为三个颜色分量(红色、绿色和蓝色)中的每一个指定一个反射率。如果我们包括常数仅仅代表漫反射材质颜色,我们第一次在“漫反射”部分中看到它,但它也出现在 Phong 反射模型中(参见“镜面高光”部分)。您可能想知道为什么因子 max(0, L·N) 没有出现在 BRDF 中。答案是 BRDF 的定义方式是不包含这个因子(因为它实际上不是材料的属性),但在进行任何光照计算时,应该将其与 BRDF 相乘。

因此,为了对不透明材料实现给定的 BRDF,我们必须将 BRDF 的所有项乘以 max(0, L·N) 并且 - 除非我们想实现物理上正确的光照 - 我们可以将任何常数因子替换为用户指定的颜色,这通常比物理量更容易控制。

对于他的 BRDF 模型的镜面项,Ward 在他的论文的等式 5b 中给出了一个近似值。我稍微修改了它,使其使用归一化的表面法线向量 N,归一化的观察者方向 V,归一化的光源方向 L 以及归一化的中间向量 H,它是 (V + L) / |V + L|。使用这些向量,Ward 对镜面项的近似值变为

这里, 是镜面反射率,它描述了镜面高光的颜色和强度; 是描述高光形状和大小的材料常数。由于所有这些变量都是材料常数,我们可以将它们组合成一个常数 。因此,我们得到一个稍短的版本

请记住,在着色器中实现它时,我们仍然必须将此 BRDF 项乘以 L·N,如果 L·N 小于 0,则将其设置为 0。此外,如果 V·N 小于 0,即我们从“错误”的一侧查看表面,它也应该为 0。

还有两个向量尚未描述:TBT 是表面上的刷子方向,而 BT 正交,但也在表面上。Unity 为我们提供了一个表面上的切线向量作为顶点属性(见 “着色器调试”部分),我们将把它用作向量 T。计算 NT 的叉积会生成一个向量 B,它与 NT 正交,正如它应该的那样。

Ward's BRDF 模型的实现

[编辑 | 编辑来源]

我们基于 “平滑镜面高光”章节 中的逐像素光照着色器实现。我们还需要一个顶点输出参数 `tangentDir` 来表示切线向量 **T**(即刷痕方向),并且我们在顶点着色器中计算 `viewDir` 以节省片元着色器中的指令。在片元着色器中,我们计算另外两个方向:`halfwayVector` 表示半程向量 **H**,`binormalDirection` 表示副法线向量 **B**。属性包括 `_Color` 代表 ,`_SpecColor` 代表 ,`_AlphaX` 代表 ,`_AlphaY` 代表

片元着色器与 “平滑镜面高光”章节 中的版本非常相似,只是它计算了 `halfwayVector` 和 `binormalDirection`,并且实现了镜面部分的不同方程。此外,该着色器仅计算一次点积 **L**·**N** 并将其存储在 `dotLN` 中,以便可以重复使用而无需重新计算。代码如下

         float4 frag(vertexOutput input) : COLOR
         {
            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 halfwayVector = 
               normalize(lightDirection + input.viewDir);
            float3 binormalDirection = 
               cross(input.normalDir, input.tangentDir);
            float dotLN = dot(lightDirection, input.normalDir); 
               // compute this dot product only once
 
            float3 ambientLighting = 
               UNITY_LIGHTMODEL_AMBIENT.rgb * _Color.rgb;
 
            float3 diffuseReflection = 
               attenuation * _LightColor0.rgb * _Color.rgb 
               * max(0.0, dotLN);
 
            float3 specularReflection;
            if (dotLN < 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
            {
               float dotHN = dot(halfwayVector, input.normalDir);
               float dotVN = dot(input.viewDir, input.normalDir);
               float dotHTAlphaX = 
                  dot(halfwayVector, input.tangentDir) / _AlphaX;
               float dotHBAlphaY = dot(halfwayVector, 
                  binormalDirection) / _AlphaY;
 
               specularReflection = 
                  attenuation * _LightColor0.rgb * _SpecColor.rgb 
                  * sqrt(max(0.0, dotLN / dotVN)) 
                  * exp(-2.0 * (dotHTAlphaX * dotHTAlphaX 
                  + dotHBAlphaY * dotHBAlphaY) / (1.0 + dotHN));
            }
            return float4(ambientLighting + diffuseReflection 
               + specularReflection, 1.0);
         }

注意 `sqrt(max(0, dotLN / dotVN))` 这个项,它是从 乘以 得到的。这确保了所有值都大于 0。

完整的着色器代码

[edit | edit source]

完整的着色器代码仅定义了适当的属性,并添加了另一个用于切线的顶点输入参数。此外,它需要一个带有累加混合但没有环境光的第二通道,用于处理额外的光源。

Shader "Cg anisotropic per-pixel lighting" {
   Properties {
      _Color ("Diffuse Material Color", Color) = (1,1,1,1) 
      _SpecColor ("Specular Material Color", Color) = (1,1,1,1) 
      _AlphaX ("Roughness in Brush Direction", Float) = 1.0
      _AlphaY ("Roughness orthogonal to Brush Direction", Float) = 1.0
   }
   SubShader {
      Pass {	
         Tags { "LightMode" = "ForwardBase" } 
            // pass for ambient light and first light source
 
         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 float4 _SpecColor; 
         uniform float _AlphaX;
         uniform float _AlphaY;
 
         struct vertexInput {
            float4 vertex : POSITION;
            float3 normal : NORMAL;
            float4 tangent : TANGENT;
         };
         struct vertexOutput {
            float4 pos : SV_POSITION;
            float4 posWorld : TEXCOORD0;
               // position of the vertex (and fragment) in world space 
            float3 viewDir : TEXCOORD1;
               // view direction in world space
            float3 normalDir : TEXCOORD2;
               // surface normal vector in world space
            float3 tangentDir : TEXCOORD3;
               // brush direction in world space
         };
 
         vertexOutput vert(vertexInput input) 
         {
            vertexOutput output;
 
            float4x4 modelMatrix = unity_ObjectToWorld;
            float4x4 modelMatrixInverse = unity_WorldToObject; 
 
            output.posWorld = mul(modelMatrix, input.vertex);
            output.viewDir = normalize(_WorldSpaceCameraPos 
               - output.posWorld.xyz);
            output.normalDir = normalize(
               mul(float4(input.normal, 0.0), modelMatrixInverse).xyz);
            output.tangentDir = normalize(
               mul(modelMatrix, float4(input.tangent.xyz, 0.0)).xyz);
            output.pos = mul(UNITY_MATRIX_MVP, input.vertex);
            return output;
         }
 
         float4 frag(vertexOutput input) : COLOR
         {
            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 halfwayVector = 
               normalize(lightDirection + input.viewDir);
            float3 binormalDirection = 
               cross(input.normalDir, input.tangentDir);
            float dotLN = dot(lightDirection, input.normalDir); 
               // compute this dot product only once
 
            float3 ambientLighting = 
               UNITY_LIGHTMODEL_AMBIENT.rgb * _Color.rgb;
 
            float3 diffuseReflection = 
               attenuation * _LightColor0.rgb * _Color.rgb 
               * max(0.0, dotLN);
 
            float3 specularReflection;
            if (dotLN < 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
            {
               float dotHN = dot(halfwayVector, input.normalDir);
               float dotVN = dot(input.viewDir, input.normalDir);
               float dotHTAlphaX = 
                  dot(halfwayVector, input.tangentDir) / _AlphaX;
               float dotHBAlphaY = dot(halfwayVector, 
                  binormalDirection) / _AlphaY;
 
               specularReflection = 
                  attenuation * _LightColor0.rgb * _SpecColor.rgb 
                  * sqrt(max(0.0, dotLN / dotVN)) 
                  * exp(-2.0 * (dotHTAlphaX * dotHTAlphaX 
                  + dotHBAlphaY * dotHBAlphaY) / (1.0 + dotHN));
            }
            return float4(ambientLighting + diffuseReflection 
               + specularReflection, 1.0);
         }
         ENDCG
      }
 
      Pass {	
         Tags { "LightMode" = "ForwardAdd" } 
            // pass for additional light sources
         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 float4 _SpecColor; 
         uniform float _AlphaX;
         uniform float _AlphaY;
 
         struct vertexInput {
            float4 vertex : POSITION;
            float3 normal : NORMAL;
            float4 tangent : TANGENT;
         };
         struct vertexOutput {
            float4 pos : SV_POSITION;
            float4 posWorld : TEXCOORD0;
               // position of the vertex (and fragment) in world space 
            float3 viewDir : TEXCOORD1;
               // view direction in world space
            float3 normalDir : TEXCOORD2;
               // surface normal vector in world space
            float3 tangentDir : TEXCOORD3;
               // brush direction in world space
         };
 
         vertexOutput vert(vertexInput input) 
         {
            vertexOutput output;
 
            float4x4 modelMatrix = unity_ObjectToWorld;
            float4x4 modelMatrixInverse = unity_WorldToObject;
 
            output.posWorld = mul(modelMatrix, input.vertex);
            output.viewDir = normalize(_WorldSpaceCameraPos 
               - output.posWorld.xyz);
            output.normalDir = normalize(
               mul(float4(input.normal, 0.0), modelMatrixInverse).xyz);
            output.tangentDir = normalize(
               mul(modelMatrix, float4(input.tangent.xyz, 0.0)).xyz);
            output.pos = mul(UNITY_MATRIX_MVP, input.vertex);
            return output;
         }
 
         float4 frag(vertexOutput input) : COLOR
         {
            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 halfwayVector = 
               normalize(lightDirection + input.viewDir);
            float3 binormalDirection = 
               cross(input.normalDir, input.tangentDir);
            float dotLN = dot(lightDirection, input.normalDir); 
               // compute this dot product only once
 
            float3 diffuseReflection = 
               attenuation * _LightColor0.rgb * _Color.rgb 
               * max(0.0, dotLN);
 
            float3 specularReflection;
            if (dotLN < 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
            {
               float dotHN = dot(halfwayVector, input.normalDir);
               float dotVN = dot(input.viewDir, input.normalDir);
               float dotHTAlphaX = 
                  dot(halfwayVector, input.tangentDir) / _AlphaX;
               float dotHBAlphaY = dot(halfwayVector, 
                  binormalDirection) / _AlphaY;
 
               specularReflection = 
                  attenuation * _LightColor0.rgb * _SpecColor.rgb 
                  * sqrt(max(0.0, dotLN / dotVN)) 
                  * exp(-2.0 * (dotHTAlphaX * dotHTAlphaX 
                  + dotHBAlphaY * dotHBAlphaY) / (1.0 + dotHN));
            }
            return float4(diffuseReflection 
               + specularReflection, 1.0);
         }
         ENDCG
      }
   }
   Fallback "Specular"
}

总结

[edit | edit source]

恭喜你完成了一个相当高级的教程!我们已经了解了

  • 什么是 BRDF(双向反射分布函数)。
  • 什么是 Ward 用于各向异性反射的 BRDF 模型。
  • 如何实现 Ward 的 BRDF 模型。

进一步阅读

[edit | edit source]

如果你想了解更多关于

  • 使用 Phong 反射模型进行光照,你应该阅读 “镜面高光”章节
  • 逐像素光照(即 Phong 着色),你应该阅读 “平滑镜面高光”章节
  • Ward 的 BRDF 模型,你应该阅读他的文章“测量和建模各向异性反射”,计算机图形学(SIGGRAPH ’92 论文集),第 265-272 页,1992 年 7 月。(该论文的副本可在 网上 获得。)或者你可以阅读 Randi Rost 等人 2009 年由 Addison-Wesley 出版的三版“OpenGL 着色语言”的第 14.3 节,或者阅读 Wolfgang Engel、Jack Hoxley、Ralf Kornmann、Niko Suni 和 Jason Zink 的“编程顶点、几何和像素着色器”(第二版,2008 年)照明章节的第 8 节(可在 网上 获得)。

< Cg Programming/Unity

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