On Mesh Cloud Rendering – Reimplement Sea of Thieves’ cloud | 模型云渲染-重现盗贼之海的云

Sea of Thieves, Tech Art and Shader Development中提到了他们的云的渲染方法,非常的trick和cheap,对云的渲染模型做了相当大的简化,然后却还有不错的效果。笔者这里尝试重现Sea of Thieves中云的做法。原作在视频中给出了很多细节原理,笔者这里给出一点补充。

本文的代码,模型,houdini预计算文件在Github可获取: Mesh-Cloud-Rendering

1. 体渲染原理

首先我们回顾一下体渲染的公式

体积渲染图解,来自RTR4
体积渲染公式,来自RTR4

体积渲染的原理可以用上面这个图解和公式表示。

这个式子有两项,p点是视线上第一个不透明物体,c是相机,v是视线

  • 前面一项是p点到c点的透射度(Transmittance) 乘以 p点的颜色
  • 后面一项是一个视线路径的积分,路径上每一点(c-vt)到c点的投射度 乘以 这个点的散射光(scatter) 乘以 体积的散射颜色(scatter coefficient)

其中透射项与灭绝系数(Extinction coefficient),以及路径的长度有关,这个叫Beer-Lambert公式

Beer-Lambert公式,衰减和距离的幂正比

第二项里的散射光

散射光项展开
  • P是光照在散射环境下的相函数,Rayleigh/Mie散射讲的就是这个。
  • V是光照的可见度,实际上有两项,一项是不透明物体阴影产生的,一项是自阴影,也就是当前点到光源的透射度。
  • C项时光源在当前点的强度

由此,云的RayMarching计算基本可以理解了,需要 RayMarching 两次。 RayMarching 一次视线方向,计算视线上每一个点的光照贡献 RayMarching 一次到光源,这对于视线上每一个点都需要计算。

这是个O(n^2)复杂度的RayMarching

当然有很多方法简化这个O(n2)的raymarching,RTR4的14.4.2章讲到了众多可能。

  • 比如用volume particle, 组合一堆球形billboard
  • 比如用mesh+hypertexture,预计算一张hypertexture帮助计算mesh表面的光照传递
  • 比如直接就用raymarching体素,像Decima的方法。其中第二次raymarching,从视线点到光源的透射可以有多种方法简化,

2. Sea of Thieves的简化

Sea of Thieves首先把第一项,背景颜色项简化了。只用一个透明度混合背景颜色,而透明度是用高斯模糊做出来的。

然后对第二项两次raymarching计算,首先忽略掉视线上的raymarching,只计算最表层一个点。

然后对于从表层点到光源的raymarching计算,把云的体积简化成了一个lobe,只有一个方向(叫occlusion, float3)和一个长度(叫density, float)的参数

2.1. 预计算遮挡(Occlusion)

预计算时,首先从每个顶点做整个球面方向的随机射线,按在模型内部移动的距离加权平均求出平均的射线方向,然后求出射线方向的最大距离。

预计算的节点

VEX的计算如下:

这里存了一个vector:meanray,是平均遮挡方向。一个float:densityAlongMean是平均遮挡方向的长度。

2.2 光照计算

在做光照计算时:

某个点的遮挡lobe

用预计算的值可以计算出光照方向的遮挡距离

如上图,椭球是某个点lobe,半透明的是云的形状

于是,这就对任意方向的光源都能计算遮挡距离了,然后根据Beer-Lambert定律就能算出透射度。

基本它云的光照模型就是这个原理。存的数值很少,就只有4个float,放uv上就行。另外假定这几个数值频率比较低,shading直接放顶点上做就好了,pixel shader只输出顶点插值结果就行。

这里我们可以拓展一下,如果不用一个lobe,而用球谐的方式,或者球谐三维点阵的方式,可以表示近似处云内部所有位置的遮挡项?那也是raymarching一次就好了。

在houdini里可以做一个快速的验证:左:体素化后用cloudlight节点渲染,左中:pervertex的两次raymarching ground truth, 中:我们的模拟的结果,中右:Lambert,右:拟合混合lambert

看上去还是有点差距,不过比lambert强多了!

在unity中,顶点光照计算完以后,在屏幕空间进行一些处理,包括模糊,噪声处理等。这里原作参考了Volumetric Clouds and Mega-Particles这篇文章,类似前文讲到的volume particle的做法。

2.3 屏幕空间后处理

顶点会把主光照和天光的transmittance存在RG两个通道,最终合成时再用光线颜色解出来。透明度存在B通道,为了最后与背景合成。当然顶点光照时还是0/1值,后面做模糊。A通道存深度信息,用于后面合成。

顶点光照后做一次高斯模糊,两个pass,一横一竖。这里注意的是,模糊的方差是根据深度做调整的。高斯函数exp(-x^2/2*s^2)中有sigma项,单个pass中,旁边像素的权重其实是x=0,1,2时高斯函数的值,当然要做一次归一化。

这样按远处的轮廓会清晰一些。

左:按深度不同方差的高斯模糊,右:原始

之后对深度做一次boxblur,然后用噪波进行变形

噪波

噪波也是按深度进行混合,用这个噪波笔者做了flowmap混合,原作说做swirl,不太清楚是怎么做的

第一步模糊,扭曲的后处理总共5个pass,

  • 一次降采样到1/4
  • 一次横向gaussian blur
  • 一次纵向gaussian blur
  • 一次box blur
  • 一次distort

最后一步混合到背景里,一个pass

2.4 云模型

最后补充一下云的做法,因为可以用模型云,美术可以就直接可以雕刻形状了,就像光遇中的云。不过笔者这里用了另一种方法,用l-system分布球。转成vdb再转成mesh

3. 讨论

本文的代码,模型,houdini预计算文件在Github可获取: Mesh-Cloud-Rendering

Sea of Thieves的做法相当巧妙,开销很小,效果也相当不错,但还有一些可以提升的地方

  1. 多光源? 当前只有一个RGBA的buffer,只能存天光和一个光源。如果要多光源,可以多几个buffer?
  2. 近处的表现。像光遇一样,近处会有波动,可以考虑顶点运动/曲面细分/POM的做法?
  3. 遮挡项的提升,如作者所讲,当前只有一个lobe,如果存一个二阶球谐呢?

参考资料:

Sea of Thieves, Tech Art and Shader Development

本项目源码地址 Mesh-Cloud-Rendering

2 thoughts on “On Mesh Cloud Rendering – Reimplement Sea of Thieves’ cloud | 模型云渲染-重现盗贼之海的云

  1. ChenA说道:

    云为什么这么模糊啊?还有在暗的场景云的黑边太严重了,这应该是个bug吧。
    1. 多光源可以在shader里一次性计算吧,不需要存transmittance在RT里吧。
    3. 二阶球谐应该可以。

    这种感觉只适合卡通风格的,细节太少,https://realtimevfx.com/t/smoke-lighting-and-texture-re-usability-in-skull-bones/5339这种消耗比较少,你试过没有?

    1. maajor说道:

      谢谢指正!已经更新了图片和shader
      RT里B通道是透明度,A通道是深度,只剩两个了,所以存的是transmittance不是颜色

      感觉细节也许可以通过cubemap贴图采样,比较适用卡通风格是确实
      没试过,感觉崩坏的云和这个做法有点相似,如果云不飘过头顶的话应该也不会穿帮

发表评论

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