【技术美术百人计划】图形 2.4 传统经验光照模型详解_哔哩哔哩_bilibili
当光照射到物体表面时,物体对光会发生反射、透射、吸收、衍射、折射、和干涉,其中被物体吸收的部分转化为热,反射、透射的光进入人的视觉系统,使我们能看见物体。为模拟这一现象,我们建立一些数学模型来替代复杂的物理模型,这些模型就称为明暗效应模型或者光照明模型。下面将简要叙述光照模型研究的发展历程。
从算法理论基础而言,光照模型分为两类
基于物理理论的光照模型(PBR模型)
偏重于使用物理的度量和统计方法,效果非常真实,但是计算复杂,实现起来也较为困难
基于经验的光照模型
对光照的一种模拟,通过经验总结出简化的方法,简化了真实的光照模型,并且能够达到很不错的效果
现实世界的光照是极其复杂的,而且受到诸多因素的影响,有限的计算能力无法完全模拟。使用简化的光照模型对现实的情况进行近似,使得计算处理起来会更容易,并且令效果更符合需求。
局部光照模型的定义:局部光照模型只关心直接光照部分,即直接从光源发出并照射到物体表面并反射至摄像头的光线
局部光照模型的组成
漫反射
在光照模型的定义中,当光线从光源照射到模型表面时,光线均匀被反射到各个方向,这种现象就是漫反射,在漫反射的过程中,光线发生了吸收和散射,而因此改变颜色和方向
如何计算
漫反射光照符合Lambert定律(1.兰伯特光照模型),反射光强与法线和光源方向之间的夹角的余弦值成正比
高光反射(镜面反射)
定义
当光线到达物体表面并发生了反射,观察视线在反射光线的附近时,便能够观察到了高光反射
高光反射描述了光线与物体表面发生的反射(光强不变,方向改变)
高光反射的反射率是根据一种菲涅尔效应的物理现象决定
通常使用对应的反射贴图描述物体表面的反射率,并且使用光泽度(粗糙度,反光度)描述高光范围的大小。
如何计算?
环境光
定义
在局部光照模型中,由于没有考虑间接光照的影响,因此为了处理这种间接光照,为光照模型引入环境光
如何计算?
C_ambient = Albedo * Ambient_light(环境光亮)
通常使用漫反射的反照率来指示环境光照的反射光量
该模型假定场景中发生多次散射和反射,并在所有方向上均等的射向目标物体。
自发光
定义
物体自身发射的光线,通常作为单独的一项加入光照模型哦,一般使用一张发光贴图描述物体的自发光
局部光照模型组合效果
经典光照模型
Lambert模型
只计算漫反射
C_light:入射光的颜色,albedo:漫反射的材质,dot(normal,L):法线和入射方向的点乘
Phong模型
Blinn-Phong模型
Blinn-Phone模型不再依赖于反射向量,而是采用了所谓的半程向量(Halfway Vector),即光线与视线夹角一半方向上的一个单位向量。当半程向量与法线向量越接近时,镜面光分量就越大。
计算半角向量:float3 halfDir = normalize(worldLight + viewDir)
Phong模型和Blinn-Phong模型的区别:
计算更加简洁,半角向量比反射向量的计算更加简洁
当光源与视线都在物体表面之上时,半角向量与法线的角度永远不大于90度
着色模型
Gourand模型(顶点着色器)
以顶点为单位计算光量在通过插值得到每个像素点的光亮度。在表现物体光滑性上有比较好的表现,但值不再是线性变化的时候,比如镜面高光,由于线性插值导致内插值不可能大于顶点值。因此高光只能出现在顶点。由于使用的颜色插值,会导致镜面高光蔓延到周边
高光效果不好
Flat模型
平面着色模型,计算多边形的单个强度,每个三角形只有一个法线方向。以相同的光强度值显示多边形的所有点。通常适用于lowPoly风格的场景。
尤其是phong着色模型与phong光照模型
一. 着色模型(频率)Shading
着色模型(频率)就是如何运用(也可以不用)光照模型计算的结果显示由多边形表示的曲面。
着色模型(频率)和光照模型是不同的概念,着色模型(频率)考虑的是面,着色模型(频率)会使用到光照模型的计算结果,当然也有不使用光照模型的情况,例如:固定着色(constant shading)。而光照模型指的是模型某一点如何与光交互的,是为了计算某一点最终颜色的。
四种着色模型
1.固定着色(constant shading)
这是最简单的着色模型(频率),不考虑任何光照效果----没有环境光,散射光等,只是使用某种颜色将多边形绘制为实心的。因此,固定着色根本不考虑光照模型,只是根据多边形上的颜色值绘制它。
2.恒定着色(flat shading)
恒定着色指的是根据多边形上某个像素的光照情况对整个多边形着色。换句话说,假设多边形是由一种材质构的。
缺点:对于平面组成的物体,这种方法是可行的;但是对于由曲面组成的物体,使用多边形近似,这种方法导致物体看起来是由多边形组成的。
3.Gouraud 着色
Gouraud 着色是分别计算三角形三个顶点的光照情况,然后通过重心坐标插值的方式来决定三角形内每一点的颜色
缺点:对高光的插值不好,因为高光往往比较锐利,如果本该面上有高光,但是顶点处没有高光,那么插值后,这个面就没有高光。
优点:效果不错,计算量小
4.phong着色
phong着色只得是通过插值计算屏幕空间多边形每个点的法线,然后根据法线来执行光照计算。
注:在顶点着色器中实现的冯氏光照模型叫做Gouraud着色(Gouraud Shading),而不是冯氏着色(Phong Shading)
优点:它对每个像素执行光照计算,真实感比gouraud着色强些,提高了镜面反射效果。
缺点:计算量比较大
二.光照模型(illumination model)
当光照射到物体表面时,物体对光会发生反射、透射、吸收、衍射、折射、和干涉,其中被物体吸收的部分转化为热,反射、透射的光进入人的视觉系统,使我们能看见物体。为模拟这一现象,我们建立一些数学模型来替代复杂的物理模型,这些模型就称为明暗效应模型或者光照明模型。下面将简要叙述光照模型研究的发展历程。
光照模型
1.Lambert漫反射
是光源照射到物体表面后,向四面八方反射,产生的漫反射效果。这是一种理想的漫反射光照模型。
2.半Lambert漫反射
我们计算法线方向和光方向的点乘值时,得到的结果有可能是负数,而兰伯特光照模型对于该情况的处理是,dot值为负数,说明该点不会受到光的照射,所以对于该光源,该点无光,直接使用max(0,diffuse)来将不应该受光的位置全都置为黑色。虽然听起来很有道理的样子,然而这种并好看。
把Lambert的结果区间转化,从(-1,1)转化到(0,1)而原本光照强度为0的地方,就变成了0.5,原本为负数的地方,也能保证为大于0了,这样暗部也有渐变。
3.冯氏光照模型(Phong Lighting Model)
冯氏光照模型的主要结构由3个分量组成:环境(Ambient)、漫反射(Diffuse)和镜面(Specular)光照。
我们通过反射法向量周围光的方向来计算反射向量。然后我们计算反射向量和视线方向的角度差,如果夹角越小,那么镜面光的影响就会越大。它的作用效果就是,当我们去看光被物体所反射的那个方向的时候,我们会看到一个高光。
基础光照 - LearnOpenGL CN
4.Blinn-Phong光照模型(Blinn-Phong lighting)
Blinn-Phong模型与冯氏模型非常相似,但是它对镜面光模型的处理上有一些不同,让我们能够解决之前提到的问题。Blinn-Phong模型不再依赖于反射向量,而是采用了所谓的半程向量(Halfway Vector),即光线与视线夹角一半方向上的一个单位向量。当半程向量与法线向量越接近时,镜面光分量就越大。
高级光照 - LearnOpenGL CN
理论 - LearnOpenGL CN
判断一种PBR光照模型是否是基于物理的,必须满足以下三个条件(不用担心,我们很快就会了解它们的):
基于微平面(Microfacet)的表面模型。
能量守恒。
应用基于物理的BRDF。
微平面模型
所有的PBR技术都基于微平面理论。这项理论认为,达到微观尺度之后任何平面都可以用被称为微平面(Microfacets)的细小镜面来进行描绘。根据平面粗糙程度的不同,这些细小镜面的取向排列可以相当不一致:
产生的效果就是:一个平面越是粗糙,这个平面上的微平面的排列就越混乱。这些微小镜面这样无序取向排列的影响就是,当我们特指镜面光/镜面反射时,入射光线更趋向于向完全不同的方向发散(Scatter)开来,进而产生出分布范围更广泛的镜面反射。而与之相反的是,对于一个光滑的平面,光线大体上会更趋向于向同一个方向反射,造成更小更锐利的反射:
在微观尺度下,没有任何平面是完全光滑的。然而由于这些微平面已经微小到无法逐像素的继续对其进行区分,因此我们只有假设一个粗糙度(Roughness)参数,然后用统计学的方法来概略的估算微平面的粗糙程度。我们可以基于一个平面的粗糙度来计算出某个向量的方向与微平面平均取向方向一致的概率。这个向量便是位于光线向量和视线向量之间的中间向量(Halfway Vector)。
微平面的取向方向与中间向量的方向越是一致,镜面反射的效果就越是强烈越是锐利。然后再加上一个介于0到1之间的粗糙度参数,这样我们就能概略的估算微平面的取向情况了:
能量守恒
微平面近似法使用了这样一种形式的能量守恒(Energy Conservation):出射光线的能量永远不能超过入射光线的能量(发光面除外)。如图示我们可以看到,随着粗糙度的上升镜面反射区域的会增加,但是镜面反射的亮度却会下降。如果不管反射轮廓的大小而让每个像素的镜面反射强度(Specular Intensity)都一样的话,那么粗糙的平面就会放射出过多的能量,而这样就违背了能量守恒定律。这也就是为什么正如我们看到的一样,光滑平面的镜面反射更强烈而粗糙平面的反射更昏暗。
为了遵守能量守恒定律,我们需要对漫反射光和镜面反射光之间做出明确的区分。当一束光线碰撞到一个表面的时候,它就会分离成一个折射部分和一个反射部分。反射部分就是会直接反射开来而不会进入平面的那部分光线,这就是我们所说的镜面光照。而折射部分就是余下的会进入表面并被吸收的那部分光线,这也就是我们所说的漫反射光照。
这里还有一些细节需要处理,因为当光线接触到一个表面的时候折射光是不会立即就被吸收的。通过物理学我们可以得知,光线实际上可以被认为是一束没有耗尽就不停向前运动的能量,而光束是通过碰撞的方式来消耗能量。每一种材料都是由无数微小的粒子所组成,这些粒子都能如下图所示一样与光线发生碰撞。这些粒子在每次的碰撞中都可以吸收光线所携带的一部分或者是全部的能量而后转变成为热量。
一般来说,并非所有能量都会被全部吸收,而光线也会继续沿着(基本上)随机的方向发散,然后再和其他的粒子碰撞直至能量完全耗尽或者再次离开这个表面。而光线脱离物体表面后将会协同构成该表面的(漫反射)颜色。不过在基于物理的渲染之中我们进行了简化,假设对平面上的每一点所有的折射光都会被完全吸收而不会散开。而有一些被称为次表面散射(Subsurface Scattering)技术的着色器技术将这个问题考虑了进去,它们显著的提升了一些诸如皮肤,大理石或者蜡质这样材质的视觉效果,不过伴随而来的则是性能下降代价。
对于金属(Metallic)表面,当讨论到反射与折射的时候还有一个细节需要注意。金属表面对光的反应与非金属材料(也被称为介电质(Dielectrics)材料)表面相比是不同的。它们遵从的反射与折射原理是相同的,但是所有的折射光都会被直接吸收而不会散开,只留下反射光或者说镜面反射光。亦即是说,金属表面不会显示出漫反射颜色。由于金属与电介质之间存在这样明显的区别,因此它们两者在PBR渲染管线中被区别处理。
反射光与折射光之间的这个区别使我们得到了另一条关于能量守恒的经验结论:反射光与折射光它们二者之间是互斥的关系。无论何种光线,其被材质表面所反射的能量将无法再被材质吸收。因此,诸如折射光这样的余下的进入表面之中的能量正好就是我们计算完反射之后余下的能量。
我们按照能量守恒的关系,首先计算镜面反射部分,它的值等于入射光线被反射的能量所占的百分比。然后折射光部分就可以直接由镜面反射部分计算得出:
float kS = calculateSpecularComponent(...); // 反射/镜面 部分 float kD = 1.0 - ks; // 折射/漫反射 部分
这样我们就能在遵守能量守恒定律的前提下知道入射光线的反射部分与折射部分所占的总量了。按照这种方法折射/漫反射与反射/镜面反射所占的份额都不会超过1.0,如此就能保证它们的能量总和永远不会超过入射光线的能量。而这些都是我们在前面的光照教程中没有考虑的问题。
BRDF,或者说双向反射分布函数,它接受入射(光)方向ωi,出射(观察)方向ωo,平面法线nn以及一个用来表示微平面粗糙程度的参数a作为函数的输入参数。BRDF可以近似的求出每束光线对一个给定了材质属性的平面上最终反射出来的光线所作出的贡献程度。举例来说,如果一个平面拥有完全光滑的表面(比如镜面),那么对于所有的入射光线ωi(除了一束以外)而言BRDF函数都会返回0.0 ,只有一束与出射光线ωo拥有相同(被反射)角度的光线会得到1.0这个返回值。
BRDF基于我们之前所探讨过的微平面理论来近似的求得材质的反射与折射属性。对于一个BRDF,为了实现物理学上的可信度,它必须遵守能量守恒定律,也就是说反射光线的总和永远不能超过入射光线的总量。严格上来说,同样采用ωiωi和ωoωo作为输入参数的 Blinn-Phong光照模型也被认为是一个BRDF。然而由于Blinn-Phong模型并没有遵循能量守恒定律,因此它不被认为是基于物理的渲染。现在已经有很好几种BRDF都能近似的得出物体表面对于光的反应,但是几乎所有实时渲染管线使用的都是一种被称为Cook-Torrance BRDF模型。
作业
基于能量守恒,写一套完整光照,包含环境光照
学习资料庄懂的技术美术入门课(美术向)-直播录屏-第12课_哔哩哔哩_bilibili
代码
Shader "VS/dota2" {
Properties {
_MainTex ("RGB:基础颜色 A:透贴", 2D) = "white" {}
_MaskTex ("R:高光强度 G:边缘光强度 B:高光染色 A:高光次幂" , 2D) = "black" {}
_NormTex ("RGB:法线贴图 ", 2D) = "bump" {}
_MatelnessTex ("金属度遮罩 ", 2D) = "black" {}
_EmissionMask ("自发光遮罩 ", 2D) = "black" {}
_DiffWarpTex ("颜色Warp图", 2d) = "gray" {}
_FresWarpTex ("菲涅尔Warp图", 2d) = "gray" {}
_Cubemap ("RGB:环境贴图", cube) = "_Skybox" {}
_LightCol ("光颜色",color) = (1.0,1.0,1.0,1.0)
_SpecPow ("高光次幂",range(0.0,30.0)) = 5
_SpecInt ("高光强度",range(0.0,10.0)) = 5
_EvnCol ("环境光颜色",color) = (1.0,1.0,1.0,1.0)
_EvnSpecInt ("环境镜面反射强度",range(0.0,30.0)) = 5
[HDR] _RimCol ("轮廓光强度",color) = (1.0,1.0,1.0,1.0)
_EminInt ("自发光强度",range(0.0,10.0)) = 1.0
[HideInInspector]
_Cutoff ("Alpha cutoff",Range(0,1)) = 0.5
[HideInInspector]
_Color ("Main Color",Color) = (1.0,1.0,1.0,1.0)
}
SubShader {
Tags {
"RenderType"="Opaque"
}
Pass {
Name "FORWARD"
Tags {
"LightMode"="ForwardBase"
}
Cull off
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#pragma multi_compile_fwdbase_fullshadows
#pragma multi_compile_fog
#pragma target 3.0
// 追加投影相关包含文件
#include "AutoLight.cginc"
#include "Lighting.cginc"
//输入参数
uniform sampler2D _MainTex;
uniform sampler2D _MaskTex;
uniform sampler2D _NormTex;
uniform sampler2D _MatelnessTex;
uniform sampler2D _EmissionMask;
uniform sampler2D _DiffWarpTex;
uniform sampler2D _FresWarpTex;
uniform samplerCUBE _Cubemap;
uniform half3 _LightCol;
uniform half _SpecPow;
uniform half _SpecInt;
uniform half _EvnDiffInt;
uniform half4 _EvnCol;
uniform half _EvnSpecInt;
uniform half3 _RimCol;
uniform half _EminInt;
uniform half _Cutoff;
struct VertexInput {
float4 vertex : POSITION;
float2 uv : TEXCOORD;
float3 normal : NORMAL;
float4 tangent : TANGENT;
};
struct VertexOutput {
float4 pos : SV_POSITION;
float4 posWS : TEXCOORD1; // 世界空间顶点位置
float2 uv0 : TEXCOORD0; // UV0
float3 nDirWS : TEXCOORD2; // 世界空间法线方向
float3 tDirWS : TEXCOORD3; // 世界空间切线方向
float3 bDirWS : TEXCOORD4; // 世界空间副切线方向
LIGHTING_COORDS(5,6)
};
VertexOutput vert (VertexInput v) {
VertexOutput o = (VertexOutput)0;
o.pos = UnityObjectToClipPos( v.vertex );
o.uv0 = v.uv; // 传递UV
o.posWS = mul(unity_ObjectToWorld, v.vertex); // 顶点位置 OS>WS
o.nDirWS = UnityObjectToWorldNormal(v.normal); // 法线方向 OS>WS
o.tDirWS = normalize(mul(unity_ObjectToWorld, float4(v.tangent.xyz, 0.0)).xyz); // 切线方向 OS>WS
o.bDirWS = normalize(cross(o.nDirWS, o.tDirWS) * v.tangent.w); // 副切线方向
TRANSFER_VERTEX_TO_FRAGMENT(o)
return o;
}
half4 frag(VertexOutput i) : COLOR {
// 准备向量
half3 nDirTS = UnpackNormal(tex2D(_NormTex, i.uv0)).rgb;
half3x3 TBN = half3x3(i.tDirWS, i.bDirWS, i.nDirWS);
half3 nDirWS = normalize(mul(nDirTS, TBN));
half3 vDirWS = normalize(_WorldSpaceCameraPos.xyz - i.posWS.xyz);
half3 vrDirWS = reflect(-vDirWS, nDirWS);
half3 lDirWS = _WorldSpaceLightPos0.xyz;
half3 lrDirWS = reflect(-lDirWS, nDirWS);
// 准备点积结果
half ndotl = dot(nDirWS, lDirWS);
half vdotr = dot(vDirWS, lrDirWS);
half vdotn = dot(vDirWS, nDirWS);
// 采样纹理
half4 var_MainTex = tex2D(_MainTex, i.uv0);
half4 var_MaskTex = tex2D(_MaskTex, i.uv0);
half var_MatelnessTex = tex2D(_MatelnessTex, i.uv0).r;
half var_EmissionMask = tex2D(_EmissionMask, i.uv0).r;
half3 var_FresWarpTex = tex2D(_FresWarpTex, vdotn);
half3 var_Cubemap = texCUBElod(_Cubemap, float4(vrDirWS, lerp(8.0, 0.0, var_MaskTex.a))).rgb;
//提取信息
half3 baseCol = var_MainTex.rgb;
half opacity = var_MainTex.a;
half specInt = var_MaskTex.r;
half rimInt = var_MaskTex.g;
half specTint = var_MaskTex.b;
half specPow = var_MaskTex.a;
half matellic = var_MatelnessTex;
half emitInt = var_EmissionMask;
half3 envCube = var_Cubemap;
half shadow = LIGHT_ATTENUATION(i);
//光照模型
//漫反射
half3 diffCol = lerp(baseCol,half3(0.0,0.0,0.0),matellic);
half3 specCol = lerp(baseCol,half3(0.3,0.3,0.3),specTint) * specInt;
//菲涅尔
half3 fresnel = lerp(var_FresWarpTex,0.0,matellic);
half fresnelCol = fresnel.r;
half fresnelRim = fresnel.g;
half fresnelSpec = fresnel.b;
//光源漫反射
half halfambert = ndotl * 0.5 + 0.5;
half3 var_DiffWarpTex = tex2D(_DiffWarpTex,half2(halfambert,0.2));
half3 dirDiff = diffCol * var_DiffWarpTex * _LightCol;
//光源镜面反射
half phone = pow(max(0.0,vdotr),specPow * _SpecPow);
half spec = phone * max(0.0,ndotl);
spec = max(spec,fresnelSpec);
spec = spec * _SpecInt;
half3 dirSpec = specCol * spec * _LightCol;
//环境漫反射
half3 envDiff = diffCol * _EvnCol;
//环境镜面反射
half reflectInt = max(fresnelSpec,matellic) * specInt;
half3 envSpec = specCol * reflectInt * envCube * _EvnSpecInt;
//轮廓光
half3 rimLight = _RimCol * fresnelRim * rimInt * max(0.0,nDirWS.g);
//自发光
half3 emissions = diffCol * emitInt * _EminInt;
//混合
half3 finalRGB = (diffCol + specCol) * shadow + envDiff + envSpec + rimLight + emissions;
clip(opacity - _Cutoff);
return float4(finalRGB,1); //reflectInt;
}
ENDCG
}
}
FallBack "Legacy Shaders/Transparent/Cutout/VertexLit"
}