来源:《UNITY SHADER入门精要》
Blinn光照模型没有使用 反射方向、引入了新的半程向量(bisecoter)
h
^
\boldsymbol{\hat{h}}
h^,它是通过对视角方向
v
^
\hat{v}
v^ 和光照方向和
r
^
\hat{r}
r^ 想加再归一化得到的:
h
^
=
v
^
+
I
∣
v
^
+
I
∣
\mathbf{\hat{h}}=\frac{\mathbf{\hat{v}}+\boldsymbol{I}}{|\mathbf{\hat{v}}+\boldsymbol{I}|}
h^=∣v^+I∣v^+I
Blinn模型计算高光反射公式如下:
c
s
p
c
u
l
a
r
=
(
c
l
i
g
h
t
⋅
m
s
p
e
c
u
l
a
r
)
max
(
0
,
n
^
⋅
h
^
)
m
g
l
o
s
s
\boldsymbol{c}_{spcular}=\left( \boldsymbol{c}_{light}\cdot \boldsymbol{m}_{specular} \right) \max \left( 0,\hat{n}\,\,\cdot \,\,\hat{h} \right) ^{m_{gloss}}
cspcular=(clight⋅mspecular)max(0,n^⋅h^)mgloss
这里只用给出逐像素的实现。
Shader "Unity Shaders Book/Chapter 6/Blinn-Phong" {
Properties {
_Diffuse ("Diffuse", Color) = (1, 1, 1, 1)
_Specular ("Specular", Color) = (1, 1, 1, 1)
_Gloss ("Gloss", Range(8.0, 256)) = 20
}
SubShader {
Pass {
Tags { "LightMode"="ForwardBase" }
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"
fixed4 _Diffuse;
fixed4 _Specular;
float _Gloss;
struct a2v {
float4 vertex : POSITION;
float3 normal : NORMAL;
};
struct v2f {
float4 pos : SV_POSITION;
float3 worldNormal : TEXCOORD0;
float3 worldPos : TEXCOORD1;
};
v2f vert(a2v v) {
v2f o;
// Transform the vertex from object space to projection space
o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
// Transform the normal from object space to world space
o.worldNormal = mul(v.normal, (float3x3)_World2Object);
// Transform the vertex from object spacet to world space
o.worldPos = mul(_Object2World, v.vertex).xyz;
return o;
}
前面的代码和之前的一致,根据公式,我们只用改变片元着色器的代码中的一部分。
回想为什么第 39 行是左乘,因为它们两换了位置,而避免了取逆的操作。
fixed4 frag(v2f i) : SV_Target {
// Get ambient term
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
fixed3 worldNormal = normalize(i.worldNormal);
fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
// Compute diffuse term
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * max(0, dot(worldNormal, worldLightDir));
// Get the view direction in world space
fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz);
// Get the half direction in world space
fixed3 halfDir = normalize(worldLightDir + viewDir);
// Compute specular term
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(worldNormal, halfDir)), _Gloss);
return fixed4(ambient + diffuse + specular, 1.0);
}
ENDCG
}
}
FallBack "Specular"
}
和之前的代码有所不一样的地方在于 第14行,采用的是 半程向量(bisecoter),就是worldLightDir + viewDir ,然后单位化一下,然后参与计算。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-U7lgGW6J-1659936047643)(assets/image-20220620171848498.png)]
其中的几个函数比较简单,不用帮助函数我们照样能够很快的写出来,也能够在 UnityCG.cginc 中找到。比如:
①WorldSpaceViewDir(float4 v)
:
// Computes world space view direction, from object space position
// *Legacy* Please use UnityWorldSpaceViewDir instead
inline float3 WorldSpaceViewDir( in float4 localPos )
{
float3 worldPos = mul(unity_ObjectToWorld, localPos).xyz;
return UnityWorldSpaceViewDir(worldPos);
}
传入的参数是 模型空间 中的位置,函数先转转换到世界空间,再传入unityworldSpaceViewDir(worldPos)函数
。
当然,我们可以从官方注释里看到,这个是个遗留下来的老函数了,不如直接使用 加了 unity 的版本。
②UnityWorldSpaceViewDir( in float3 worldPos )
inline float3 UnityWorldSpaceViewDir( in float3 worldPos )
{
return _WorldSpaceCameraPos.xyz - worldPos;
}
这里面也就执行了 摄像机-顶点位置,这个操作了。其他也没啥了。
⑤UnityWroldSpaceLightDir(float4 v)函数
表中其他三个关于 LightDir 的函数都是使用了 _WorldSpaceLightPos0
这个内置来获取光照方向的。必须是前向渲染的时候它才会被正确赋值。
⑦ UnityObjectToWorldNormal( in float3 norm )函数
// Transforms normal from object to world space
inline float3 UnityObjectToWorldNormal( in float3 norm )
{
#ifdef UNITY_ASSUME_UNIFORM_SCALING
return UnityObjectToWorldDir(norm);
#else
return normalize(mul(norm, (float3x3)unity_WorldToObject));
#endif
}
法线的 模型空间 到 世界空间 的变换和其他普通向量不太一样。如果对此有疑问,可以参考《UnityShader入门精要》的4.7节。也可以参考本系列的[13 UnityShader入门(四)](13 UnityShader入门(四).md/## 6、法线变换问题),通过公式查看为什么。
⑧UnityObjectToWorldDir( in float3 dir )函数
:
inline float3 UnityObjectToWorldDir( in float3 dir )
{
return normalize(mul((float3x3)unity_ObjectToWorld, dir));
}
把方向矢量从 模型空间 转到 世界空间。