我的专栏目录:

小IVan:专题概述及目录

目前业内流行有两种体积云模拟的方式,模型+特殊shader法,RayMarching法。我前两篇文章已经对它们都做了介绍。当然还有些比较非主流的,比如粒子云法,点云法。这篇将作为体积云天空模拟部分的终篇,如果有一些更好的方法,我会更新这三篇文章。前面两篇介绍了它的原理。这里将记录Ray Marching法的完整具体实现方法。

虚幻4渲染编程(环境模拟篇)【第一卷:体积云天空模拟(1)---层云】

虚幻4渲染编程(环境模拟篇)【第二卷:体积云天空模拟(2)---3D体纹理低云】


下面是使用ray marching的办法构造天空。第一步是构造出一个体积场。我使用3D Value Noise

下面是我的Noise生成函数的代码

分形部分的代码

raymarching部分的代码如下:

这部分逻辑非常简单,从视口方向发射射线,每0.01单位的距离采样一次,并且把采样结果累加起来。因为我们的摄像机是世界空间的,而noise使用的是另一个空间的(这里把它叫作Noise Space吧),所以我们需要对其进行尺寸缩放转换。所以我乘了一个0.001的缩放值。

现在得到了一个体积场,但是这个体积场还离我需要的云的效果还很远。云除了有一定的形状结构外,还要相对于世界的高度等。

用目前这种积分方式是渲染不出云海的

因为迭代次数有限,根本不可能渲染到很远的地方,于是我改进了一下积分的步长算法,让步长不是等距的。

        curpos += rd * exp(0.01 * i) * stepsize;

可以看到算法改进前后的差别

(等距)

(非等距)

改进后能够追踪到更远的距离而且不会影响近处的效果。但是这仍然达不到云海的需求,因为对于这种方法来说还是无法追踪覆盖云海那种超大范围的空间。改进步长算法已经不能满足要求了,因为如果继续拉高步长的增长曲线已经会出现明显的不连续了。所以这时候就需要重新定位追踪起始点的位置。

可以进行一次求交,算出云层的位置然后从这个位置再开始raymarching,这样就能达到非常远的距离。

再对整个天球和噪波做一些调整,用smoothstep控制一下它的衰减

在控制衰减和数值调整后,于是我们可以得到一个非常粗略的天空。

可以看到云的形状还不够,现在这个形状实在太“噪波”了,云朵的光影也不正确。于是下面就来根据这两点来进行完善。

【云朵的光影】

在每次迭代积分云层浓度的时候,还需要向太阳光方向积分能量衰减。我们每次循环积分云的浓度的时候得到一个位置,然后再用这个位置作为向太阳方向积分的起始点,太阳方向作为积分方向,再次积分。

沿着太阳方向积分能量其实就是对云做light shading的过程,做light shading自然就需要建立光照模型。云的光线传播有反射和散射。光线大致是像下图那样传播的

所以对上图的光线进行数学建模。首先考虑到光线传播的能量衰减:

下面是衰减公式

用这种最简单的能量衰减公式可以得到如下效果

调整下smoothstep的范围,进一步控制一下云的形状,把高层的杂云clip掉即可得到如下效果:

离想要的效果又进了一步了。

但是这里面有一个不正确的地方是光照和浓度积分没有分开,将光照和浓度剥离后即可得到如下效果

除了衰减,光线还会在云层中散射。散射计算常用到Henyey Greenstein模型。

其函数图像如图所示,g代表云层的各向同性大小

Horizon zero drawn又对HenyeyGreenstein模型又进行了完善

使用上述 Horizon zero drawn 的光照模型可以得到如下效果:

给天空加上着色和运动后

代码如下:

        #define NUM_STEPS 128float hash(float3 p)
{p = frac(p * 0.3183099 + 0.1);p *= 17.0;return frac(p.x * p.y * p.z * (p.x + p.y + p.z));
}float Noise(in float3 x)
{float3 p = floor(x);float3 f = frac(x);f = f * f * (3.0 - 2.0 * f);return lerp(lerp(lerp( hash(p + float3(0,0,0)), hash(p + float3(1,0,0)),f.x),lerp( hash(p + float3(0,1,0)), hash(p + float3(1,1,0)),f.x),f.y),lerp(lerp( hash(p + float3(0,0,1)), hash(p + float3(1,0,1)),f.x),lerp( hash(p + float3(0,1,1)), hash(p + float3(1,1,1)),f.x),f.y),f.z);
}float map5(in float3 p)
{float3 q = p;float f = 0.0f;f  = 0.50000 * Noise( q );q = q * 2.02;f += 0.25000 * Noise( q );q = q * 2.03;f += 0.12500 * Noise( q );q = q * 2.01;f += 0.06250 * Noise( q );q = q * 2.02;f += 0.03125 * Noise( q );return clamp(f, 0.0, 1.0);
}float map3( in float3 p )
{float3 q = p;float f = 0.0f;f  = 0.50000 * Noise( q );q = q * 2.02;f += 0.25000 * Noise( q );q = q * 2.03;f += 0.12500 * Noise( q );return clamp(f, 0.0, 1.0);
}//origin       : Ray origin position
// dir          : Ray direction
//spherePos     : Sphere center position
//sphere Rad    : Sphere radius
float intersectSphere(float3 origin, float3 dir, float3 spherePos, float sphereRad)
{float3 oc = origin - spherePos;float b = 2.0 * dot(dir, oc);float c = dot(oc, oc) - sphereRad * sphereRad;float disc = b * b - 4.0 * c;if (disc < 0.0)return -1.0;    float q = (-b + ((b < 0.0) ? -sqrt(disc) : sqrt(disc))) / 2.0;float t0 = q;float t1 = c / q;if (t0 > t1) {float temp = t0;t0 = t1;t1 = temp;}if (t1 < 0.0)return 0.0;return (t0 < 0.0) ? t1 : t0;
}float Lighting(float3 ro, float3 rd, float3 curpos)
{   float en = 0.0f;float3 L = float3(1, 1, 1);float3 lightpos = curpos;float lightstepsize = 0.9;float lightden = 0;float cos = dot(L, rd);float g = 0.3;float hen = (1- g*g) / pow((1 + g*g - 2 * g * cos), 3/2) / (4 * 3.1415);hen *= 1.5;for(int i = 0; i < 4; i++){lightden = map3(lightpos * 0.002);lightpos += L * lightstepsize * exp(0.01 * i);}//hen *= 2 * exp(-lightden) * (1 - 2 * exp(2 * lightstepsize * 4));en = exp(- 1.2 * lightden * i);en = smoothstep(0.01, 0.99, en);return en * hen;
}float4 mainImage(float2 fragCoord, float2 iResolution, float iTime, float3 rd, float3 ro, float3 wpos, Texture2D remap, SamplerState remapSampler)
{float4 Output = float4(0, 0, 0, 1);float stepsize = 3;//iTime *= 0.01;float TransToNoiseSpace = 0.001;ro *= TransToNoiseSpace;float t = intersectSphere(ro, rd, float3(0, 0, -99000), 100000);float3 curpos = ro + rd * t;curpos += float3(iTime, 0, 0);float4 col = float4(0, 0, 0, 0);for(int i = 0; i < NUM_STEPS; i++){if(curpos.z < 30) break;//step size distance functionfloat dens = map5(curpos * 0.002);dens = smoothstep(0.4, 0.55, dens);float3 pos = curpos;float T = Lighting(ro, rd, curpos);col.rgb += dens * 0.2 * (exp(-0.01 * i) + 1);col.a += T * 0.005;curpos += rd * exp(0.01 * i) * stepsize;}//col = pow(col, 15);//Output.rgb = lerp(float3(0.4,0.6,0.8), col.rgb * 10 , col.r);Output.rgb = col.rgb * col.a;return Output;
}//return  mainImage(fragCoord, iResolution, iTime, rd, ro, wpos, remap, remapSampler);// fragCoord
// iResolution
// iTime
// rd
// ro
// wpos
// remap

如果想让云朵拥有更多细节,可以考虑在Noise函数上下功夫。我把Map5换成Map7可以得到更多细节并且性能损失也较少。最后我们可以得到如下效果:


在做天空的过程中,很多次失败还无意间做出了很多意想不到的有趣效果

【水下效果】

【丑陋的山洞效果】

接下来的环境模拟篇将开启可交互植物模拟的篇章。

Enjoy it 。


Next:

小IVan:虚幻4渲染编程(环境模拟篇)【第三卷:体积云天空模拟(4) - 基于Texture的体积云】


参考文章:

【1】http://www.oceanopticsbook.info/view/scattering/the_henyeygreenstein_phase_function

【2】http://killzone.dl.playstation.net/killzone/horizonzerodawn/presentations/Siggraph15_Schneider_Real-Time_Volumetric_Cloudscapes_of_Horizon_Zero_Dawn.pdf

虚幻4渲染编程(环境模拟篇)【第三卷:体积云天空模拟(3)---高层云】相关推荐

  1. unity scence灯光不显示_Unity渲染编程(灯光篇)【第二卷:MobileVolumetricLight】

    MY BLOG DIRECTORY: todo... INTRODUCTION: 如果需要一个方案来渲染城镇或空旷的马路上巨量的灯光.图形程序拿到这个需求直接开始搞F+或者延迟光照,但是对于移动端的巨 ...

  2. 虚幻4渲染编程(环境模拟篇)【第五卷:可交互物理植被模拟 - 上】

    我的专栏目录: 小IVan:专题概述及目录 开篇综述 这一卷将会开始研究可交互植被环境的模拟.我把可交互植被环境模拟这个大的课题拆解为几个部分.我挑选了几个森林模拟至关重要的几个要素并且实现它们. [ ...

  3. 虚幻4渲染编程(材质编辑器篇)【第三卷:正式准备开始材质开发】

    My blog directory: YivanLee:专题概述及目录 Introduction: 前面两章我们已经完成了对工具的研究,下面我们久正式开始启程啦!后面的内容可能就比较美术了. 还是老规 ...

  4. 虚幻4渲染编程(图元汇编篇)【第五卷:游戏中的动力学模拟】

    我的专栏目录 小IVan:专题概述及目录 还是先上效果吧 目前(2018年)在游戏中,通常使用韦尔莱积分做动力学模拟.使用韦尔莱积分可以模拟大部分物体的运动.布料,绳子,弹簧,软体,棍子都可以模拟.但 ...

  5. 渲染到ui_虚幻4渲染编程(UI篇)【第二卷:程序化UI特效-[1]】

    MY BLOG DIRECTORY: 小IVan:专题概述及目录​zhuanlan.zhihu.com INTRODUCTION: 当遇见某些特殊需求,比如对游戏效果有很多变化的要求,这时使用静态的贴 ...

  6. 虚幻4渲染编程(材质编辑器篇)【第五卷:布料,丝绸纱皮革棉】

    My blog directory: 小IVan:专题概述及目录 Introduction: 现在的游戏对质感要求越来越高(我估计是硬件越来越好,可编程管线越来越来越完善).游戏的画面已经越来越接近影 ...

  7. 虚幻4渲染编程(灯光篇)【第二卷:体积光】

    我的专栏目录: 小IVan:专题概述及目录 体积光在游戏里被越来越多地用到,对烘托场景气氛,提高游戏的逼格有比较重要的作用.这篇就来由浅入深研究一下这个东西.从容易的做法到高端做法依次递进. 首先先来 ...

  8. 虚幻4渲染编程(Pipeline篇)【第一卷:PBR Production Pipeline】

    我的专栏目录: 小IVan:专题概述及目录 简介: 当技术这边完成理论推导和研发后,需要为其配备一整套完整的落地方案,并且这套方案的成熟度,完善度和开发效率直接决定了产品的最后品质和公司的生产成本.随 ...

  9. 虚幻4渲染编程(游戏Demo篇)【第一卷:飞机大战】

    我的专栏目录: 小IVan:急速游戏开发综述及目录 概述: 技术美术需要对整个游戏制作流程都非常清楚,这样才能在开发中提出合理的制作方法.有时候有空可以自己做做游戏demo,这个过程也非常有趣.游戏D ...

最新文章

  1. python识别文字并且提示_python脚本:检测字符串标识符
  2. 剑指offer之二叉树的下一个结点
  3. VS2008 JS调试和Silverlight 后台代码调试 相互影响的问题。---自己做实例证明
  4. 企业注册一站式服务平台公司宝App挂牌新三板
  5. c语言公路竖曲线要素代码,竖曲线要素
  6. python之数据处理篇
  7. 基于 MindStudio 完成 SE-ResNeXt101- PyTorch 模型开发
  8. 东北师范大学计算机研究生拟录取名单,东北师范大学2016年硕士研究生拟录取名单公示...
  9. 天然气阶梯是按年还是按月_燃气阶梯是一年一清吗 燃气阶梯的定义
  10. 使用 MitmProxy 玩爬虫的,这篇文章别错过了!
  11. 鱼和熊掌兼得!这些应用是如何使用 Material Design 的?
  12. mysql数据库文件损坏的原因_MySQL数据库文件损坏如何解决
  13. lumen时间不准确,少8个小时
  14. 都是行业盛宴,AWE和CES等展会到底有啥不一样?
  15. 【前沿技术RPA】 一文学会用UiPath实现PDF自动化
  16. ArcGIS分子式的标注
  17. Mxnet (45): 使用sequence-aware recommender(Caser模型)进行电影推荐
  18. Linux常用命令——tac命令
  19. VGA、QVGA、HVGA、WVGA、
  20. SLAM专题(10)- 最小化重投影误差与Bundle Adjustment (BA)

热门文章

  1. 独立站导航栏装修指南
  2. Python操作MongoDB看这一篇就够了
  3. 计算机基本原理 学习笔记(五)
  4. Vivo升级android版本,vivo手机系统怎么升级?vivo系统升级教程
  5. Permission denied: user=administrator, access=WRITE 问题解决
  6. 微擎 人人商城 头像获取失败问题
  7. ecshop小京东首页分类楼层左侧广告修改方法
  8. JAVA编写Word
  9. 腾讯小程序php,微信小程序实现使用腾讯地图SDK步骤详细介绍
  10. 用python提取文字中省份与城市