![86b5bf56aca6c078e4beade08c9f664f.png](https://i-blog.csdnimg.cn/blog_migrate/df1669cca2a30ac39ac95e5be19d37ba.jpeg)
UnityShader入门精要—基础光照
(此文为易于理解的演讲配合文档,设计到专业词汇和解释可能不当)
一:物体接受光照并显示到摄像机的物理过程
1.光源发射光
2.物体与光交互发生“散射”以及“吸收”两个结果
散射:不改变光本身的物理属性(颜色,密度等),只改变光的方向。但是散射根据最终结果的不同方向又分为两类,到物体内部,这种现象称之为“折射”或“透射”,另一种方向为为散射到物体外部,即反射。
吸收:只改变光的本身物理属性,不改变方向。光被吸收后通常转为其他形式的能量,比如热。
下图反应了实际光与物体表面交互的过程和结果,对于不透明物体,折射到内部的光线还会继续交互,最终部分光线重新从表面出射。
![9bddaf065b93a4a89dbdce3e32e196c2.png](https://i-blog.csdnimg.cn/blog_migrate/030cea92f28b0be297e3054a9d7955b6.png)
3.最终离开物的光到部分到达摄像机,摄像机形成影像
二:模拟物体与光交互结果
光照模型概念:即根据物体表面属性,光源信息,使用某一个数学公式计算,最终得到沿某个观察方向出射的光的集合。
标准光照模型(基于经验的模拟)
标准光照模型最终到传到摄像机的光线,分为自发光,高光反射,漫反射,环境光四个部分即
![81ddc9412eebd8d2e60214ab469873b7.png](https://i-blog.csdnimg.cn/blog_migrate/82ef99d47440534203dc15b326029832.png)
漫反射(diffuse)
漫反射(diffuse)即光经过表面交互(折射吸收等)后,会向每个方向出射多少量的光,因此在任意方向观察,漫反射结果都是一样的,计算公式(由符合兰伯特定律推出):出射光线强度与表面法线和光源方向之间夹角成正比,即
![1db4c5a7dc35a58211c3fa8f85fad787.png](https://i-blog.csdnimg.cn/blog_migrate/34cc15f61acf24b19952aec17212164f.png)
其中,n是法线方向,I是指向光源的单位矢量,
此外,还有一种计算公式,即半兰伯特模型,通过缩放和偏移映射原来的值域到新值域,例如 [-1,1] 映射到 [0,1],这种模型计算的漫反射看起来整体更亮,有些领域也在使用,如下图效果对比
![37512c353c21efa04efc92a9e04e6e6c.png](https://i-blog.csdnimg.cn/blog_migrate/982d054964af018f2921d4b45a92415c.png)
计算公式为:
高光反射(spscular)
高光反射用于计算光跟表面交互后,会在完全镜面反射方向出射多少光,这些镜面反射的光投影到我们的观察方向,也就形成了高光区域
这一部分计算结果使用以下公式(Phong模型)
r=2(ncdot I)n-I
![1db4c5a7dc35a58211c3fa8f85fad787.png](https://i-blog.csdnimg.cn/blog_migrate/34cc15f61acf24b19952aec17212164f.png)
其中
两者都是经验模型,并不是正确的,他们分别在某些情况计算效率会高于另有一种,对比效果如下,Blinn-Phong计算出来的高光区域更大,边缘更硬一些
![65ef007cc105107ae03f66444fc7a295.png](https://i-blog.csdnimg.cn/blog_migrate/f28c04ca33605c2367a2d81e6ae58fdc.png)
自发光(emissive)
自发光描述了物体本身会向观察方向发射什么样的光,如果没有全局关照技术,这些自发光并不会真正照亮周围物体,而只是本身看起来更亮了
环境光(ambient)
描述其他所有的间接光照
三.Unity应用标准光照模型(非真实)
unity内置渲染流程中提供了两个地方进行自定义计算,即顶点着色器,片元着色器
顶点:逐定点计算,模型有n个顶点则计算n次
片元:逐像素计算,整个模型画到屏幕上时如果占了m个像素,则计算m次,一般情况m远大于n
顶点着色器中的数据,会经过插值得到到偏远着色器中使用的数据,如两个顶点颜色分别为白色(1,1,1)和黑色(0,0,0),到了片元着色器里边对应的一系列像素颜色是(1,1,1)(0.9,0.9,0.9)(0.8,0,0.8,)...(0,0,0)。着色计算即可以在定点中着色阶段进行,也可以在片元着色阶段进行,在前者进行更快但是可能由于插值而出现明显边际,在后者计算量大但是效果最好。
这里只展示主要在片元着色器中进行计算
环境光和自发光
环境光:Unityshader中可以直接使用系统定义的“UNITY_LIGHTMODEL_AMBIENT”获得环境光
自发光:大多数物体并没有自发光,如许添加,只要在最后的运算结果加上自定义的自发光颜色即可
漫反射
漫反射效果:
![8ada014548b8fbfadb1b8a20de695f52.png](https://i-blog.csdnimg.cn/blog_migrate/37e518bbf1d2e98ad5b0de70c8d793af.png)
UnityShader逐像素计算面反射代码段
v2f vert(a2v v){
v2f o;
o.pos=UnityObjectToClipPos(v.vertex);
//保存世界空间的法线
o.WorldNormal=UnityObjectToWorldNormal(v.normal);
return o;
}
fixed4 frag(v2f i):SV_TARGET{
//环境光
fixed3 ambient=UNITY_LIGHTMODEL_AMBIENT.xyz;
//光源方向
fixed3 WorldSpaceLightDir=normalize(_WorldSpaceLightPos0.xyz);
//diffuse=C(light) * m(diffuse) * max(0,I.n)
fixed3 diffuse=_LightColor0.xyz * _Diffuse.xyz *saturate(dot(WorldSpaceLightDir,i.WorldNormal))
return fixed4(diffuse,1);
}
UnityASE编辑器节点
![7ff73b47fc6693acbc82db0f90f4b563.png](https://i-blog.csdnimg.cn/blog_migrate/4146bfd19387480392f4c54c8a27f298.jpeg)
漫反射的半兰伯特模型
shader修改只要修改最后部分
//兰伯特diffuse=C(light) * m(diffuse) * max(0,I.n)
//fixed3 diffuse=_LightColor0.xyz * _Diffuse.xyz * saturate(dot(WorldSpaceLightDir,i.WorldNormal));
//半兰伯特diffuse=C(light) * m(diffuse) * (,0.5*I.n+0.5)
fixed3 diffuse=_LightColor0.xyz * _Diffuse.xyz * (0.5h* dot(WorldSpaceLightDir,i.WorldNormal)+0.5h);
ASE编辑器也只需按公式做修改即可
![c8f280905e790aa04c817a459d72732b.png](https://i-blog.csdnimg.cn/blog_migrate/a2dc827c973d1127601b243f5f7b01d1.png)
高光反射
Unity效果(Phong模型):
![a37ddd825bbb40d83bb28b9355a9c53c.png](https://i-blog.csdnimg.cn/blog_migrate/6386db698976dae5344864302dda7bd0.png)
Unityshader代码片段:
v2f vert(a2v i){
v2f o;
o.pos=UnityObjectToClipPos(i.vertex);
//保存世界法线
o.wNormal=UnityObjectToWorldNormal(i.normal);
//保存世界位置
o.wPos=mul((float3x3)unity_ObjectToWorld,i.vertex);
return o;
}
fixed4 frag(v2f i):SV_TARGET{
fixed4 finialColor;
finialColor.w=1;
//光源方向
fixed3 wLightDir=normalize(_WorldSpaceLightPos0.xyz);
//环境光
fixed3 GI=UNITY_LIGHTMODEL_AMBIENT.xyz;
//漫反射
fixed3 diffuse=_LightColor0.xyz*_Diffuse.xyz*saturate(dot(i.wNormal,wLightDir));
//反射方向
fixed3 refDic=normalize(reflect(-wLightDir,i.wNormal));
//观察方向
fixed3 vDic=normalize(_WorldSpaceCameraPos.xyz-i.wPos);
//高光=C(light) * m(Specular) * max(0,r.v)^m(gloss)
fixed3 specular=_LightColor0.xyz*_Specular.xyz*pow(saturate(dot(refDic,vDic)),_Gloss);
finialColor.xyz=diffuse+GI+specular;
return finialColor;
}
UnityASE编辑器节点
![f63b293896ee017328ceca9ad1bfaca6.png](https://i-blog.csdnimg.cn/blog_migrate/491c4c475dcc138ef6b4e543ebc9ec12.jpeg)
优化的高光计算模型(Blinn-Phong模型)
![de44a3111a2f51b32ec35c98e0650414.png](https://i-blog.csdnimg.cn/blog_migrate/3a8991e730c43f1281ed46f423eaefc8.png)
代码修改片段:
fixed3 vDic=normalize(UnityWorldSpaceViewDir(i.wPos));
fixed3 hlfVerc=normalize(wLightDir+vDic);
fixed3 specular=_LightColor0.xyz*_Specular.xyz*pow(saturate(dot(i.wNormal,hlfVerc)),_Gloss);
ASE编辑器修改,代替原点乘部分即可
![4cb74859e79407a808cf8a883bab4f29.png](https://i-blog.csdnimg.cn/blog_migrate/31f190fc06bba9c8e414ff526a9ef6d7.png)
四:总结
- 在真实世界中,光接触物体,会发生散射和吸收,散射是一个整体概念,它细分才包括反射和折射现象
- 我们所说的“最终颜色=漫发射+高光(+环境光+自发光)”是经验模型,并不能完全反映真实情况,高光反射只反应了真实交互中镜面反射部分,漫反射反应了光被折射被吸收后出射的光量
- 计算漫反射的Lambert模型(公式),和后来的加强显示效果的Hlf-Lambert模型(公式),是根据经验模拟的结果,只是简单易用以及特殊的显示效果需求
- 计算高光的Phong模型(公式),以及改良的Blinn-Phong模型(公式),也是根据经验模拟的结果,两者不存在谁对谁错,现在大多数非PBR光照使用的Blinn-Phong模型
- Unity中模拟光照可在顶点着色器中计算(计算快,可能产生棱边),以及片元着色器中(计算稍慢,效果细腻)
- ASE中生成的代码默认大多会在片元着色器中计算,如要求在顶点着色器中计算,需要使用“Vertex To Fragment”节点来约束
参考文档:
《UnityShader入门精要》