osg 使用整理 (8):基础光照和法线贴图

发布时间 2023-09-23 21:38:25作者: 王小于的啦

# osg 使用整理 (8):基础光照和法线贴图

## 1 冯氏光照模型(Phong Lighting Model)

​ 冯氏光照模型只考虑直接光照,将进入摄像机的光分为4个部分:

​ (a)自发光表示当给定一个方向时,一个表面本身会向该方向发射多少辐射量

​ (b)镜面高光表示物体表面镜面反射的辐射量,模拟有光照的亮点

​ (c)漫反射光照表示物体表面漫反射的辐射量

​ (d)环境光给物体表面一个辐射常量

 

1.1 环境光照

环境光照考虑了光在其他表面上的反射造成的间接光照,它是一种简化的全局光照模型。添加环境光照后,即使场景中没有直接光源,也能看到物体表面微弱光照。

void main()
{
  float ambientStrength = 0.1;
  vec3 ambient = ambientStrength * lightColor;

  vec3 result = ambient * objectColor;
  FragColor = vec4(result, 1.0);
}

1.3 漫反射光照

漫反射数学模型如上图所示,n表示物体表面法线向量,s表示物体表面和光照连线向量。进入物体表面的光照取决于s和n的相对关系,当s和n重合时,这个方向上的辐射度最大,反之相互垂直时最小。用数学公式可以表示为:

```
float diff = max(dot(norm, lightDir), 0.0);
vec3 diffuse = diff * lightColor;
```

​ 注意不等比缩放会改变表面法线方向,需要使用法线矩阵生成正确的法线向量。

 

1.3 镜面高光光照

 

镜面高光数学模型如上图所示,n表示物体表面法线向量,s表示物体表面和光照连线向量,r代表光线反射方向,v代表摄像机和物体表面连线向量。反射向量可以通过法向量求得,当反射向量r和观察方向n的相对角度越小,镜面高光的辐射度越大。

```
float spec = pow(max(dot(viewDir, reflectDir), 0.0), 32);
vec3 specular = specularStrength * spec * lightColor;
```

### 1.4 Blinn-Phong光照模型

​ Phong光照模型的镜面高光计算考虑了视线和反射光线向量的夹角,导致当法线和光源夹角大于90度时,镜面高光为0。如下图所示,在镜面高光区域的边缘出现了明显的断层。

 

1977年,Blinn引入了半程向量概念,即光线与视线方向夹角一般方向上的一个单位向量,当半程向量和法线向量越接近时,镜面高光辐射度越大。

vec3 lightDir   = normalize(lightPos - FragPos);
vec3 viewDir   = normalize(viewPos - FragPos);
vec3 halfwayDir = normalize(lightDir + viewDir);

float spec = pow(max(dot(normal, halfwayDir), 0.0), shininess);
vec3 specular = lightColor * spec;

2 法线贴图(Normal Mapping)

如上面所述,影响模型视觉效果的模型固有属性除了顶点位置、颜色,还有很重要的表面法线向量。可以通过改变模型表面法线达到一种虚拟的凹凸不平视角效果,极大提升模型细节而不用改变顶点位置。法线纹理存储了模型表面的法线向量。取值范围为[-1,1],主要分为模型空间的法线纹理和切线空间的法线纹理,如下图所示。可以发现模型空间下的法线纹理五颜六色的,对应到模型坐标系下不同数值的法线向量,而切线空间下的法线基本都是浅蓝色(0.5,0.5,1.0),对应到切线坐标系的数值为(0,0,1)。

 

2.1 切线空间

切线空间定义为原点在顶点上,z轴方向为顶点法线方向,x轴为顶点切线方向,y轴为切线和法线叉乘得到称为副法线。因为切线空间的法线纹理大部分与顶点法线相同,改变的部分为每个点的法线扰动方向。美术人员更喜欢切线空间的法线纹理,分析他们的优劣如下

模型空间下法线纹理优点

(a)实现简单,更加直观

(b)在纹理坐标的缝合处和尖锐的边角附近,可见的突变较少。

切线空间下的法线纹理优点

(a)自由度很高。模型空间下的法线纹理记录的是绝对法线信息,仅可用于创建它的那个模型,而不能用在其他模型上,如立方体的相同6个平面就要生成6张法线纹理。

(b)可以进行uv动画。比如可以移动一个纹理的uv坐标来实现一个凹凸移动的效果,在水和火山熔岩类型的物体上经常用到。

(c)可压缩。切线空间的法线纹理只需要存储XY方向的值。

实际使用切线空间的法线纹理时,有两种选择:

一是在切线空间下计算光照,意味着要把光照方向和视角方向变换到切线空间,基本思路是首先在顶点着色器中求得模型空间到切线空间的变换矩阵,然后将视角向量和光线向量变换到切线空间,最后在片段着色器中通过纹理采样得到切线空间下的法线并计算光照。

void main()
{
  [...]
  vec3 T = normalize(vec3(model * vec4(tangent,   0.0)));
  vec3 B = normalize(vec3(model * vec4(bitangent, 0.0)));
  vec3 N = normalize(vec3(model * vec4(normal,   0.0)));
  mat3 TBN = mat3(T, B, N)
}
注:对方向矢量的坐标变换不需要考虑原点平移,因此3X3的矩阵就可以表示向量的坐标变换
void main()
{    
  [...]
  mat3 TBN = transpose(mat3(T, B, N));
  vs_out.TangentLightPos = TBN * lightPos;
  vs_out.TangentViewPos = TBN * viewPos;
  vs_out.TangentFragPos = TBN * vec3(model * vec4(position, 0.0));
}

二是在世界空间下进行光照计算,需要把采样得到的法线变换到世界空间下。

void main()
{
  [...]
  vec3 T = normalize(vec3(model * vec4(tangent,   0.0)));
  vec3 B = normalize(vec3(model * vec4(bitangent, 0.0)));
  vec3 N = normalize(vec3(model * vec4(normal,   0.0)));
  mat3 TBN = mat3(T, B, N)
}
void main()
{  
normal = texture(normalMap, fs_in.TexCoords).rgb;
normal = normalize(normal * 2.0 - 1.0);  
normal = normalize(fs_in.TBN * normal);
}

使用法线贴图可以使用更少的顶点表现出同样丰富的细节。