本文总结几个常用光照模型的原理,并用Unity基于原理写出对应的Shader进行验证
原理参考资料: 现代计算机图形学入门-闫令琪 第7讲 (说明图片来源该课件截图)
什么是光照模型?
图形学中为了模拟现实生活中物体表面反射光而形成明暗变化,引入了光照计算公式来进行处理,所谓模型可以理解为对不同光照公式的应用方案
Lambert (兰伯特)光照模型
Lambert 光照模型主要模拟的是物体对光线漫反射形成的效果
如图,在漫反射现象中,光线照射到物体表面的某一个点的时候,反射光会被发散到四面八方,这里我们可以大胆假设所有反射方向反射出去的光都是一样的,也就是说无论我站在哪一个方向,只要我能看到该物体的这个点,我看到的结果都是一样的,这说明漫反射与观察方向无关
漫反射结果与哪些因素有关?
1.入射角度
如图,我们把整个紫色的方块看成一个单位面积,整个黄色的部分看成单位面积受照射到的光线,每个箭头都可成这光的强度
左图:当受光面正对光线入射方向(即:光线直射)时,单位表面受到百分百的光照强度,可想而知此时该单位表面相对较亮
中图: 当受光面倾斜一定角度时,单位表面接受不到所有的光照强度,说明相比左图的情况,此时单位表面没有之前那么亮了
右图: 经过左图与中图的观察,可以发现漫反射亮度受光线入射方向I与单位表面的法线n夹角影响
继续推导:
受夹角θ影响,也就是cosθ值的影响,(当两向量方向一样则夹角为0很亮,cos0=1,当为垂直时cos90=0无光)又因为向量I与向量n都是单位向量(I和n只表示方量),所以就是相当于受\(\vec{L}\cdot\vec{N}\)的结果影响(这就是兰伯特余弦定律),最后,如果\((\vec{L}\cdot\vec{N})\)<0,说明光是从物体内部照射过来的,这种情况我们都是以0处理,即max(0,\(\vec{L}\cdot\vec{N}\))
2.衰减公式
如图,我们观察一个点光源的传播方式是以自身为中间的一个不断放大的球体的方式进行的传播,根据能量守恒定律,以光源为中心半径为1的球体总能量与半径为r的球体总能量是一样的。那么我们假设半径为1的球壳单位面积受到的强度为I,则有\(4\pi 1^{2}I = 4\pi r^{2}x\),x表示半径为r的球壳上的单位面积受到的光照强度,那么很容易得出x=I/r2
(这里提出个人对衰减公式的看法,衰减公式没有固定格式,unity中平行光甚至没有衰减,关于点光源算用最简单的线性公式只要最终结果看着OK就行)
Lambert光照公式
\(L_{d} = k_{d}(I/r^{2})\max(0,\vec{N}\cdot \vec{L})\)
公式中kd是漫反射系数,所谓系数就是对最终的结果进行一个调控,比如在物理中,当物体表面被光照时,也会吸收一部门光,我们可以将kd当成物体表面吸收率,也可以当成最终的反射率,当然我们也可以当成入射光的颜色进行最终的叠加,甚至说细数不只一个都可以(比如我即要叠加入射光颜色,又要再乘以一个系数控制反射率)都是可以的,这里用kd统一表示漫反射系数
半兰伯特光照模型
\(L_{d} = k_{d}(I/r^{2})((\vec{N}\cdot \vec{L})0.5+0.5)\)
与兰伯特模型相比,把法线方向 · 光源方向的值从原来的 [-1,1],限制到了 [0,1] 范围内,原来背光面值都映射到了0,现在也会有明暗效果了。从公式层面看,半兰伯特模型会比兰伯特模型更亮
UntiyShader验证(lambert模型)
Shader "Custom/Half-LambertLight" { Properties { _MainTex("MainTex",2D) = "white"{} _Intensity("Intensity",FLOAT) = 1 } SubShader { LOD 100 Pass { Name "LambertLight" Tags{"LightMode" = "ForwardAdd"} CGPROGRAM #pragma vertex vert #pragma fragment frag uniform float4 _LightColor0; struct a2v { float4 model_vertex : POSITION; float3 model_normal : NORMAL; float2 uv : TEXCOORD0; }; struct v2f { float4 clip_pos : SV_POSITION; float2 uv : TEXCOORD0; float3 world_pos : TEXCOORD1; float3 world_normal : TEXCOORD2; }; sampler2D _MainTex; float _Intensity; v2f vert(a2v v) { v2f o; o.uv = v.uv; o.clip_pos = UnityObjectToClipPos(v.model_vertex); o.world_normal = mul((float3x3)unity_ObjectToWorld, v.model_normal); //UnityObjectToWorldNormal(v.model_normal); o.world_pos = mul(unity_ObjectToWorld, v.model_vertex).xyz; return o; } fixed4 frag(v2f i) : SV_Target { //顶点法线(世界空间) fixed3 world_normal = normalize(i.world_normal); //光入射方向(世界空间) fixed3 world_light = _WorldSpaceLightPos0.xyz - i.world_pos.xyz; fixed3 world_light_dir = normalize(world_light); //距离光源的位置 float world_light_distance = length(world_light); //衰減系数 fixed atten = _Intensity /pow(world_light_distance, 2); //光照公式(Half-Lambert) fixed3 diffuseColor = atten * _LightColor0.rgb * (dot(world_normal, world_light_dir)*0.5+0.5); //贴图颜色 fixed4 texture_color = tex2D(_MainTex, i.uv); //叠加漫反射颜色 fixed3 finalColor = texture_color.rgb * diffuseColor; return fixed4(finalColor, 1.0); } ENDCG } } }
效果如下(将点光源从远处逐渐拉近):
UntiyShader验证(half-lambert模型)
只要修改如上的光照公式那一行为
//光照公式 fixed3 diffuseColor = atten * _LightColor0.rgb * (dot(world_normal, world_light_dir)*0.5 + 0.5);
效果如下(将点光源从远处逐渐拉近):
UntiyShader对比(相同点光源距离的情况lambert模型与half-lambert模型)
文章评论