使用体积纹理制作体积云01

1.原理

我使用的版本是5.4.4
UE可将纹理存储在三维空间中,纹理的每个点都有自己对应的xyz(uvw)坐标。在渲染体积云时,只需要准备一张三维纹理即可。如下。


该图有144张纹理作为序列帧组合在一起。每一张纹理都可被单独采样。当我们需要某个位置的纹理值,就可以对临近的两个切片纹理进行采样后,进行插值得到。将这些切片纹理放在一起就可以得到一个体积云。

如图,在摄像机采样时,需要对每一个方向进行采样。

采样后,能够确定物体在哪些位置出现。如图中绿色的部分是能够看到物体的区域,显示到相机上就如右图所示。

但是做体积云是需要体积的,所以我们让光线继续前进采样,返回一个累积的密度值(密度值由体积纹理提供)。假设图中是一个密度均匀的球体,采样后就会得到如下的结果。

2.插件与设置

需要搜索这个插件并打开。这个插件中有一些制作体积云时比较方便的节点,也可以不下载,自己来写。

并在项目设置中打开光追与距离场

3.制作

3.1 创建材质

创建一个材质,设置为unlit模式

创建一个custom node,并建立7个输入(后面会继续增加),分别是输入纹理,每个方向输入的帧数,总帧数(如果在内部计算会增加消耗,所以在外部计算),相机最大采样步数,采样步长,模型空间的相机方向,当前采样位置。

连接如下

在custom中加入如下代码
定义一个float accumdens用来表示累积密度。在for循环中,总共循环MaxSteps次,每次循环时都进行一次体积纹理采样,并进行累加。体积采样函数需要四个参数,Tex纹理,TexSampler采样器,归一化的当前位置,单一方向的帧数,总帧数。每次采样后进行累加到accumdens中,最后更新CurPos的值。

将纹理更换为云的体积纹理,会得到如下结果

3.2 计算斜方向观察时的步长偏移

如果不从立方体的正面观察(如对角处观察)时,会出现片状的采样结果,观察起来并不完美。这是因为在Bounding Box Base节点中,默认的计算是沿着xyz三个方向的,而从斜侧面看时,也是用的xyz三个方向计算的。如果以倾斜角度观察的话,采用了不正确的步长,采样结果就会有误差。

需要使其采样步长符合下图

在前面提到的需要安装的插件中有可以修正的函数。找到unspecified function节点

我们需要添加如下图所示的函数

进入该节点。该节点输入三个参数:一个自定义标量Input Plane alignment,可以取1或者0,取决于我们是否想要把平面和相机对齐;采样步数;场景深度。

在下面的代码中进行计算,找到与相机方向垂直的平面,并与该边界框相交,计算相交出的第一个交点与最后一个交点,通过这两个交点计算斜方向观察与正面观察的偏移量,用当前相机坐标与该偏移量相加计算当前采样位置,最后返回采样位置与包围盒厚度。但是由于在

localcampos = (localcampos / (GetPrimitiveData(Parameters).LocalObjectBoundsMax.x * 2)) + 0.5;

这行代码中,对相机的位置进行了归一化,是通过从正面观察的方向去进行归一化的,因此从斜面观察时,会有一部分的位置无法采样得到,也就无法渲染。

此外,增加了一点优化。在计算盒子距离时只需要一步,我们可以通过预计算出盒子的厚度来计算采样步长,这样可以避免过多的计算。
为了解决以上节点,我们退回到material中继续连接。

会出现报错,Only transparent or postprocess materials can read from scene depth,修改材质的混合模式。

3.3 翻转法线

在之前的节点中,无法进入盒子中观察云的采样(只能从盒子外观察)。需要一个反转法线的立方体。可以在建模软件中实现,也可以利用UE5的插件,启用如下的两个插件。

进入modeling modeb并建立一个立方体,点击accepet。

它的pivot不在中心,所以需要进入edit pivot,点击center

接下来翻转法线,进入attribute并点击normals,invert normals

反转好了,可以进入selection mode继续操作。将材质赋给刚才创建的立方体,即可进入到立方体中观察到体积云。

3.4 体积云密度计算

接下来修改体积云的连接,并把blend mode改成translucent

加入贝尔兰伯特模型来计算密度,找到beers law并连接如下。它能够计算在不同密度下介质吸收了多少光。可以通过更改depth scale来更改density。

3.5 物体进入体积云交互

但是此时物体无法被体积云遮挡。此时计算的是球体与立方体之间的交集,而非与体积云内部的体积。

首先需要进入材质中关闭深度测试。因为我们不需要立方体的深度,需要的只有体积云本身。

接下来用box distance来计算我们需要的深度值。由于box distance是进行过归一化的值,因此需要与采样步数相乘使用。

将其连接至max steps。用floor对其取整,并限制在256以下。

如果使用的volumes插件,会出现物体与体积云进行交互时计算不正确。
添加如下的代码

添加后注释掉前面的计算,最终修改结果如下

修改material中的blend mode

并修改节点

但是交互时出现了层之间分离的情况,这是因为我们采样时的步数直接取整,由于球体较小,因此直接忽略掉小数部分会使得计算结果不准确。

在custom节点中继续添加输入参数FinalStepSize

并将采样的最大步数取小数连入

修改代码
修改的第一行代码中,使用了LWCTToFloat函数,如果物体放大时会丢失精度,可以用这行代码来修正。在前面的for循环结束时,我们已经得到了当前的采样位置。接下来在后面的红色高亮代码中对最后一次采样进行修正。首先后退一步至倒数第二步采样Position,接下来用这个Position加上修正后的采样密度,即前面取到的小数部分作为缩放与StepSize相乘。接下来正常进行体积采样和累加密度即可。

【未完待续】
参考
[Unreal 5.3 – Making a volumetric ray marching shader from scratch (part 1) (youtube.com)](https://www.youtube.com/watch?v=eDYyBc3cRmw)

发表评论