1.lambert(兰伯特)
效果:
原理:
我们判断灯光方向(lightDirection)与法线方向(normalDirection)的角度来决定受光情况。那么用灯光方向与法线方向的点积(N dot L)就能很好的判断。但是按照图示点积出来的结果,光亮的地方的结果为-1,暗处反而为1。这是不符合直觉且会让之后的计算有误。所以我们在做计算时获取灯光的反方向,这样亮处为1(纯白),暗处为-1(纯黑),明暗交接处为0(纯黑)。
(截图来自-庄懂的技术美术入门课(美术向)-直播录屏-第1课)
其中,负数没有意义,而且有可能会干扰最终的计算结果,并且与0的结果相同。所以我们只要0~1之间的结果即可。那么这时候,max()函数就能发挥作用了,具体为:Max(0,nDir*lDir)意思是0与nDir*lDir结果取更大的,既0之前的所有结果都取0。
(截图来自-庄懂的技术美术入门课(美术向)-直播录屏-第1课)
代码实现:
Shader "Lambert" {
Properties {
}
SubShader {
Tags {
"RenderType"="Opaque"
}
Pass {
Name "FORWARD"
Tags {
"LightMode"="ForwardBase"
}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include "AutoLight.cginc"
struct VertexInput {
float4 vertex : POSITION;
float3 normal : NORMAL;
};
struct VertexOutput {
float4 pos : SV_POSITION;
float4 posWorld : TEXCOORD0;
float3 normalDir : TEXCOORD1;
LIGHTING_COORDS(2,3)
};
VertexOutput vert (VertexInput v) {
VertexOutput o = (VertexOutput)0;
//将模型空间的法线转换为世界空间法线
o.normalDir = UnityObjectToWorldNormal(v.normal);
//模型空间的顶点位置转换为世界空间
o.posWorld = mul(unity_ObjectToWorld, v.vertex);
//将顶点位置从模型空间转换为裁剪空间
o.pos = UnityObjectToClipPos( v.vertex );
TRANSFER_VERTEX_TO_FRAGMENT(o)
return o;
}
float4 frag(VertexOutput i) : COLOR {
//获取法线方向并归一化,确保在后续的光照计算正确
i.normalDir = normalize(i.normalDir);
//获取光照方向并归一化,确保在后续的光照计算正确
float3 lightDirection = normalize(_WorldSpaceLightPos0.xyz);
// Lighting:
// Emissive:
float Lambert = saturate(dot(i.normalDir,lightDirection));
//创建了一个三维向量
float3 emissive = float3(Lambert,Lambert,Lambert);
return fixed4(emissive,1);
}
ENDCG
}
}
FallBack "Diffuse"
}
2.half lambert(半兰伯特)
效果:
原理:
我们上面看到了Lambert的效果,但是Lambert的明暗交接过于分明,且暗部死黑并不算好看,为了让暗部更好看,就有了半Lambert的这个方法。回到之前(-1,1)的结果,这时,我们对其*0.5再加上0.5,就能极大改善死黑的情况。
(截图来自-庄懂的技术美术入门课(美术向)-直播录屏-第1课)
代码实现:
我们只需要将
float Lambert = saturate(dot(i.normalDir,lightDirection));
改为
float Lambert = ((dot(i.normalDir,lightDirection)*0.5)+0.5);
3.phong高光模型
效果:
原理:
光照 = 漫反射(Diffuse) + 高光反射(Specular) + 环境光(Ambient)
其中,漫反射既是我们上面讲到的Lambert模型,总结来说就是漫反射光线的强度与物体表面的法线和光源方向之间夹角的余弦值成正比。
(截图来自-GAMES101-现代计算机图形学入门-闫令琪-第8课)
而phong模型的核心就是高光反射(Specular),简单解释就是光线经过物体表面,反射到视野中反射强度取决于观察方向(vDir)和光反射方向(reflectDir)夹角。光反射方向(reflectDir)由光方向的负方向(-lDir)点乘(dot)法线方向(nDir)得到
(截图来自-庄懂的技术美术入门课(美术向)-直播录屏-第5课)
值得注意的是,关于观察方向(vDir)的获取,我们首先通过unity的内置函数_WorldSpaceCameraPos.xyz获取世界空间下相机的位置
再通过将 o.worldPos=UnityObjectToWorldDir(v.vertex);将顶点从模型空间转换到世界空间,使得位置在整个场景中被正确定位。最后用_WorldSpaceCameraPos.xyz - i.worldPos得到观察方向。
代码实现:
Shader "phong"
{
Properties
{
_Diffuse ("Diffuse", Color) = (1,1,1,1)
_Specular("Specular", Color) = (1,1,1,1)
_Gloss("Gloss",Range(8.0,256)) = 20
_MainTex ("Texture", 2D) = "white" {}
}
SubShader
{
Tags { "RenderType"="Opaque" "LightMode"="ForwardBase"}
LOD 100
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include "Lighting.cginc"
struct a2v
{
float4 vertex : POSITION;
float3 normal : NORMAL;
float2 uv : TEXCOORD0;
};
struct v2f
{
float2 uv : TEXCOORD0;
float3 normal:NORMAL;
float4 vertex : SV_POSITION;
float3 worldPos : TEXCOORD1;
};
fixed4 _Diffuse;
sampler2D _MainTex;
float4 _MainTex_ST;
fixed4 _Specular;
float _Gloss;
v2f vert (a2v v)
{
v2f o;
//顶点坐标转换
o.vertex = UnityObjectToClipPos(v.vertex);
//获取法线(统一到世界坐标系下)。
o.normal = UnityObjectToWorldNormal(v.normal);
//获取UV
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
//顶点坐标转世界坐标
o.worldPos=UnityObjectToWorldDir(v.vertex);
//mul(unity_ObjectToWorld,v.vertex).xyz;
return o;
}
fixed4 frag (v2f i) : SV_Target
{
//获取平行光
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
//获取归一化的法线
fixed3 worldNormal = normalize(i.normal);
//fixed3 worldNormal = i.normal;
//获取环境光的方向
fixed3 worldLight = normalize(_WorldSpaceLightPos0.xyz);
//兰伯特:漫反射颜色 = 光源颜色 x 材质的漫反射颜色 x Max(0,Dot(法线,指向光源的方向))
//计算光照强度
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb*saturate(dot(worldNormal,worldLight));
//高光反射颜色 = 入射光颜色 X 高光反射颜色 X max(0,Dot(视角方向,反射方向))^反射系数
//计算反射方向
fixed3 reflectDir = normalize(reflect(-worldLight,worldNormal));
//获取视线方向
fixed3 view = normalize(_WorldSpaceCameraPos.xyz - i.worldPos);
//计算高光反射
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(reflectDir,view)),_Gloss);
//叠加
fixed3 color = ambient + diffuse + specular;
//获取贴图颜色
fixed4 col = tex2D(_MainTex, i.uv);
//将贴图颜色与顶点计算的颜色相乘
col=fixed4(col.rgb*color.rgb,1.0);
return col;
}
ENDCG
}
}
}
4.Blinn-phong光照模型
效果:
原理:
Blinn-phong模型可以说是phong模型的升级版,phong模型中,计算反射光线是一件比较耗时的事情,那么我们该怎么改进呢?
人们发现一个现象:当观察方向(vDir)与镜面反射方向也就是光反射方向(rDir)接近的时候,也就说明法线方向(nDir)和半程向量(hDir)也就是l和v的角平分线很接近。所以,判断是否看得到高光,只需要判断n和h是否接近,也就是通过点乘。以此可以简化对于反射光的计算。
(截图来自-GAMES101-现代计算机图形学入门-闫令琪-第8课)
代码实现:
Shader "BlinnPhone"
{
Properties
{
_Diffuse("Difuse", Color) = (1, 1, 1, 1) //漫反射颜色属性
_Specular("Specular", Color) = (1, 1, 1, 1) //高光反射颜色
_Gloss("Gloss", Range(8.0, 256)) = 20 //控制高光区域大小
}
SubShader{
Pass{
//LightMode 定义Pass在Unity光照流水线中的角色
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;
o.pos = UnityObjectToClipPos(v.vertex);
o.worldNormal = mul(v.normal, (float3x3)unity_WorldToObject);
o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
return o;
}
fixed4 frag(v2f i) : SV_Target{
//漫反射
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
fixed3 worldNormal = normalize(i.worldNormal);
fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal, worldLightDir));
//高光反射
fixed3 reflectDir = normalize(reflect(-worldLightDir, worldNormal));
fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz);
fixed3 halfDir = normalize(worldLightDir + viewDir);
fixed3 specular = _LightColor0.rgb * _Specular * pow(saturate(dot(worldNormal, halfDir)),_Gloss);
return fixed4(ambient + diffuse + specular, 1.0);
}
ENDCG
}
}
Fallback "Specular"
}
欢迎大家指正,谢谢!