模拟真实的光照环境:
高光反射(specular):表示物体表面是如何反射光线的
漫反射(diffuse):表示有多少光线被折射、吸收和散射出表面
直接光照(direct light):直接从光源发射出来经过物体表面一次发射后进入摄像机的光线
基本方法:把进入到摄像机的光线分为4部分,每一部分单独计算贡献度
环境光:一般是一个全局变量,即场景中的所有物体都使用同一个环境光
自发光:直接使用该材质的自发光颜色
漫反射:符合兰伯特定律(Lambert’s Law):反射光线的强度与表面法线和光源方向的夹角的余弦值成正比
高光反射:
需要知道的信息:表面法线
、视角方向
、光源方向
、反射方向
Phong模型:
Blinn模型:
为了避免计算反射方向
,引入一个新的矢量
,
是由视角方向
与光源方向
取平均再归一化得到的,即
然后使用
和
之间的夹角进行计算
如果摄像机和光源距离模型足够远的情况下,我们可以认为
和 是定值,所以 是一个常量,此时Blinn模型会快于Phong模型
逐顶点光照(per-vertex lighting):在顶点着色器中使用基本光照模型
逐像素光照(per-pixel lighting):在片元着色器中使用基本光照模型
Unity中的环境光与自发光:
环境光:
在Shader中,只需要通过内置变量
UNITY_LIGHTMODEL_AMBIENT
就可以直接得到环境光的信息
大多数物体是没有自发光特性的,所以不用去计算;如果非要计算自发光的话,就在片元着色器输出最后颜色之前,将材质的自发光颜色加进去就可以
逐顶点光照:
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_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代码中按公式修改漫反射计算部分即可
运行结果:
逐顶点光照:
公式回忆:
在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模型的高光反射部分更大更亮一点,大多数情况下,在渲染的过程中都会选择此模型。