Cg Programming/Unity/Brushed Metal拉丝金属

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

这是几个关于光照教程中的其中一个教程,这个光照超出了Phone反射模型的范围。但是,它是基于章节“镜面高光”(逐顶点光照)以及章节“光滑镜面高光”(逐像素光照)中描述的Phone反射模型光照。如果你没阅读过那两章,建议先阅读一下。

这里写图片描述
对于纸张、塑料以及一些其它各向同性的材质来说,Phone反射模型看上去还是相当不错的。本教程特别关注各向异性反射材质(即非圆形高光),就比如上图中的拉丝铝。

Ward的各向异性反射模型

Gregory Ward在他的论文“各向异性反射测量与建模”,计算机图形学(SIGGRAPH ’92 Proceedings), pp. 265–272, July 1992中公布了一种合适的各向异性反射模型。(网上可以找到该论文。)这个模型用BRDF(双向反射分布函数)来描述反射,它是一个四维函数,描述了任意方向的光线是如何反射到任意其它方向上。他的BRDF模型包含了两项:一个漫反射项这里写图片描述,以及一个比较复杂的镜面反射项。

让我们先来看一下漫射项这里写图片描述:π是一个常量(约是3.14159),这里写图片描述指定了漫反射率。原则上每个波长的反射率是必要的;但是,通常对三种颜色分量(红、绿和蓝)中的每一个都指定一个反射率。如果我们包含了常量π,这里写图片描述就表示漫射材质颜色这里写图片描述,我们第一次看到它是在章节“漫反射”中,但是它也出现在Phone反射模型中(参考章节“镜面高光”)。你可能疑惑为什么因子max(0, L·N)没有出现在BRDF中。原因在于BRDF就是如此定义的,这个因子没有包含进来(因为它实际并不是材质的一个属性),但是当计算任何光照的时候应该把它乘以BRDF。

于是,为了对不透明材质实现一个指定的BRDF,我们必须用max(0, L·N)乘以BRDF所有的项,以及除非我们想要实现物理上正确的光照,我们可以把任何常量因子替换成用户指定的颜色,它通常比物理量更容易控制。

对于BRDF模型中特殊的项,Ward在他的论文中提出了一个近似方程5b。我稍微修改了一下,这样就可以使用归一化的表面法向量N,指向观察者的归一化向量V,指向光源的归一化向量L,以及归一化的中间向量H(V + L) / | V + L |。利用这些向量,Ward对于这个特殊项的近似就成为了这个样子:
这里写图片描述
这里,这里写图片描述是镜面反射率,它描述了颜色和镜面高光的强度;这里写图片描述这里写图片描述是描述高光形状和大小的材质常量。既然所有这些变量就是材质常量,我们可以把它们整合到一个常量这里写图片描述中去。于是我们得到一个稍微简短点的版本:
这里写图片描述

记住当在着色器中实现它时,我们仍然需要把BRDF项乘以L·N,并且当L·N小于0的时候把它设置为0。此外,如果L·N的值小于0它也应该是0,也就是如果从“错”的面看表面的话。

还有两个向量没有描述过:T和B。T是表面上刷(拉丝)的方向,B垂直于T并且也在表面上。Unity为我们提供了一个表面上的切线向量作为一个顶点属性(参考章节“着色器调试”),我们就把它用作向量T。计算N和T的叉乘可以得到向量B,它垂直于N和T。

Ward的BRDF模型的实现

我们的实现基于章节“光滑镜面高光”中的逐像素光照着色器。对于切线向量T(也就是刷的方向)我们需要另一个顶点输出参数tangentDir,并且我们也需要在顶点着色器中计算viewDir这样可以在片元着色器中节省一些指令。在片元着色器中,我们计算额外两个方向:中间向量H的halfwayVector和副法线向量B的binormalDirection。属性_Color就是这里写图片描述_SpecColor就是这里写图片描述_AlphaX就是这里写图片描述_AlphaY就是这里写图片描述

这个片元着色器跟章节“光滑镜面高光”中的版本非常类似,除了它会计算halfwayVectorbinormalDirection,为镜面部分实现一个不同的等式。此外,这个着色器只计算一次点乘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))这项,它由这里写图片描述乘以(L·N)得到。这个保证了结果始终大于0。

完整的着色器代码只是定义了合适的属性并且为切线添加了另一个顶点输入参数。它也需要第二个通道,里面有加性混合但没有额外光源的环境光照。

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 = _Object2World;
            float4x4 modelMatrixInverse = _World2Object; 

            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 = _Object2World;
            float4x4 modelMatrixInverse = _World2Object;

            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"
}

总结

恭喜,你完成了一个相当高级的教程!我们看到了:

  • 什么是BRDF(双向反射分布函数)。
  • 什么是各向异性反射的Ward BRDF模型。
  • 如何实现Ward的BRDF模型。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值