写在前面:
高光反射一般建立在已有漫反射的基础上对模型进行颜色的叠加渲染。在这里我们多个一个对象——视野方向。
高光反射的计算包含平行光方向、视野方向、反射光方向等元素。
逐顶点:
Shader "MyShader/Specular_Vertex"{
ProPerties{
_Diffuse("Diffuse Color",Color)=(1,1,1,1)
_Gloss("Specular Gloss",Range(1,20))=20
_Specular("Specular Color",Color)=(1,1,1,1)
}
SubShader{
Pass{
Tags{"LightMode"="ForwardBase"};
CGPROGRAM
#include "Lighting.cginc"
#pragma Vertex vert
#pragma Fragment frag
fixed4 _Diffuse;
half _Gloss;
fixed4 _Specular;
struct a2v{
float4 ps:POSITION;
fixed3 nm:NORMAL;
};
struct v2f{
float4 position:SV_POSITION;
fixed3 cl:COLOR;
};
v2f vert(a2v v){
v2f f;
f.position=mul(v.ps,UNITY_MATRIX_MVP);
fixed3 ambient=UNITY_LIGHTMODEL_AMBIENT.rgb;
fixed3 normalDir=normalize(mul(v.nm,(float3x3)_World2Object));
fixed3 lightDir=normalize(_WorldSpaceLightPos0.xyz);
fixed3 halfLambert=dot(lightDir,normalDir)*.5+0.5;
fixed3 Diffuse=_LightColor0*halfLambert*_Diffuse;
//高光反射计算
fixed3 reflectDir=normalize(reflect(-lightDir,normalDir));//反射光方向
fixed3 viewDir=normalize(_WorldSpaceCameraPos.xyz-mul(v.ps,_World2Object);//视野方向=摄像机坐标-顶点坐标
fixed3 Specular=_LightColor*pow(max(dot(reflectDir,viewDir),0),_Gloss)*_Specular;//Specular=直射光颜色*pow(max(视野方向和反射光方向之间的点积,0),光泽度)*高光颜色
f.cl=Diffuse+ambient+Specular;
return f;
}
fixed4 frag(v2f f):SV_Target{
return fixed4(f.cl,1);
}
ENDCG
}
}
Fallback "VertexLit"
}
当然,一般情况下我们都常用的是BlinnPhong光照模型,以上部分代码稍作修改:
fixed3 reflectDir=normalize(reflect(-lightDir,normalDir));//反射光方向
fixed3 viewDir=normalize(_WorldSpaceCameraPos.xyz-mul(v.ps,(float3x3)_World2Object);//视野方向=摄像机坐标-顶点坐标
fixed3 Specular=_LightColor*pow(max(dot(reflectDir,viewDir),0),_Gloss)*_Specular;//Specular=直射光颜色*pow(max(视野方向和反射光方向之间的点积,0),光泽度)*高光颜色
修改为:
fixed3 viewDir=normalize(_WorldSpaceCameraPos.xyz-mul(v.ps,(float3x3)_World2Object);//视野方向=摄像机坐标-顶点坐标
fixed3 halfBlinn=normalize(lightDir+viewDir);//将平行光和视野方向的平分线单位化
fixed3 Specular=_LightColor*pow(max(dot(normalDir,halfBlinn),0),_Gloss)*_Specular;//BlinnPhong计算公式:高光反射=直射光颜色*pow(max(法线和平行光与视野方向的平分线的点积,0),光泽度)*高光颜色
逐像素:
Shader "MyShader/Specular_Fragment"{
Properties{
_Diffuse("Diffuse Color",Color)=(1,1,1,1)
_Gloss("Specular Gloss",Range(1,20))=20
_Specular("Specular Color",Color)=(1,1,1,1)
}
SubShader{
Pass{
Tags{"LightMode"="ForwardBase"};
CGPROGRAM
#include "Lighting.cginc"
#pragma Vertex vert
#pragma Fragment frag
fixed4 _Diffuse;
half _Gloss;
fixed4 _Specular;
struct a2v{
float4 ps:POSITION;
fixed3 nm:NORMAL;
};
struct v2f{
float4 position:SV_POSITION;
float3 vertexV:TEXCROOD0;
float3 normalV:TEXCROOD1;
};
v2f vert(a2v v){
v2f f;
f.position=mul(v.ps,UNITY_MATRIX_MVP);
f.worldNormal=mul(v.nm,(float3x3)unity_WorldToObject);//世界空间下的法线方向
f.worldVertex=mul(v.ps,unity_WorldToObject);//世界空间下的顶点坐标
return f;
}
fixed4 frag(v2f f):SV_Target{
fixed3 ambient=UNITY_LIGHTMODEL_AMBIENT.rgb;
fixed3 normalDir=normalize(f.worldNormal);
fixed3 lightDir=normalize(_WorldSpaceLightPos0.xyz);
fixed3 halfLambert=dot(lightDir,normalDir)*.5+0.5;
fixed3 Diffuse=_LightColor0*halfLambert*_Diffuse;
//高光反射计算
fixed3 viewDir=normalize(_WorldSpaceCameraPos.xyz-f.worldVertex);//视野方向=摄像机坐标-顶点坐标
fixed3 halfBlinn=normalize(lightDir+viewDir);//将平行光和视野方向的平分线单位化
fixed3 Specular=_LightColor*pow(max(dot(normalDir,halfBlinn),0),_Gloss)*_Specular;//BlinnPhong计算公式:高光反射=直射光颜色*pow(max(法线和平行光与视野方向的平分线的点积,0),光泽度)*高光颜色
fixed3 tempColor=Diffuse+ambient+Specular;
return fixed4(tempColor,1);
}
ENDCG
}
}
Fallback "VertexLit"
}
逐顶点光照和逐像素光照渲染出来的效果对比:
使用BlinnPhong和不使用的对比:
UnityCG.cginc中一些常用的函数:
摄像机方向:
float3 WorldSpaceViewDir(float4 v) 根据模型空间下的顶点坐标得到从这个点到摄像机的观察方向
float3 UnityWorldSpaceViewDir(float4 v) 把世界空间下的顶点坐标转换成从这个点到摄像机的观察方向
float3 ObjSpaceViewDir(float4 v) 把模型空间中的顶点坐标转换成模型空间中从这个点到摄像机的观察方向
//光源方向:
float3 WorldSpaceLightDir(float4 v) 根据模型空间中的顶点坐标得到从这个点到光源的方向
float3 UnityWorldSpaceLightDir(float4 v) 把世界空间下的顶点坐标转换成从这个点到光源的方向
float3 ObjSpaceLightDir(float4 v) 把模型空间中的顶点坐标转换成模型空间中从这个点到光源的方向
//方向转换
float3 UnityObjectToWorldNormal(float3 nom) 把法线方向从模型空间转换到世界空间
float3 UnityObjectToWorldDir(float3 dir)把方向从模型空间转换到世界空间
float3 UnityWorldToObjectDir(float3 dir)把方向从世界空间转换到模型空间
示例:
以上代码中
f.worldNormal=mul(v.nm,(float3x3)unity_WorldToObject);
fixed3 lightDir=normalize(_WorldSpaceLightPos0.xyz);
fixed3 viewDir=normalize(_WorldSpaceCameraPos.xyz-f.worldVertex);
这几行代码可可写作
f.worldNormal=UnityObjectToWorldNormal(v.nm);
fixed3 lightDir=normalize(UnityWorldSpaceLightDir(f.worldVertex).xyz);
fixed3 viewDir=normalize(UnityWorldSpaceViewDir(f.worldVertex).xyz);