模拟真实的光照环境:
高光反射(specular):表示物体表面是如何反射光线的
漫反射(diffuse):表示有多少光线被折射、吸收和散射出表面
标准光照模型:
直接光照(direct light):直接从光源发射出来经过物体表面一次发射后进入摄像机的光线
基本方法:把进入到摄像机的光线分为4部分,每一部分单独计算贡献度
- 自发光(emissive)部分:当给定一个方向时,物体本身会向这个方向发射多少辐射量。
- 高光反射(specular)部分:描述当光线从光源照射到模型表面时,该表面会在完全镜面反射方向散射多少辐射量。
- 漫反射(diffuse)部分:描述当光线从光源照射到模型表面时,该表面会向每个方向散射多少辐射量。
- 环境光(ambient)部分:描述其他所有的间接光照。
环境光:一般是一个全局变量,即场景中的所有物体都使用同一个环境光
自发光:直接使用该材质的自发光颜色
漫反射:符合兰伯特定律(Lambert’s Law):反射光线的强度与表面法线和光源方向的夹角的余弦值成正比
- :光源颜色
- :材质漫反射颜色
- :表面法线
- :指向光源的单位矢量
- :防止点乘结果为负值
高光反射:
需要知道的信息:表面法线
、视角方向
、光源方向
、反射方向
Phong模型:
- :光源颜色和强度
- :材质的高光反射颜色和强度
- :材质的光泽度,越大,高光区域亮点越小
Blinn模型:
为了避免计算反射方向
,引入一个新的矢量
,
是由视角方向
与光源方向
取平均再归一化得到的,即
然后使用
和
之间的夹角进行计算
如果摄像机和光源距离模型足够远的情况下,我们可以认为
和 是定值,所以 是一个常量,此时Blinn模型会快于Phong模型
逐顶点光照(per-vertex lighting):在顶点着色器中使用基本光照模型
逐像素光照(per-pixel lighting):在片元着色器中使用基本光照模型
Unity中的环境光与自发光:
环境光:
在Shader中,只需要通过内置变量
UNITY_LIGHTMODEL_AMBIENT
就可以直接得到环境光的信息
大多数物体是没有自发光特性的,所以不用去计算;如果非要计算自发光的话,就在片元着色器输出最后颜色之前,将材质的自发光颜色加进去就可以
在Unity Shader中实现漫反射光照模型
逐顶点光照:
- 新建一个材质Material
- 新建一个Shader并赋给Material
- 创建一个Capsule,把Material赋给该胶囊体
Shader内容为:
Shader "Custom/Chapter6-DiffuseVertexLevel" {
Properties{
//声明一个颜色属性:白色
_Diffuse ("Diffuse", Color) = (1, 1, 1, 1)
}
SubShader{
Pass{
//定义该Pass在渲染中的角色
Tags{"LightMode" = "ForwardBase"}
//以下为Cg代码片,ENDCG结束
CGPROGRAM
#pragma vertex vert//顶点着色器
#pragma fragment frag//片元着色器
//包含内置的光照模型
#include "Lighting.cginc"
//使用定义的属性必须定义一个与属性相匹配的变量
//由于颜色范围在(0,1),所以使用fixed精度的变量
fixed4 _Diffuse;
//定义结构
struct a2v{
float4 vertex:POSITION;//顶点位置
float3 normal:NORMAL;//顶点法线
};
struct v2f{
float4 pos:SV_POSITION;
fixed3 color:COLOR;
};
//顶点着色器
v2f vert(a2v v){
v2f o;
//将顶点坐标从模型空间变换到裁剪空间
o.pos = UnityObjectToClipPos(v.vertex);
//获得环境光
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
////将法线方向从模型空间变换到世界空间
fixed3 worldNormal = normalize(mul(v.normal, (float3x3)unity_WorldToObject));
//世界空间中的光线方向
fixed3 worldLight = normalize(_WorldSpaceLightPos0.xyz);
//按照公式计算漫反射 saturate在这里等同于max
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal,worldLight));
//最终颜色值等于环境光加漫反射
o.color = ambient + diffuse;
return o;
}
//片元着色器
//把顶点颜色输出
fixed4 frag(v2f i) :SV_Target {
return fixed4(i.color,1.0);
}
ENDCG
}
}
//回调Shader设置为内置Diffuse
Fallback "Diffuse"
}
与原来代码相比:
- Unity自动将模型-世界-投影矩阵
UNITY_MATRIX_MVP
与顶点坐标的点乘替换为UnityObjectToClipPos
函数,将顶点坐标从模型空间(object space)变换到裁剪空间(clipping space),更直观更好理解
运行结果:
逐像素光照:
Shader "Custom/Chapter6-DiffusePixelLevel" {
Properties{
//声明一个颜色属性:白色
_Diffuse ("Diffuse", Color) = (1, 1, 1, 1)
}
SubShader{
Pass{
//定义该Pass在渲染中的角色
Tags{"LightMode" = "ForwardBase"}
//以下为Cg代码片,ENDCG结束
CGPROGRAM
#pragma vertex vert//顶点着色器
#pragma fragment frag//片元着色器
//包含内置的光照模型
#include "Lighting.cginc"
//使用定义的属性必须定义一个与属性相匹配的变量
//由于颜色范围在(0,1),所以使用fixed精度的变量
fixed4 _Diffuse;
//定义结构
struct a2v{
float4 vertex:POSITION;//顶点位置
float3 normal:NORMAL;//顶点法线
};
struct v2f{
float4 pos:SV_POSITION;
fixed3 worldNormal:TEXCOORD0;
};
//顶点着色器
v2f vert(a2v v){
v2f o;
//将顶点坐标从模型空间变换到裁剪空间
o.pos = UnityObjectToClipPos(v.vertex);
//将法线方向从模型空间变换到世界空间
o.worldNormal = mul(v.normal, (float3x3)unity_WorldToObject);
return o;
}
//片元着色器
fixed4 frag(v2f i) :SV_Target {
//获得环境光
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
//获取世界空间的法线方向
fixed3 worldNormal = normalize(i.worldNormal);
//获取世界空间中的光线方向
fixed3 worldLight = normalize(_WorldSpaceLightPos0.xyz);
//按照公式计算漫反射 saturate在这里等同于max
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal,worldLight));
//最终颜色值等于环境光加漫反射
fixed3 color = ambient + diffuse;
return fixed4(color,1.0);
}
ENDCG
}
}
//回调Shader设置为内置Diffuse
Fallback "Diffuse"
}
运行结果:
逐像素光照可以达到更加平滑的光照效果,但是有一个问题存在:在光照无法到达的区域,模型一般是黑的,没有任何明暗变化。这可以通过添加环境光来改善,但是仍然有背光面的存在。因此,半兰伯特光照模型被提出
半兰伯特光照模型:Half Lambert
为了解决上述问题,Valve公司在开发《半条命》的时候提出了一种新技术,在原Lambert模型上进行了修改
与原模型相比,半兰伯特光照模型并没有使用max来限定
,
为(0,1),而是对其结果进行一个
倍的缩放在进行一个
的偏移,一般默认为0.5
,当
·
为负值时,即模型的背光面,点积值都变为了0,这样就导致没有明暗变化,而
将点积结果映射到了不同的值上
半兰伯特光照模型是没有任何物理依据的,只是一种视觉加强技术
在Shader代码中按公式修改漫反射计算部分即可
运行结果:
在Unity Shader中实现高光反射光照模型
逐顶点光照:
公式回忆:
在Cg中提供了反射方向
的计算函数reflect(i, n)
,i
为入射方向,一般为光源方向的相反方向
运行结果:
可以观察到高光部分不平滑,主要的原因是高光的计算是非线性的,而逐顶点的计算是线性的,这破坏了原计算的非线性关系,就出现以上问题
逐像素光照:
顶点着色器只需要计算世界空间下的法线方向和顶点坐标,然后传递给片元着色器即可
运行结果:
Blinn-Phong光照模型:
Blinn模型没有使用反射方向
,而是定义了一个新的矢量
按照公式将高光计算部分修改即可
//计算h
fixed3 h = normalize(worldLight + viewDir);
//计算高光
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(worldNormal, h)), _Gloss);
运行结果:
可以看出:Blinn-Phong模型的高光反射部分更大更亮一点,大多数情况下,在渲染的过程中都会选择此模型。