概述
当光照射到物体表面时,一部分被物体表面吸收,另一部分被反射,对于透明物体而言,还有一部分光穿过透明体,产生透射光。被物体吸收的光能转化为热量,只有反射光和透射光能够进入眼睛,产生视觉效果。通过反射和透射产生的光波(光具有波粒二相性)决定了物体呈现的亮度和颜色,即反射和投射光的强度决定了物体表面的亮度,而它们含有的不同波长光的比例决定了物体表面的色彩。
所以,物体表面光照颜色由入射光、物体材质,以及材质和光的交互规律共同决定。
光与物体最基本的交互方式就是反射,遵循反射定律:反射光与入射光位于表面法向两侧,对理想反射面(如镜面),入射角等于反射角,观察者只能在表面法向的反射方向一侧才能看到反射光。
光源
环境光(Ambient Light):从物体表面所产生的反射光的统一照明,称为环境光或背景光(计算机图形学第二版 389 页)。例如房间里面并没有受到灯光或者太阳光的直接照射,而是由墙壁、天花板、地板及室内各物体之间光的多次反射进行自然照明。通常我们认为理想的环境光具有如下特性:没有空间或方向性;在所有方向上和所有物体表面上投射的环境光强度是统一的恒定值。
由于环境光给予物体各个点的光照强度相同,且没有方向之分,所以在只有环境光的情况下,同一物体各点的明暗程度均一样,因此,只有环境光是不能产生具有真实感的图形效果。
环境光是对光照现象的最简单抽象,局限性很大。它仅能描述光线在空间中无方向并均匀散布时的状态。真实的情况是:光线通常都有方向。点光源是发光体的最简单的模型,光线由光源出发向四周发散。还有一种是平行光,即光线都从同一个方向照射。通过模拟方向光和物体表面的交互模式,可以渲染出具有高真实感(明暗变化、镜面反射等)的三维场景。
漫反射与 Lambert 模型
粗糙的物体表面向各个方向等强度地反射光,这种等同地向各个方向散射的现象称为光的漫反射(diffuse reflection)。产生光的漫反射现象的物体表面称为理想漫反射体,也称为朗伯(Lambert)反射体。
Lambert 实现出来的效果,一旦入射光向量与材质表面的角度大于90度,那么得到的漫反射颜色就会全部变为黑色,没有任何明暗变化效果。
Lambert 光照模型公式:
最终颜色 = 直射光颜色 * 漫反射颜色 * max(0, dot(光源方向, 法线方向))
其中,直射光颜色,漫反射颜色,都是我们自定义的变量。
Half Lambert 模型
Half Lambert 是在 Lambert 模型的基础上,做了微调,也就是将光源方向与法线方向的点乘结果,从原来[-1, 1],映射为 [0, 1],这样原来背光面,也会有明暗效果。
Half Lambert 光照模型公式:
最终颜色 = 直射光颜色 * 漫反射颜色 * (dot(光源方向, 法线方向) * 0.5 + 0.5)
镜面反射与 Phong 模型
Lambert 模型较好地表现了粗糙表面上的光照现象,如石灰粉刷的墙壁、纸张等,但在用于诸如金属材质制成的物体时,则会显得呆板,表现不出光泽,主要原因是该模型没有考虑这些表面的镜面反射效果。一个光滑物体被光照射时,可以在某个方向上看到很强的反射光,这是因为在接近镜面反射角的一个区域内,反射了入射光的全部或绝大部分光强,该现象称为镜面反射。
Phone 模型,的原理很简单,想象一束光射向某个点,然后反射出去,我们的眼睛同样看向那个点,当我们的眼睛看向那个点的方向,与光线反射的方向,越接近时,进入我们眼睛的反射光则越多,也就是更亮。看下面的图:
很明显,当视野方向与光的反射方向夹角越小时,也就是说进入眼睛的光越多,所以那个点也就会越亮,这就是高光反射的原理。所以高光反射,实现起来也就很简单了,只要拿到视野方向,拿到直射光的反射方向,就可以求出最终的颜色值。
Phong 光照模型公式:
最终颜色 = 直射光颜色 * 反射光颜色 * pow(max(0, dot(反射光方向, 视野方向)), 光泽度(gloss)) + 漫反射颜色 + 环境光颜色
其中,光泽度用于控制高光区域的亮点大小,gloss 值越大,亮点越小。
Blinn-Phong 光照模型
Blinn-Phong 光照模型,又称为 Blinn-phong 反射模型(Blinn–Phong reflectionmodel)或者 phong 修正模型(modified Phong reflection model),是由 Jim Blinn于 1977 年在文章“Models of light reflection for computer synthesized pictures”中对传统 phong 光照模型基础上进行修改提出的。和传统 phong 光照模型相比,Blinn-phong 光照模型混合了 Lambert 的漫射部分和标准的高光,渲染效果有时比 Phong 高光更柔和、更平滑,此外它在速度上相当快,因此成为许多 CG 软件中的默认光照渲染方法。此外它也集成在了大多数图形芯片中,用以产生实时快速的渲染。在 OpenGL 和 Direct3D 渲染管线中,Blinn-Phong 就是默认的渲染模型。
Phone 模型有一些缺点,所以后来出现了改进的模型,Blinn-Phone,对 Phone 模型进行了微调。Phone 模型的高光强度,是由光线的反射方向与视野方向的夹角决定的。而 Blinn-Phone 的模型,只是把反射方向和视野方向换成,法线方向,和视野与光线方向的中间向量之间的夹角。看下面的图
Blinn-Phone 高光反射公式:
最终颜色 = 直射光颜色 * 反射光颜色 * pow(max(0, dot(法线方向, 视野与光线中间向量)), 光泽度(gloss)) + 漫反射颜色 + 环境光颜色
关于 Phone 和 Blinn-Phone 更详细的一篇文章,learnopengl-cn 上的,推荐大家阅读 点这里。
实现上面的四种光照模型
接下来的实现,都是在片元函数中实现。当然,也可以放到顶点函数中实现,只是放到片元函数中效果会更平滑一些,但是相比放在顶点函数中做计算,耗费的性能也会更多一点。
先看一下四种实现的效果对比
从正面看
从背面看
从下面看
Lambert:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58
| Shader "iMoeGirl/Lambert" {
Properties { _Diffuse("Diffuse Color", Color) = (1,1,1,1) }
SubShader{ Pass{ Tags { "LightMode" = "ForwardBase" }
CGPROGRAM #include "Lighting.cginc" #pragma vertex vert #pragma fragment frag
fixed4 _Diffuse;
struct a2v { float3 vertex : POSITION; float3 normal: NORMAL; };
struct v2f { float4 svPos: SV_POSITION; fixed3 normalizedWorldNormal : COLOR; };
v2f vert(a2v v) { v2f f;
f.svPos = UnityObjectToClipPos(v.vertex);
f.normalizedWorldNormal = normalize(UnityObjectToWorldNormal(v.normal));
return f; }
fixed4 frag(v2f f) : SV_TARGET { float3 normalizedLightDir = normalize(_WorldSpaceLightPos0.xyz);
fixed dotValue = max(0, dot(normalizedLightDir, f.normalizedWorldNormal));
fixed3 diffuse = _LightColor0.rgb * _Diffuse * dotValue;
return fixed4(diffuse, 1); }
ENDCG } }
Fallback "VertexLit" }
|
Half Lambert:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
| Shader "iMoeGirl/Half-Lambert" { Properties { _Diffuse("Diffuse Color", Color) = (1,1,1,1) }
SubShader{ Pass{ Tags { "LightMode" = "ForwardBase" }
CGPROGRAM #include "Lighting.cginc" #pragma vertex vert #pragma fragment frag
fixed4 _Diffuse;
struct a2v { float3 vertex : POSITION; float3 normal: NORMAL; };
struct v2f { float4 svPos: SV_POSITION; fixed3 normalizedWorldNormal : COLOR; };
v2f vert(a2v v) { v2f f;
f.svPos = UnityObjectToClipPos(v.vertex);
f.normalizedWorldNormal = normalize(UnityObjectToWorldNormal(v.normal));
return f; }
fixed4 frag(v2f f) : SV_TARGET { float3 normalizedLightDir = normalize(_WorldSpaceLightPos0.xyz);
fixed dotValue = dot(normalizedLightDir, f.normalizedWorldNormal) * 0.5 + 0.5;
fixed3 diffuse = _LightColor0.rgb * _Diffuse * dotValue;
return fixed4(diffuse, 1); }
ENDCG } }
Fallback "VertexLit" }
|
Phong:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74
| Shader "iMoeGirl/Phone" { Properties { _Diffuse("Diffuse Color", Color) = (1,1,1,1) _Specular("Specular Color", Color) = (1,1,1,1) _Gloss("Gloss", Range(10, 200)) = 20 }
SubShader{ Pass{ Tags { "LightMode" = "ForwardBase" }
CGPROGRAM #include "Lighting.cginc" #pragma vertex vert #pragma fragment frag
fixed4 _Diffuse; fixed4 _Specular; float _Gloss;
struct a2v { float3 vertex : POSITION; float3 normal: NORMAL; };
struct v2f { float4 svPos: SV_POSITION; fixed3 normalizedWorldNormal : COLOR; float3 worldPos: TEXCOORD0; };
v2f vert(a2v v) { v2f f;
f.svPos = UnityObjectToClipPos(v.vertex);
f.normalizedWorldNormal = normalize(UnityObjectToWorldNormal(v.normal));
return f; }
fixed4 frag(v2f f) : SV_TARGET { float3 normalizedLightDir = normalize(_WorldSpaceLightPos0.xyz); fixed dotValue = dot(normalizedLightDir, f.normalizedWorldNormal) * 0.5 + 0.5; fixed3 diffuse = _LightColor0.rgb * _Diffuse * dotValue;
fixed3 reflectDir = normalize(reflect(-normalizedLightDir, f.normalizedWorldNormal)); fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - f.worldPos); float specularValue = pow(max(dot(reflectDir, viewDir), 0), _Gloss); fixed3 specular = _LightColor0.rgb * _Specular * specularValue;
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.rgb;
fixed3 color = specular + diffuse + ambient;
return fixed4(color, 1); }
ENDCG } }
Fallback "VertexLit" }
|
Blinn-Phone:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74
| Shader "iMoeGirl/Blinn-Phone" { Properties { _Diffuse("Diffuse Color", Color) = (1,1,1,1) _Specular("Specular Color", Color) = (1,1,1,1) _Gloss("Gloss", Range(10, 200)) = 20 }
SubShader{ Pass{ Tags { "LightMode" = "ForwardBase" }
CGPROGRAM #include "Lighting.cginc" #pragma vertex vert #pragma fragment frag
fixed4 _Diffuse; fixed4 _Specular; float _Gloss;
struct a2v { float3 vertex : POSITION; float3 normal: NORMAL; };
struct v2f { float4 svPos: SV_POSITION; fixed3 normalizedWorldNormal : COLOR; float3 worldPos: TEXCOORD0; };
v2f vert(a2v v) { v2f f;
f.svPos = UnityObjectToClipPos(v.vertex);
f.normalizedWorldNormal = normalize(UnityObjectToWorldNormal(v.normal));
return f; }
fixed4 frag(v2f f) : SV_TARGET { float3 normalizedLightDir = normalize(_WorldSpaceLightPos0.xyz); fixed dotValue = dot(normalizedLightDir, f.normalizedWorldNormal) * 0.5 + 0.5; fixed3 diffuse = _LightColor0.rgb * _Diffuse * dotValue;
fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - f.worldPos); fixed3 halfDir = normalize(viewDir + normalizedLightDir);
float specularValue = pow(max(dot(f.normalizedWorldNormal, halfDir), 0), _Gloss); fixed3 specular = _LightColor0.rgb * _Specular * specularValue; fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.rgb;
fixed3 color = specular + diffuse + ambient;
return fixed4(color, 1);
}
ENDCG } }
Fallback "VertexLit" }
|
本文参考:
康玉之——《GPU 编程与 CG 语言之阳春白雪下里巴人》 第 9 章 经典光照模型(illumination model)
https://imoegirl.com/2020/03/19/unity-shader-basis-05/