球谐函数在光照估计中的应用
参考文献:https://blog.csdn.net/tinyzhao/article/details/62419220
光照估计就是从图片中获取光照信息,从而降低光照对纹理的影响。传统的光照估计依赖于光源方向和法线方向的估计,而光源方向和法线方向都是一种“估计”,本身就是不够精确的。使用不够精确的参数去估计目标值,就需要使用到一些高级的估计算法,这些算法一般都依赖于对象的统计特征。这样的光照模型过于复杂,其中的每一个过程都会影响最终的结果。
在2001年,Basri和Jacobs证明了曲面上的像素值可以使用9维的球谐基函数进行线性表示,这种光照模型只需要估计球谐基函数前面的权值,不需要光源的方向,大大简化了光照的估计。点p处的像素值等于p处的albedo乘以球谐基函数的线性组合,用数学公式表示一下就是:
ρ
(
p
)
ρ_(p)
ρ(p)表示p处的albedo,
l
j
l_j
lj表示球谐基函数的系数,
h
j
h_j
hj表示9个球谐基函数。使用
n
x
n_x
nx,
n
y
n_y
ny,
n
z
n_z
nz表示法线方向,这9个基分别为:
从式子中可以看到球谐函数基还是依赖于法线的方向的。Albedo的原意是反照率,我们可以看成是去除光照和阴影以后的纹理,在游戏图形工程上,更接近于基色的概念。
光照估计
对于一个有n个像素值的图片,n个像素对应着n个线性方程组,光照的估计就变成了n个方程求解n+9个未知数的问题,n+9表示n个albedo值和9个光照系数。一般的方法是使用最小二乘法进行估计。
-
初始化albedo估计
albedo的初始值 ρ 0 ρ^0 ρ0, ρ 0 ρ^0 ρ0可以使用图片像素平均值来估计。 -
初始化光照参数
有了 ρ 0 ρ^0 ρ0,就可以带入到 I = ρ 0 H L I=ρ^0HL I=ρ0HL中,使用最小二乘法获得初始的光照系数 L 0 L^0 L0。 -
更新albedo
使用上一步中得到的L值,可以根据式子 I = ρ 0 H L I=ρ^0HL I=ρ0HL直接解得新的ρ。 -
更新光照系数
再次使用最小二乘法估计光照系数L。重复估计albedo和L,直到满足要求,一般5次就足够了。
def get_normal(vertices, triangles):
''' calculate normal direction in each vertex
Args:
vertices: [nver, 3]
triangles: [ntri, 3]
Returns:
normal: [nver, 3]
'''
pt0 = vertices[triangles[:, 0], :] # [ntri, 3]
pt1 = vertices[triangles[:, 1], :] # [ntri, 3]
pt2 = vertices[triangles[:, 2], :] # [ntri, 3]
tri_normal = np.cross(pt0 - pt1, pt0 - pt2) # [ntri, 3]. normal of each triangle Vector的叉乘,可算出法线
normal = np.zeros_like(vertices) # [nver, 3]
#这里是将每个面的面法线都加到这个面的每一个顶点上
for i in range(triangles.shape[0]):
normal[triangles[i, 0], :] = normal[triangles[i, 0], :] + tri_normal[i, :]
normal[triangles[i, 1], :] = normal[triangles[i, 1], :] + tri_normal[i, :]
normal[triangles[i, 2], :] = normal[triangles[i, 2], :] + tri_normal[i, :]
# normalize to unit length
mag = np.sum(normal**2, 1) # [nver]
zero_ind = (mag == 0)
mag[zero_ind] = 1 #如果有法线为(0,0,0)的 将其变成1
normal[zero_ind, 0] = np.ones((np.sum(zero_ind)))
normal = normal/np.sqrt(mag[:,np.newaxis])
return normal
def add_light_sh(vertices, triangles, colors, sh_coeff):
'''
In 3d face, usually assume:
1. The surface of face is Lambertian(reflect only the low frequencies of lighting)
2. Lighting can be an arbitrary combination of point sources
--> can be expressed in terms of spherical harmonics(omit the lighting coefficients)
I = albedo * (sh(n) x sh_coeff)
albedo: n x 1
sh_coeff: 9 x 1
Y(n) = (1, n_x, n_y, n_z, n_xn_y, n_xn_z, n_yn_z, n_x^2 - n_y^2, 3n_z^2 - 1)': n x 9
# Y(n) = (1, n_x, n_y, n_z)': n x 4
Args:
vertices: [nver, 3]
triangles: [ntri, 3]
colors: [nver, 3] albedo 可以看到这是在顶点上着色啊
sh_coeff: [9, 1] spherical harmonics coefficients
Returns:
lit_colors: [nver, 3]
'''
assert vertices.shape[0] == colors.shape[0]
nver = vertices.shape[0]
normal = get_normal(vertices, triangles) # [nver, 3] get_normal -> calculate normal direction in each vertex
sh = np.array((np.ones(nver), n[:,0], n[:,1], n[:,2], n[:,0]*n[:,1], n[:,0]*n[:,2], n[:,1]*n[:,2], n[:,0]**2 - n[:,1]**2, 3*(n[:,2]**2) - 1)) # [nver, 9]
ref = sh.dot(sh_coeff) #[nver, 1]
lit_colors = colors*ref
return lit_colors
flat、Gouraud、Phong Shading的差別 (Comparison flat, Gouraud, Phong shading)
参考文献:https://cg2010studio.com/2011/11/01/flat%E3%80%81gouraud%E3%80%81phong-shading%E7%9A%84%E5%B7%AE%E5%88%A5-comparison-flat-gouraud-phong-shading/
现今多边形的着色方法基本的有这三种:flat、Gouraud、Phong Shading,它们之间有何差別呢?喜欢玩游戏的人一定要知道Gouraud Shading,这是PC最常使用的着色法,因为效能好、效果还不錯。而近年來,随着GPU快速的发展,Phong Shading逐渐使用在更真实的着色上。
从一张图可以看出他们的各自的特色:(a: Flat→b: Gouraud→c: Phong)
时间从古到今:Flat→Gouraud→Phong Shading。
从简单到复杂:Flat→Gouraud→Phong Shading。
观察三者所呈现的效果,可以归纳出以下结论:
flat shading
: constant surface shading
Gouraud shading
: color interpolation shading
Phong shading
: vertex normal interpolation shading
用简单的中文来解释原理:
flat shading
:三角形的顶点沒有法向量,三角形整个面才有法向量,打光时整个三角形只呈现一种颜色。Gouraud shading
:三角形的顶点都有各自的法向量,打光时三个顶点都有各自的颜色,接着做双线性内插(bilinear interpolation)来求得颜色,使整个三角形有逐渐的颜色变化。Phong shading
:三角形的顶点都有各自的法向量,先对三角形整个面作法向量的双线性内插,接着打光来求整个三角形的颜色。