兰伯特
漫反射,是投射在粗糙表面上的光向各个方向反射的现象。当一束平行的入射光线射到粗糙的表面时,表面会把光线向着四面八方反射,所以入射线虽然互相平行,由于各点的法线方向不一致,造成反射光线向不同的方向无规则地反射,这种反射称之为“漫反射”或“漫射”。这种反射的光称为漫射光。很多物体,如植物、墙壁、衣服等,其表面粗看起来似乎是平滑,但用放大镜仔细观察,就会看到其表面是凹凸不平的,所以本来是平行的太阳光被这些表面反射后,弥漫地射向不同方向。
(这图盗的别人的,直观,非原创)
漫反射光照符合兰伯特定律:反射光线的强度与表面法线和光源方向之间的夹角的余弦成正比,因此漫反射的部分计算如下:
Cdiffuse =( Clight *Mdiffuse )max(0,dot(N,L))
N是表面法线,L是光源方向的单位矢量,Mdiffuse是材质的漫反射颜色,Clight是光源颜色。我们使用取最大值函数来将其截取到0,还可以防止物体被从后面来的光源照亮。
逐顶点计算着色shader
我们在shader中需要计算输出的颜色,逐顶点着色也就是说我们的计算主要放在了vertex shader中,根据顶点来计算,每个顶点中计算出了该点的颜色,直接作为vertex shader的输出,pixel(fragment) shader的输入,当到达pixel阶段时,直接输出顶点shader的结果。比如一个三角形面片,在vertex阶段,分别计算了每个顶点的颜色值,在pixel阶段时,这个面片经过投影,最终显示在屏幕上的像素,会根据该像素周围的顶点来插值计算像素的最终颜色,这种着色方式也叫做高洛德着色。
Shader "ApcShader/DiffusePerVetex"
{
//属性
Properties{
_Diffuse("Diffuse", color) = (1,1,1,1)
}
//子着色器
SubShader
{
Pass
{
//定义Tags
Tags{ "RenderType" = "Opaque" }
CGPROGRAM
//引入头文件 ,一些内置变量,如_LightColor0,需要使用到
#include "Lighting.cginc"
//定义Properties中的变量
fixed4 _Diffuse;
//使用vert函数和frag函数
#pragma vertex vert
#pragma fragment frag
//定义结构体:应用阶段到vertex shader阶段的数据,如果定义了
struct a2v
{
float4 vertex : POSITION;
float3 normal : NORMAL;
};
//定义结构体:vertex shader阶段输出的内容
struct v2f
{
float4 pos : SV_POSITION;
fixed4 color : COLOR;
};
//定义顶点shader
v2f vert(a2v v)
{
v2f o;
//模型顶点转换
o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
//环境光
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
//把法线转化到世界空间
float3 worldNormal = mul(v.normal, (float3x3)_World2Object);
//归一化法线
worldNormal = normalize(worldNormal);
//把光照方向归一化
fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
//根据兰伯特模型计算顶点的光照信息,dot可能有负值,小于0的部分可以理解为看不见,直接取0 ,lambert光强*材质diffuse颜色*光颜色
fixed3 diffuse = _LightColor0.xyz*_Diffuse.xyz*max(0.0, dot(worldNormal, worldLightDir));
o.color = fixed4(diffuse + ambient, 1.0);
return o;
}
//定义片元shader
fixed4 frag(v2f i) : SV_Target
{
return i.color;
}
ENDCG
}
}
//前面的SubShader失效的话,使用默认的Diffuse
FallBack "Diffuse"
}
逐像素计算着色shader
逐像素计算时,我们的主要计算放到了pixel shader里,在vertex shader阶段只是进行了基本的顶点变换操作,以及顶点的法线转化到世界空间的操作,然后将转化后的法线作为参数传递给pixel shader。其他的计算都放到了pixel shader阶段,这样,针对每个像素,我们都可以来计算这个像素的光照情况,而不是像逐顶点计算时,先计算好顶点的颜色,然后差值得到中间的像素颜色。这种逐像素着色的方式也叫作Phone着色(非Phone光照模型)
Shader "Custom1/LambertFragment"
{
//属性
Properties{
_Diffuse("Diffuse", Color) = (1,1,1,1)
}
//子着色器
SubShader
{
Pass
{
//定义Tags
Tags{ "RenderType" = "Opaque" }
CGPROGRAM
//引入头文件
#include "Lighting.cginc"
//定义Properties中的变量
fixed4 _Diffuse;
//定义结构体:应用阶段到vertex shader阶段的数据
struct a2v
{
float4 vertex : POSITION;
float3 normal : NORMAL;
};
//定义结构体:vertex shader阶段输出的内容
struct v2f
{
float4 pos : SV_POSITION;
float3 worldNormal : TEXCOORD0;
};
//定义顶点shader
v2f vert(a2v v)
{
v2f o;
o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
//把法线转化到世界空间
o.worldNormal = mul(v.normal, (float3x3)_World2Object);
return o;
}
//定义片元shader
fixed4 frag(v2f i) : SV_Target
{
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
//归一化法线,即使在vert归一化也不行,从vert到frag阶段有差值处理,传入的法线方向并不是vertex shader直接传出的
fixed3 worldNormal = normalize(i.worldNormal);
//把光照方向归一化
fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
//根据兰伯特模型计算像素的光照信息,小于0的部分理解为看不见,置为0
fixed3 lambert = max(0.0, dot(worldNormal, worldLightDir));
//最终输出颜色为lambert光强*材质diffuse颜色*光颜色
fixed3 diffuse = lambert * _Diffuse.xyz * _LightColor0.xyz;
return fixed4(diffuse+ambient, 1.0);
}
//使用vert函数和frag函数
#pragma vertex vert
#pragma fragment frag
ENDCG
}
}
//前面的Shader失效的话,使用默认的Diffuse
FallBack "Diffuse"
}
半兰伯特模型
广义的半兰伯特模型公式如下:
Cdiffuse =( Clight *Mdiffuse )*(阿鲁法dot(N,L)+贝塔)
可以看到与原兰伯特模型相比,半兰伯特没有使用max操作来防止点积为负,而是对结果进行了一个阿鲁法倍的缩放再加上一个贝塔大小的偏移。当然绝大多情况阿鲁法和贝塔的值都为0.5
Shader "Custom1/LambertFragment"
{
//属性
Properties{
_Diffuse("Diffuse", Color) = (1,1,1,1)
}
//子着色器
SubShader
{
Pass
{
//定义Tags
Tags{ "RenderType" = "Opaque" }
CGPROGRAM
//引入头文件
#include "Lighting.cginc"
//定义Properties中的变量
fixed4 _Diffuse;
//定义结构体:应用阶段到vertex shader阶段的数据
struct a2v
{
float4 vertex : POSITION;
float3 normal : NORMAL;
};
//定义结构体:vertex shader阶段输出的内容
struct v2f
{
float4 pos : SV_POSITION;
float3 worldNormal : TEXCOORD0;
};
//定义顶点shader
v2f vert(a2v v)
{
v2f o;
o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
//把法线转化到世界空间
o.worldNormal = mul(v.normal, (float3x3)_World2Object);
return o;
}
//定义片元shader
fixed4 frag(v2f i) : SV_Target
{
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
//归一化法线,即使在vert归一化也不行,从vert到frag阶段有差值处理,传入的法线方向并不是vertex shader直接传出的
fixed3 worldNormal = normalize(i.worldNormal);
//把光照方向归一化
fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
//根据半兰伯特模型计算像素的光照信息
fixed3 lambert = 0.5* dot(worldNormal, worldLightDir)+0.5;
//最终输出颜色为lambert光强*材质diffuse颜色*光颜色
fixed3 diffuse = lambert * _Diffuse.xyz * _LightColor0.xyz;
return fixed4(diffuse+ambient, 1.0);
}
//使用vert函数和frag函数
#pragma vertex vert
#pragma fragment frag
ENDCG
}
}
//前面的Shader失效的话,使用默认的Diffuse
FallBack "Diffuse"
}
如图,左边是兰伯特,右边是半兰伯特。对比效果还是比较明显的
.Half Lambert最初是由Valve(游戏半条命2使用的引擎即是其开发的)提出来,用于提高物体在一些光线无法照射到的区域的亮度的。简单说来,它提高了漫反射光照的亮度,使得漫反射光线可以看起来照射到一个物体的各个表面。而Half Lambert最初也是被用于游戏半条命的画面渲染,为了防止某个物体的背光面丢失形状并且显得太过平面化。这个技术是完全没有基于任何物理原理的,而仅仅是一种感性的视觉增强
相关阅读
【Unity Shader】 Lambert(兰伯特)光照模型
Unity Shader Lambert光照模型与漫反射 一、漫反射简介 漫反射的特点 二、漫反射光照模型-Lambert(兰伯特)光照模型 Lambert 余弦