本教程镜面高光的菲涅耳系数。
这是关于光照教程中的其中一个教程,这个光照超出了Phone反射模型的范围。但是,它是基于章节“镜面高光”(逐顶点光照)以及章节“光滑镜面高光”(逐像素光照)中描述的Phone反射模型光照。如果你没阅读过那两章,建议先阅读一下。
当光线掠过表面时,大多数材质(比如亚光纸)会表现出强烈的镜面反射;如上图所示,背光从观察者相反方向进行反射。对一些材质来说菲涅耳系数解释了这种强反射。当然,还有其它原因会导致明亮的轮廓,比如半透明的头发或织物(参考章节“半透明表面”)。
有趣地是,这种效果通过很难被看到,因为很可能轮廓的背景非常明亮。但是,在这种情况下,一个明亮的轮廓会混合进背景中,于是会变得很难被看到。
译者注:总感觉文章说得不是很明白,不知道是不是自己翻译的问题。
所谓菲涅耳反射,其实就是根据视角方向来控制反射程度。当光照射到物体表面时,被反射的光和入射的光之间有一定的比率关系,而这种关系就是用菲涅耳等式计算的。
菲涅耳系数描述了波长为λ的非偏振光照下非导电材质的镜面反射。Schlick的近似等式如下:
这里V是指向观察者的归一化方向,H是归一化的中间向量: H = (V + L) / |V + L|,L是指向光源的归一化方向。H·V = 1时就是反射率,也就是指向光源的方向、指向观察者的方向以及中间向量都是等价的。当中间向量垂直于指向观察者的方向V,也就意味着指向光源的方向跟指向观察者的方向是相反的(即掠射光反射的情况)。实际上在这种情况下是独立于波长的,并且材质表现得就像一面完美的镜子。
使用内置Cg函数lerp(x,y,w) = x*(1-w) + y*w
,你可以改写Schlick的近似函数:
至少在一些GPU上面,这个会稍微更有效一点。我们将会通过允许每个颜色分量有不同的值把波长的依赖性考虑进来,也就是我们认为它是一个RGB向量。实际上,我们把它认同于章节“镜面高光”中的。菲涅耳系数会在指向观察者的方向和中间向量之间的角度上添加材质颜色的独立性。于是,我们会在任何镜面反射运算中把常量材质颜色替换为Schlick的近似等式(使用)。
举例来说,在Phong反射模型中我们对于镜面项的等式是:
把替换为有的菲涅耳系数的Schlick近似:
该实现是基于章节“光滑镜面高光”中的着色器代码。这只是计算中间向量并且包含了菲涅耳系数的近似:
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
{
float3 halfwayDirection =
normalize(lightDirection + viewDirection);
float w = pow(1.0 - max(0.0,
dot(halfwayDirection, viewDirection)), 5.0);
specularReflection = attenuation * _LightColor0.rgb
* lerp(_SpecColor.rgb, float3(1.0, 1.0, 1.0), w)
* pow(max(0.0, dot(
reflect(-lightDirection, normalDirection),
viewDirection)), _Shininess);
}
Shader "Cg Fresnel highlights" {
Properties {
_Color ("Diffuse Material Color", Color) = (1,1,1,1)
_SpecColor ("Specular Material Color", Color) = (1,1,1,1)
_Shininess ("Shininess", Float) = 10
}
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 _Shininess;
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;
float3x3 modelMatrixInverse = _World2Object;
output.posWorld = mul(modelMatrix, input.vertex);
output.normalDir = normalize(mul(input.normal, modelMatrixInverse));
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
* 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
{
float3 halfwayDirection =
normalize(lightDirection + viewDirection);
float w = pow(1.0 - max(0.0,
dot(halfwayDirection, viewDirection)), 5.0);
specularReflection = attenuation * _LightColor0.rgb
* lerp(_SpecColor.rgb, float3(1.0, 1.0, 1.0), w)
* pow(max(0.0, dot(
reflect(-lightDirection, normalDirection),
viewDirection)), _Shininess);
}
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 _Shininess;
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 =
attenuation * _LightColor0.rgb * _Color.rgb
* 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
{
float3 halfwayDirection =
normalize(lightDirection + viewDirection);
float w = pow(1.0 - max(0.0,
dot(halfwayDirection, viewDirection)), 5.0);
specularReflection = attenuation * _LightColor0.rgb
* lerp(_SpecColor.rgb, float3(1.0, 1.0, 1.0), w)
* pow(max(0.0, dot(
reflect(-lightDirection, normalDirection),
viewDirection)), _Shininess);
}
return float4(diffuseReflection
+ specularReflection, 1.0);
}
ENDCG
}
}
Fallback "Specular"
}
对以上的实现,一个有意义的修改就是把power 5.0
替换为用户指定的着色器属性。这将使艺术家可以根据他们的艺术需求来选择夸大或减弱菲涅耳系数的效果。
菲涅耳系数除了会影响镜面高光,它还会影响不透明表面的不透明度α。实际上,菲涅耳系数描述了在掠射光照下的表面如何变得反射更强,这意味着少数光是被吸收、折射或是透射的,即透明度T减小,同时不透明度α = 1 - T增大。最终,菲涅耳系数可以用表面法向量N代替半矢量H来计算,以及使用以下等式,不透明表面的不透明度会从用户指定的值(在表面法线方向上观察)增加到1:
在章节“轮廓增强”中,不透明度被认为是由于光线穿过半透明材质层而衰减的结果。这种不透明度应该由于以下方式增加反射率的不透明度结合起来(原文是:This opacity should be combined with the opacity due to increased reflectivity in the following way。这个怎么翻译!!!)。总的不透明度就是1减去总的透明度,它是根据衰减得到的透明度(1-)和根据菲涅耳系数得到的透明度(1-)相乘得到的。
就是上面计算得到的不透明度,而是章节“轮廓增强”中计算得到的不透明度。对于平行于表面法向量的观察方向来说,和可以被用户指定。然后对于法向量来说,等式可以修正,并且实际上它会修正所有常量,因此就可以为所有视图方向计算。注意不管漫反射还是镜面反射都不应该乘以不透明度,因为镜面反射已经乘以了菲涅耳系数,并且由于衰减漫反射应该只是乘以不透明度。
恭喜,你完成了一些比较高级的教程中的一章!我们看到了: