使用梯度来估计法线



写在前面

  一般来说,法线贴图不是由美术直接制作的。法线贴图的制作通常有两个途径:
– 由灰度图、高度图进行生成,通过这种途径生成的法线会丢失许多细节。
– 进行高精度建模,然后把相应的表面凹凸信息写入法线贴图或者视差贴图,再把高模减面成适合游戏使用的低精度模型,然后将生成的贴图应用在模型上,这种方式可以拥有各种细节。


  这篇文章记录的是我实现地形算法的副产品,其中包括应用 sobel 算子估计梯度生成法线的应用与推导针对不同高度和规模生成法线贴图的参数详解以及几何着色器展示地形法线的方法。重点在于理论推导,希望可以起到抛砖引玉的作用。

sobel 算子计算梯度

  首先,如果学习过图像相关课程的话,肯定会接触一个非常重要的算子——sobel 算子。那么,什么是 sobel 算子?
  Sobel 算子是整像素图像边缘检测中最重要的算子之一,在机器学习、数字媒体、计算机视觉等信息科技领域起着举足轻重的作用。在技术上,它是一个离散的一阶差分算子,用来计算图像亮度函数的一阶梯度之近似值。在图像的任何一点使用此算子,将会产生该点对应的梯度矢量或是其法矢量,来自百度百科
  sobel 算子包含两组3×3的矩阵,分别为横向及纵向,将之与图像作平面卷积,即可分别得出横向及纵向的亮度差分近似值。如果以A代表原始图像,Gx及Gy分别代表经横向及纵向边缘检测的图像,其公式如下:

  得到的G_xG_y为两个数字,表示平面坐标上的 x,y 值。所以图像的每一个像素的横向及纵向梯度近似值可用以下的公式结合,来计算梯度的大小。G=\sqrt{G_x^2+G_y^2}  从上面的简介中,就可以看出,sobel 算子具备直接计算图像近似梯度值的能力。一旦获取了梯度值,就可以由梯度计算法线——在 等值线(面) 中,任一点法线的方向和梯度的方向相同。
  也就是说,对于曲线z=f(x,y)=c,若 f_xf_y 不同时为 0,则任意点 P_0(x_0,y_0) 处有n={{(f_x(x_0,y_0),f_y(x_0,y_0))}\over{\sqrt{f_x^2(x_0,y_0) + f_y^2(x_0,y_0)}}}={\nabla f(x_0,y_0)\over |\nabla f(x_0,y_0)|}  推广到三维中也是如此,通俗的讲其实描述了一个定理,二维中的直线和三维中的平面的法向量是一个常数。

拟合平面

  地形从来都是凹凸不平的,很大可能高度图的相邻点并不在同一个平面内,所以就需要假定对于适用于 sobel 算子的一个中心点加八个边缘点处于一个平面上,并求出所有点的误差最小的平面及其法向量,这个法向量就作为地形的法向量使用。注意,由于 sobel 本身是一阶算子,它刨除了高阶特性,这是生成法线的误差来源。个人估计专业的商业软件会配合使用某些高阶的算子来生成法线,但这个想法无从考证。平面拟合概念图如下:


平面拟合

  而通常的地形高度图的定义,就是对于高度图上某一点有 z=f(x,y)=c。原公式定义 x,y,z 单位方向都是 1,即每个像素的单位距离为 1,且最大高度在 0 到 1 之间插值。而地形高度图根据实际需要,最大高度不可能限制在某个常数。这么理解,一张 512 x 512 的高度图,不仅有可能用来生成高耸入云的山峰,也可能用来生成平坦的地形,最大高度往往并不需要保持常数。所以需要对生成法向量的 z 分量进行调整。

梯度、正切平面和法线

  对于由点 (x_0,y_0,z_0) 和法向量 {n}=(a,b,c) 定义的平面,有a(x-x_0)+b(y-y_0)+c(z-z_0)=0

事实上,对于等值曲线来说,在点 (x_0,y_0) 处的梯度向量 \nabla f(x_0,y_0)f(x,y)=k 正交。
同理,对于等值面来说,在点 (x_0,y_0,z_0) 处的梯度向量 \nabla f(x_0,y_0,z_0)f(x,y,z)=k 正交

  所以,在已知梯度的情况下,我们可以设正切平面也就是高度场的拟合平面的方程式为:f_x(x_0,y_0,z_0)(x-x_0)+f_y(x_0,y_0,z_0)(y-y_0)+f_z(x_0,y_0,z_0)(z-z_0)=0  上面两个公式来看,结果已经呼之欲出了——在等值线(面)中,梯度向量的方向和法线向量的方向相同


  首先设 z=f(x,y),这是一个空间曲面方程,但并不直接满足等值面的定义,我们需要找到在这个曲面中满足 z_0=f(x_0,+y_0) 的点 (x_0,y_0,z_0) 的切平面,而切平面的法向量就是该点的法向量。
  为了向上述公式靠近,可以调整表达式为:\displaystyle f(x,y)-z=0  那么,如果我们定义一个更高阶的表达式F(x,y,z)=f(x,y)-z  就可以看到,z=f(x,y)F(x,y,z)=0 的等值面。所以,下一步,我们需要找到 F 的梯度向量。由偏导数和梯度的定义,可以得到:\nabla F=(F_x,F_y,F_z)=(f_x,f_y,-1)  其中:F_x={\partial\over\partial x}(f(x,y)-z) =f_x \displaystyle F_y={\partial\over\partial y}(f(x,y)-z) =f_y \displaystyle F_z={\partial\over\partial z}(f(x,y)-z) =-1  所以,可以得到正切平面:\displaystyle f_x(x_0,y_0)(x-x_0)+f_y(x_0,y_0)(y-y_0)-(z-z_0)=0  处理一下 z,可以得到:\displaystyle z=f(x_0,y_0)+f_x(x_0,y_0)(x-x_0)+f_y(x_0,y_0)(y-y_0)  根据三维平面定义,所以可以得到 \nabla FF(x,y,z)=0(x_0,y_0,z_0)处的切平面,也就是 z=f(x,y) 的切平面。
  所以有:n={{(f_x(x_0,y_0),f_y(x_0,y_0))}\over{\sqrt{f_x^2(x_0,y_0) + f_y^2(x_0,y_0)}}}={\nabla f(x_0,y_0)\over |\nabla f(x_0,y_0)|}


转载请带上本文永久固定链接:https://www.selfgleam.com/normal-from-gradient.html

About the Author

发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注