Ray Marching体积云渲染(一):理论

在 Youtube 上刷到了《Coding Adventure: Clouds》这一视频,正好最近想做一个 3D 地图的练手程序,遂记录一下 Ray Marching 体积云渲染的原理和实现。

SDF & Ray Marching

Ray Marching 翻译成中文往往称为光线步进
Ray Marching 解决的问题是 3D 空间下 SDF 片元空间信息重建和着色问题。

SDF

我们可以用一个式子描述一个圆心在原点,半径为 R 的圆:

length(target.xy - vec2.zero) <= r

上面这个式子一般叫做 SDF(Signed Distance Field),中文叫做有符号距离场,因为是有符号,因此书写形式一般如下:

// 根据返回值的正负判断点是否在范围内
float sdCircleTest(vec2 pos)
{
    return length(target.xy - vec2.zero) - r;
}

扩展到三维的话,描述的就是一个球:

float sdSphereTest(vec3 pos)
{
    return length(target.xyz - vec3.zero) - r;
}

简单来说的话,常规方法用顶点/三角面/Mesh来描述模型,而 SDF 用公式描述一个模型,二者虽然实现不同,但目的相同。

小知识:多个 SDF 描述联合使用,可以方便地实现一些效果,例如 3D 建模中的布尔:

subtract = max(-sdfA,sdfB)
intersection = max(sdfA,sdfB)

ShaderToy 上很多大神的作品都是基于 SDF 的。

Ray Marching

消失的 Z

SDF 扩展到 3D 空间时存在一个问题:若直接绘制 SDF 图形,由于缺少片元的“深度”,因此无法进行多个 SDF 元素的遮挡关系、光照、阴影等解算。

虽然可以通过深度图获取不透明队列的片元 3D 坐标,但这个解决的是不透明队列与 SDF 两个不同体系的重叠、遮挡问题,无法解决多个 SDF 图形互相重叠、遮挡的问题。

为 SDF 体系下的片元重建 3D 信息

为了真正“渲染” 3D 模型,我们需要知道 SDF 体系下每个片元的“深度”、“颜色”等基础信息,这就是 Ray Marching 要做的事情。

熟悉 3D 渲染流程的不难理解,从某种意义上说,整个屏幕的图像可以视为贴在摄像机近裁切面上的一幅图。基于此,我们构建一个向量,其从摄像机原点指向近裁切面图像的某个“片元”:

使用上述向量,向场景投射射线,沿着射线出发,每隔一小段就计算一下,直到击中物体或到达最大长度,这就是所谓的“步进”,前进的距离即为深度:

对每个片元都进行上述投射过程,最终可以重建 SDF 体系下每个片元的 3D 信息,之后再用一些式子求得法线等参数,后续的遮挡、光照计算将与传统渲染管线无异。

体积云

体积云主要依赖两点:

  • Worley Noise:数据
  • Texture3D:插值

Worley Noise

Worley noise 是一种快速生成的 Cellular Noise 的实现,这种噪声图类似于细胞。

Cellular Noise 生成过程可以简单概述为:

  1. 随机生成一些特征点
  2. 遍历空间中每个点,计算该点到每个特征点的距离,选出最小的距离值并存储

Steven Worley 对 Cell Noise 做了一些优化,大幅减小了计算量,因此被广泛采用,感兴趣的可以自己搜论文,论文标题为《A Cellular Texture Basis Function》。

在 Worley 之后,还有一些新算法进一步提升了效果/减小了计算量,The Book of Shaders 上这篇关于网格噪声的文章写得非常好,建议阅读:

网格噪声(Cellular Noise)

Texture3D

Unity 中一种特殊的贴图格式,由多张 2D 图像构成,可以在 XYZ 三个方向上采样、插值。

一个形象的比喻是 CT,将 64 层 CT 的截面扫描图叠在一块,图与图中间的空隙插值,可以构建一个 3D 的器官:

Worley Noise, Texture3D, 与体积云

那么,三者有什么关系呢?

仔细回想 Worley Noise,看看它表示的形状,像不像云的切面?它的“细胞核”像不像云的中心?

假如把 Worley Noise 做成 Texture3D,再通过一些阈值 Clip 掉一些小值,将 Texture3D 映射到某个 3D 空间中,那体积云是不是就成型了?

体积云和 RayMarching 有什么关系

我们把上述 Texture3D 视为 SDF,把颜色值或某个颜色通道值作为云的“密度”(也就是 SDF 的输出值),进而利用 Ray Marching 的方法对每个片元进行采样、着色。

由于云是半透明的,因此体积云渲染中的 Ray Marching 不会局限于击中表面,还会进入云的内部进行步进,以进行漫散射光照叠加计算。

总结

到这里,教程中体积云的渲染流程和整体理论就写完了。

至于开发中会用到的云漫反射公式、大气散射、光照计算等,大多属于经验公式,由各种大牛的论文堆砌而成,我们站在巨人的肩膀上,很难说是对是错、孰优孰劣。

后续有时间的话,会把具体的程序实现整理出来。

参考资料

梓喵出没博客(azimiao.com)版权所有,转载请注明链接:https://www.azimiao.com/10940.html
欢迎加入梓喵出没博客交流群:313732000

发表评论

*

*