「UnityShader笔记」04.Blinn-Phong模型的实现
Part1 完整代码
Shader "Light/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 output;
output.pos=UnityObjectToClipPos(v.vertex);
output.worldNormal = mul(v.normal,(float3x3)unity_WorldToObject);
output.worldPos = mul(unity_ObjectToWorld,v.vertex).xyz;
return output;
}
fixed4 frag(v2f i) :SV_Target
{
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.rgb;
fixed3 worldDir = normalize(_WorldSpaceLightPos0.xyz);
fixed3 worldNormal = normalize(i.worldNormal);
fixed3 diffuse = _LightColor0.rgb*_Diffuse.rgb*saturate(dot(worldNormal,worldDir));
fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz);
fixed3 halfDir = normalize(worldDir + viewDir);
fixed3 specular = _LightColor0.rgb*_Specular.rgb*pow(saturate(dot(worldNormal,halfDir)),_Gloss);
fixed3 color = ambient + diffuse + specular;
return fixed4(color,1.0);
}
ENDCG
}
}
FallBack "Diffuse"
}
Part2 逐段解析
Properties
{
_Diffuse("Diffuse",Color) = (1,1,1,1)
_Specular("Specular",Color) = (1,1,1,1)
_Gloss("Gloss",Range(8.0,256)) = 20
}
首先定义属性
- _Diffuse为漫反射颜色,类型为Color,本应是取自贴图,但在本例简化情况下,所有点都赋予单色
- _Specular为高光颜色,类型为Color
- _Gloss为高光区域大小,限定取值范围在8~256
struct a2v
{
float4 vertex:POSITION;
float3 normal:NORMAL;
};
struct v2f
{
float4 pos:SV_POSITION;
float3 worldNormal : TEXCOORD0;
float3 worldPos : TEXCOORD1;
};
定义a2v结构体
- vertex:模型空间坐标
- normal:模型空间法向量
定义v2f结构体
- pos:裁剪空间坐标
- worldNormal:世界空间法向量
- worldPos:世界空间坐标
v2f vert(a2v v)
{
v2f output;
//使用UNITY内置的变换矩阵将模型空间的坐标一步变换到裁剪空间
output.pos=UnityObjectToClipPos(v.vertex);
//计算法线方向
output.worldNormal = mul(v.normal,(float3x3)unity_WorldToObject);
//计算世界坐标
output.worldPos = mul(unity_ObjectToWorld,v.vertex).xyz;
return output;
}
首先使用Unity内置函数将模型空间的坐标一步变换到裁剪空间
然后通过将模型空间法向量乘以变换矩阵unity_WorldToObject获得世界空间的法向量,该矩阵的作用本是将坐标从世界空间转换到模型空间,注意到它实际上是ObjcetToWorld的逆矩阵,同时注意到在坐标变换时,一般是变换矩阵左乘一个向量,此处为右乘,这是由于法线变换的特殊性,若直接使用ObjcetToWorld矩阵左乘模型空间法向量,并不能得到正确的结果,事实上,应该使用ObjcetToWorld矩阵的逆转置矩阵左乘模型空间法向量,这一部分的数学推导可以参考:
法线变换详解(Normal Transform)
同时,转置矩阵左乘与原矩阵右乘是等效的,所以使用WorldToObject(实际上是ObjcetToWorld的逆矩阵)变换矩阵右乘模型空间法向量,就可以得到正确的变换结果
最后计算世界坐标,使用ObjectToWorld左乘顶点坐标即可
fixed4 frag(v2f i) :SV_Target
{
//获取环境光
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.rgb;
//在世界空间中获取光照方向(单平行光源情形)
fixed3 worldDir = normalize(_WorldSpaceLightPos0.xyz);
//获取世界空间中法向量
fixed3 worldNormal = normalize(i.worldNormal);
//计算漫反射项
fixed3 diffuse = _LightColor0.rgb*_Diffuse.rgb*saturate(dot(worldNormal,worldDir));
//计算观察方向,观察方向 = 相机位置 - 世界坐标
fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz);
//计算半程向量,方法是将观察方向和光源方向进行向量和后再归一化
fixed3 halfDir = normalize(worldDir + viewDir);
//计算高光项,第三项意为法线方向和半程向量点乘后取gloss次方
fixed3 specular = _LightColor0.rgb*_Specular.rgb*pow(saturate(dot(worldNormal,halfDir)),_Gloss);
fixed3 color = ambient + diffuse + specular;
return fixed4(color,1.0);
}
首先使用Unity内置变量获取环境光ambient、世界空间光照方向worldDir
然后计算漫反射项diffuse,式中_LightColor0为直线平行光光照颜色,必须在开头指定了光照模式为FowardBase,才可以使用这个变量。关于这部分信息可以参考:
_LightColor0将会是主要的directional light的颜色。
随后计算观察方向,直接在世界坐标系下将摄像机坐标和顶点坐标相减再归一化即可
计算半程向量halfDir,方法是光源方向和观察方向进行矢量和,再归一化
计算了所有需要的向量后,便可以计算高光项,随后将环境光项,漫反射项,高光项全部加和,得到Bulinn-Phong模型的颜色输出,效果如下:
可以调整Gloss值来改变高光区的大小,Gloss越大,高光区越小,下图为将Gloss设置到256的结果: