Signed Distance Field

项目链接:https://github.com/Candlend/SDFProject

Introduction

首先介绍一下SDF(Signed Distance Field),距离场代表我们可以获得场景中任意一点到最近表面的距离,而有向代表这个距离是有符号的,一般情况下得到的距离是负的时,我们认为这个采样点在物体内部。这是一种有别于我们平常接触的mesh的一种对于物体表面的表示方法。

渲染SDF的本质是渲染等值面。我们定义一个函数$f\left(p\right)$来评估p点与最近表面的距离,定义域为$R^3$,值域为$R$,实际上要做的就是渲染$f\left(p\right)=0$的曲面。

我们使用ray marching来渲染SDF,ray marching和ray casting类似都是从相机向每个像素发射射线,ray marching的特点在于,射线是从源头一点一点向前步进的,每次步进采样一个点,从距离场中获取该点离最近表面的距离,如果距离为0就可以获得射线与物体表面的交点。

利用SDF和ray marching的一些特性,我们可以很简单地实现很多一般实时渲染使用的rasterization难以实现的效果,比如软阴影,环境光遮蔽,融球效果等。

首先我们使用了openframeworks,用户界面,相机控制等非重点功能我们直接使用了of自带的接口。由于opengl本身是不支持ray marching的,我们通过对一个四边形使用着色器来达成对屏幕渲染的效果。

Ray Marching

我先计算出view frustum的四个角在视图空间中的向量,将它赋给四边形的四个点,然后在cpu中计算出将从视图空间转移到世界空间的矩阵,从而计算出view frustum的四个角在世界空间中的向量。

然后fragment shader就会插值出每个像素的射线方向,虽然也可以直接在fragment shader中根据相机参数计算射线方向,但由于vertex shader到fragment shader的插值是在硬件中进行的,前一种方法的效率要更高。然后就是在fragment shader中实现ray marching。

Sphere Tracing

Ray marching的步长不是固定的,我使用了sphere tracing技术。由于我们可以获得任意一点离最近表面的距离,换句话说,以该点为球心,以该距离为半径的球体内没有任何物体,因此可以直接让射线向前步进这个距离。如果距离极其接近0,则我们认为射线与物体表面相交。

1592380671214

Shading

Normal Calculation

在获得射线与物体表面的交点后,我们要计算该表面的法线方向才能进行下一步。由于本质是等值面,我们可以将这个点的梯度作为法线。我们可以使用中心差分(Central Difference)来计算梯度。有一种方法是分别获得三个维度正负方向上的距离值来做差分。梯度计算公式如下:

$$ \begin{align}\nabla f\left(p\right)&=\left(\frac{d}{dx}f\left(p\right),\frac{d}{dy}f\left(p\right),\ \frac{d}{dz}f\left(p\right)\right)^T\\\frac{d}{dx}f\left(p\right)&\approx\frac{f\left(p+\left\{h,\ 0,\ 0\right\}\right)-f\left(p-\left\{h,\ 0,\ 0\right\}\right)}{2h}\\\frac{d}{dy}f\left(p\right)&\approx\frac{f\left(p+\left\{0,\ h,0\right\}\right)-f\left(p-\left\{0,\ h,0\right\}\right)}{2h}\\\frac{d}{dz}f\left(p\right)&\approx\frac{f\left(p+\left\{0,\ 0,\ h\right\}\right)-f\left(p-\left\{0,\ 0,\ h\right\}\right)}{2h}\\n&=normalize\left(\nabla f\left(p\right)\right)\approx\ normalize\left(\left(  \begin{matrix}   f\left(p+\left\{h,\ 0,\ 0\right\}\right)-f\left(p-\left\{h,\ 0,\ 0\right\}\right) \\   f\left(p+\left\{0,\ h,0\right\}\right)-f\left(p-\left\{0,\ h,0\right\}\right) \\   f\left(p+\left\{0,\ 0,\ h\right\}\right)-f\left(p-\left\{0,\ 0,\ h\right\}\right)  \end{matrix} \right)\right) \end{align} $$

Improvement

但以上方法这样要计算六次距离。这里我们用了一种优化算法,只需要进行四次距离计算。

$$
\begin{align}
k_0=\left(1,\ -1,\ -1\right)^T\ k_1=\left(-1,\ -1,\ 1\right)^T\ k_2=\left(-1,1,1\right)^T\ k_3=\left(1,1,1\right) \\
\sum_{i}{k_i\ f\left(p+hk_i\right)}=\sum_{i}{k_i\ \left(f\left(p+hk_i\right)-f\left(p\right)\right)}\approx\sum_{i}{k_i\cdot h\nabla_{k_i}f\left(p\right)}\\ \approx\sum_{i}{k_i\cdot h\left(k_i\cdot\nabla f\left(p\right)\right)}=h\cdot\nabla f\left(p\right)\sum_{i}\ k_i^2=\left(4,4,4\right)^Th\cdot\ \nabla f\left(p\right)
\end{align}
$$

Phong Lighting Model

获得法线后,这里使用了简单的phong lighting model。为简化模型,目前仅支持单个平行光和多个点光源,且每个光源只包含一个颜色信息,即ambient diffuse和specular为同种颜色。

Shadow

hard Shadow

然后从射线与物体表面的交点沿光源方向发射一条shadow ray,如果有和物体相交,就代表该点在阴影中。但这么做只能实现hard shadow。为了使效果更真实,我们还实现了soft shadow。

Soft Shadow

这是一种近似方案,是基于SDF的特性实现的。尽管shadow ray没有与物体相交,但可以将shadow ray与物体很近的情况视作为该点处于半影区。在SDF中,我们可以很方便的获得射线上某一点与最近物体的距离,以及到达该点的射线长,通过这两者来评估该物体表面的暗度。

令$h$为射线上某一点与最近物体的距离,$t$为到达该点的射线长,设一个参数$k$来影响半影区的大小,$k$越大,$k\cdot\frac{h}{t}$变化越快,半影区越小。$\frac{h}{t}$越大,阴影越深,记录$k\cdot\frac{h}{t}$的最小值,作为阴影暗度。

Improvement

如果shadow ray的步进使用sphere tracing,半影区可能会不够平滑,这是因为当射线上某一点与最近物体表面的连线与射线垂直时,$\frac{h}{t}$最小,理应记录该情况下的$k\cdot\frac{h}{t}$作为阴影暗度,但使用sphere tracing可能无法到达该情况。

1592381062274

以该图为例,A点为前一个采样点,根据sphere tracing,后一个采样点为B点,而D点才是$k\cdot\frac{h}{t}$最小的点。令$d$为A点与最近物体的距离,$d’$为B点与最近物体的距离,则:

$$

$$\begin{align}p&=\frac{\left|AB\right|+\left|AC\right|+\left|BC\right|}{2}=\frac{d+d+d^\prime}{2}=d+\frac{d^\prime}{2}\\S_{\Delta ABC}&= \sqrt{p\left(p-\left|AB\right|\right)\left(p-\left|AC\right|\right)\left(p-\left|BC\right|\right)}\\&=\sqrt{\left(d+\frac{d^\prime}{2}\right)\left(d+\frac{d}{2}-d\right)^2\left(d+\frac{d^\prime}{2}-d^\prime\right)}=\frac{d^\prime}{2}\sqrt{d^2-\frac{{d^\prime}^2}{4}}\\\left|CD\right|&=\frac{2S_{\Delta ABC}}{\left|AB\right|}=\frac{d^\prime\sqrt{d^2-\frac{{d^\prime}^2}{4}}}{d}=\sqrt{{d^\prime}^2-\frac{{d^\prime}^4}{4d^2}}\\\left|BD\right|&=\sqrt{\left|BC\right|^2-\left|CD\right|^2}=\sqrt{{d^\prime}^2-\left({d^\prime}^2-\frac{{d^\prime}^4}{4d^2}\right)}=\sqrt{\frac{{d^\prime}^4}{4d^2}}=\frac{{d^\prime}^2}{2d}\end{align} $$

令$y=\frac{{d^\prime}^2}{2d}$,则采样D点时,$k\cdot\frac{h}{t}=k\cdot\frac{\left|CD\right|}{t-\left|BD\right|}=k\cdot\frac{\sqrt{{d^\prime}^2-\frac{{d^\prime}^4}{4d^2}}}{t-\frac{{d^\prime}^2}{2d}}=k\cdot\frac{\sqrt{{d^\prime}^2-y^2}}{t-y}$

Ambient Occlusion

我同样实现了近似的环境光遮蔽效果。原理在于从交点沿法线方向发射一条射线,这里使用了固定步长,考虑每个步进到的采样点的距离评估值和到达该点的射线长来评估环境光遮蔽的效果。采样点的距离评估值与到达该点的射线长之比越大,AO值越小,最后取所有采样点的算术平均值。视情况也可以对不同距离的采样点权重不同。

Result

Reference

发表评论

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