这篇文章最好与前一篇连着看,自己可做比较。
1.BlinnPhong 模型
Blinn是计算和估计镜面反射的另一种更有效的方法。这是通过从 视图方向和光方向获得半矢量来完成饿。JimBlinn将它带入来Cg的世界。他发现只获得半矢量而不是计算我们自己的反射 矢量效率更高。它减少了代码和处理时间。如果你实际查看UnityCG.cginc文件中包含的内置BlinnPhong光照模型,你会注意到它也使用来半矢量,因此它被命名为BlinnPhong,它只是完整的Phong的简单版本。
a..创建一个shader命名为BlinnPhong。
b.创建一个material命名为BlinnPhongMat。
c.移走所以才当前属性,并在属性块中添加如下属性,这样我们就可以控制镜面高光的外观:
_MainTint("Diffuse Tint",Color) = (1,1,1,1)
_MainTex("Base (RGB)",2D) = "white" {}
_SpecularColor("Specular Color",Color) = (1,1,1,1)
_SpecPower("Specular Power",Range(0.1,60)) = 3
d.并在CGPROGRAM中添加相关变量‘
sampler2D _MainTex;
float4 _MainTint;
float4 _SpecularColor;
float _SpecPower;
e. 现在是时候创建我们自定义光照模型来处理我们的漫反射和高光计算。
fixed4 LightingCustomBlinnPhong(SurfaceOutput s, fixed3 lightDir, half3 viewDir, fixed atten)
{
float NdotL = max(0, dot(s.Normal, lightDir));
float3 halfVector = normalize(lightDir + viewDir);
float NdotH = max(0, dot(s.Normal, halfVector));
float spec = pow(NdotH, _SpecPower) * _SpecularColor;
float4 color;
color.rgb = (s.Albedo * _LightColor0.rgb * NdotL) + (_LightColor0.rgb * _SpecularColor.rgb * spec) * atten;
color.a = s.Alpha;
return color;
}
h.更新surf函数如下表
void surf(Input IN, inout SurfaceOutput o)
{
half4 c = tex2D(_MainTex, IN.uv_MainTex) * _MainTint;
o.Albedo = c.rgb;
o.Alpha = c.a;
}
i.为了完成我们的shader,我们需要通过使用以下代码修改 #pragma语句来告诉我们的CGPROGRAM块使用我们的自定义光照模型而不是内置模型:
CPROGRAM
#pragma surface surf CustomBlinnPhong
源码如下:
Shader "Custom/BlinnPhong" {
Properties {
_MainTint("Diffuse Tint",Color) = (1,1,1,1)
_MainTex("Base (RGB)",2D) = "white" {}
_SpecularColor("Specular Color",Color) = (1,1,1,1)
_SpecPower("Specular Power",Range(0.1,60)) = 3
}
SubShader {
Tags { "RenderType"="Opaque" }
LOD 200
CGPROGRAM
#pragma surface surf CustomBlinnPhong
// Use shader model 3.0 target, to get nicer looking lighting
#pragma target 3.0
sampler2D _MainTex;
float4 _MainTint;
float4 _SpecularColor;
float _SpecPower;
struct Input {
float2 uv_MainTex;
};
void surf(Input IN, inout SurfaceOutput o)
{
half4 c = tex2D(_MainTex, IN.uv_MainTex) * _MainTint;
o.Albedo = c.rgb;
o.Alpha = c.a;
}
fixed4 LightingCustomBlinnPhong(SurfaceOutput s, fixed3 lightDir, half3 viewDir, fixed atten)
{
float NdotL = max(0, dot(s.Normal, lightDir));
float3 halfVector = normalize(lightDir + viewDir);
float NdotH = max(0, dot(s.Normal, halfVector));
float spec = pow(NdotH, _SpecPower) * _SpecularColor;
float4 color;
color.rgb = (s.Albedo * _LightColor0.rgb * NdotL) + (_LightColor0.rgb * _SpecularColor.rgb * spec) * atten;
color.a = s.Alpha;
return color;
}
ENDCG
}
FallBack "Diffuse"
}
它是怎么运行的
BlinnPhong Specular几乎与Phong Specular完全相同。只是它更有效,因为它使用更少的代码来视线几乎相同的效果。在引入基于物理的渲染之前,此方法是Unity 4中Specular反射的默认选择。
计算R矢量的反射通常是昂贵的,BlinnPhong Specular 模型用视图方向V和灯光方向L之间的半矢量来代替它
我们只是在视图方向和光线方向之间获取矢量,而不是计算我们自己的反射矢量,基本上模拟反射矢量。实际上已经发现这种方法比上一种方法在物理上更准确。
根据向量代数,半矢量可以如下计算:
这里|V + L|是矢量V+L的长度。在Cg中,我们只需要将视图方向和光照方向一起添加,然后将结果标准化为单位矢量:
float3 halfVector = normalize(lightDir + viewDir)
然后,我们只需要用这个新的半矢量dot顶点法线来获得我们主要的Speculat值。在此之后,我们只需将其赋予_SpecPower的幂并将其乘以Specular颜色变量 。它在代码和数学上很轻量,但仍然为我们提供了一个很好的Specular高亮,它可以用于许多实时情况。
相关
B这些灯光模型很简单;没有真正的材料是完美的额哑光或完美的镜面。此外,对于诸如衣服,木材和皮肤的复杂材料而言,需要知道光如何在表面下方的层中散射,这并不罕见。使用下表来回顾一些光照模型,其它光照模型,可翻看之前的博客。
2.Anisotropic镜面模型
Anisotropic是一种高光或反射,它模拟表面中凹槽的方向性,并在垂直方向上修改拉伸Specular。当你想要模拟拉丝金属时,它非常有用,而不是具有清晰,光滑和抛光表面的金属。想象以下当你查看CD或DVD的数据侧,或者高光是如何在锅或平底锅的底部形成的。你会注意到,如果你仔细检查表面,凹槽的方向,通常是金属刷的方式。将镜面反射应用于此曲面时,会获得在垂直方向上拉伸的镜面反射。
a.创建一个Shader命名为Anisotropic。
b.创建一个Material命名为AnisotropicMat。
c.双击打开shader脚本在属性块里移走之前的属性而且添加如下属性,这些将允许对表面的最终外观进行大量的艺术控制,并在CGPROGRAM添加相应变量:
Properties {
_MainTint("Diffuse Tint",Color) = (1,1,1,1)
_MainTex("Base(RGB)" ,2D) = "white" {}
_SpecularColor("Specular Color",Color) = (1,1,1,1)
_Specular("Specular Amount",Range(0,1)) = 0.5
_SpecPower("Specular Power",Range(0,1)) = 0.5
_AnisoDir("Anisotropic Diretion",2D) = "" {}
_AnisoOffset("Anisotropic Offset",Range(-1,1)) = -0.3
}
sampler2D _MainTex;
float4 _MainTex;
float4 _SpecularColor;
float _Specular;
float _SpecPower;
sampler2D _AnisoDir;
float _AnisoOffset;
d.现在我们可以创建我们的Lighting函数u,它将在我们的表面上产生正确的各向异性效果。我们将使用以下代码;
fixed4 LightingAnisotropic(SurfaceAnisoOoutput s, fixed3 lightDir, half3 viewDir, fixed atten)
{
fixed3 halfVector = normalize(normalize(lightDir) + normalize(viewDir));
float NdotL = saturate(dot(s.Normal, lightDir));
fixed HdotA = dot(normalize(s.Normal + s.AnisoDirection), halfVector);
float aniso = max(0, sin(radians((HdotA + _AnisoOffset) * 180)));
float spec = saturate(pow(aniso, s.Gloss * 128) * s.Specular);
fixed4 c;
c.rgb = ((s.Albedo * _LightColor0.rgb * NdotL) + (_LightColor0.rgb * _SpecularColor.rgb * spec)) * atten;
c.a = a.Alpha;
return c;
}
e.同时我们把内置的Lighting函数替换称我们新创建的Lighting函数
CGPROGRAM
#pragma surface surf Anisotropic
h.我们还通过在 struct Input中声明以下代码,为各向异性(Anisotropic)法线贴图提供了自己的UV.这并不是完全必要的,因为我们可以只使用主纹理中的UV,但这使我们能够独立控制拉丝金属效果的平铺,以使我们可以将其缩放到我们想要的任何尺寸寸:
struct Input {
float2 uv_MainTex;
float2 uv_AnisoDir;
};
i.我们也需要在struct SurfaceAnisoOutput添加
struct SurfaceAnisoOutout
{
fixed3 Albedo;
fixed3 Normal;
fixed3 Emission;
fixed3 AnisoDirection;
half Specular;
fixed Gloss;
fixed Alpha;
};
j.最后我们需要使用surf()函数将正确的数据传递给Lighting函数。因此,我们将从各向异性法线贴图中获取每像素信息,并设置我们的Specular参数,如下所示:
void surf (Input IN, inout SurfaceAnisoOutput o) {
half4 c = tex2D(_MainTex, IN.uv_MainTex) * _MainTint;
float3 anisoTex = UnpackNormal(tex2D(_AnisoDir, IN.uv_AnisoDir));
o.AnisoDiretion = anisoTex;
o.Specular = _Specular;
o.Gloss = _SpecPower;
o.Albedo = c.rgb;
o.Alpha = c.a;
}
k.保存脚本回到Unity编辑器,我们可以调节Anisotopic Offset属性值观察变化。效果图如图所示。
源码如下:
Shader "Custom/Anisotropic" {
Properties {
_MainTint("Diffuse Tint",Color) = (1,1,1,1)
_MainTex("Base(RGB)" ,2D) = "white" {}
_SpecularColor("Specular Color",Color) = (1,1,1,1)
_Specular("Specular Amount",Range(0,1)) = 0.5
_SpecPower("Specular Power",Range(0,1)) = 0.5
_AnisoDir("Anisotropic Diretion",2D) = "" {}
_AnisoOffset("Anisotropic Offset",Range(-1,1)) = -0.3
}
SubShader {
Tags { "RenderType"="Opaque" }
LOD 200
CGPROGRAM
#pragma surface surf Anisotropic
// Use shader model 3.0 target, to get nicer looking lighting
#pragma target 3.0
sampler2D _MainTex;
float4 _MainTint;
float4 _SpecularColor;
float _Specular;
float _SpecPower;
sampler2D _AnisoDir;
float _AnisoOffset;
struct Input {
float2 uv_MainTex;
float2 uv_AnisoDir;
};
struct SurfaceAnisoOutput
{
fixed3 Albedo;
fixed3 Normal;
fixed3 Emission;
fixed3 AnisoDirection;
half Specular;
fixed Gloss;
fixed Alpha;
};
fixed4 LightingAnisotropic(SurfaceAnisoOutput s, fixed3 lightDir, half3 viewDir, fixed atten)
{
fixed3 halfVector = normalize(normalize(lightDir) + normalize(viewDir));
float NdotL = saturate(dot(s.Normal, lightDir));
fixed HdotA = dot(normalize(s.Normal + s.AnisoDirection), halfVector);
float aniso = max(0, sin(radians((HdotA + _AnisoOffset) * 180)));
float spec = saturate(pow(aniso, s.Gloss * 128) * s.Specular);
fixed4 c;
c.rgb = ((s.Albedo * _LightColor0.rgb * NdotL) + (_LightColor0.rgb * _SpecularColor.rgb * spec)) * atten;
c.a = s.Alpha;
return c;
}
void surf (Input IN, inout SurfaceAnisoOutput o) {
half4 c = tex2D(_MainTex, IN.uv_MainTex) * _MainTint;
float3 anisoTex = UnpackNormal(tex2D(_AnisoDir, IN.uv_AnisoDir));
o.AnisoDirection = anisoTex;
o.Specular = _Specular;
o.Gloss = _SpecPower;
o.Albedo = c.rgb;
o.Alpha = c.a;
}
ENDCG
}
FallBack "Diffuse"
}
它是如何运行的
我们将这个shader分解为其核心组件,并解释我们为什么会产生这种效果。我们将在这里主要介绍自定义光照函数。我们首先声明我们自己的SurfaceAnisoOutput结构。我们需要这样做才能从各向异性法线贴图中获取没像素信息,而我们在Surface Shader中执行此操作的唯一方法是在Surface Shader中执行此操作的唯一方法是在surf()函数中使用tex2D()函数。以下代码显示了shader中使用的自定义surface output结构:
struct SurfaceAnisoOutput
{
fixed3 Albedo;
fixed3 Normal;
fixed3 Emission;
fixed3 AnisoDirection;
half Specular;
fixed Gloss;
fixed Alpha;
};
我们可以使用SurfaceAnisotropicOutput结构作为Lighting函数和surface 函数之间的交互方式。在我们的例子中,我们将每像素纹理信息存储在我们的surf()函数中名为anisoTex的变量中,然后通过将其存储在AnisoDirection变量中将其传递给SurfaceAnisoOutput结构。一旦我们有了这个,我们就可使用s.AnisoDirection在Lighting函数中使用每像素信息。
设置此数据连接后,我们可以继续进行实际的光照计算,这首先得到通常的半矢量,这样我们就不必进行全反射计算和漫反射光照。这是用光矢量或方向点和顶点法线的点积。这是在Cg中完成的,包括如下几行:
fixed3 halfVector = normalize(normalize(lightDir) + normalize(viewDir));
float NdotL = saturate(dot(s.Normal, lightDir));
然后,我们开始对Specular进行实际修改以获得正确的外观,首先,我们用从上一步得到的halfVector点积归一化后的各向异性法线贴图的顶点法线和每像素矢量的和。这给了我们一个浮点值,它的表面法线是1 ,当它与半矢量平行时,它回被各向异性法线贴图修正,当它与半矢量垂直时,它会变成0.最后,我们用sin()函数修改这个值,这样我们基本上可以得到一个更亮的中间高亮,最终得到一个基于halfVector的环效果。前面提到的所有操作都在以下两行Cg代码中进行了总结:
fixed HdotA = dot(normalize(s.Normal + s.AnisoDirection), halfVector);
float aniso = max(0, sin(radians((HdotA + _AnisoOffset) * 180)));
最后,我们通过将它变为s.Gloss的幂来缩放aniso值的效果,然后通过将其乘以s.Specular来全局减小其强度。
这种效果非常适合创建更高级的金属类型表面,尤其时那些经过刷涂并看起啦具有方向性的表面。它也适用于头发或任何具有方向性的柔软表面。
以上均基于Unity2018.1.0f。源码可见我的GitHub