Cesium中实现全球体积云效果的一种方案

原生 Cesium 提供了一种积云的效果,云的物理特征和渲染性能都还不错,这种方案适合表达小范围相对离散的云朵,但是用来实现全球范围下相对连续、柔和渐变的云层比较困难。本文在体渲染的基础上,参考了开源社区中 shadertoy 和 three.js 关于体积云的一些例子,提出一种在 Cesium 上实现全球体积云效果的方案。

Cesium 上进行体渲染可以参考《Cesium中使用Sampler3D,3D纹理,实现体渲染》。体渲染有相对固定的实现流程,关键在于以三维纹理表达的体数据的构建。由于云没有统一的形状和密度,因此我们在构建体数据时,需要引入噪声来表达这种随机性。本文参考的 shadertoy 效果 用到了perlin 噪声和 worley 噪声的组合,并且在 perlin 噪声和 worley 噪声的基础上都加上了分形布朗运动(Fractal Brownian Motion),将不同振幅(amplitude)和频率(frequency)的多个噪声叠加起来,让噪声有更多细节,使得云层的形态更加自然。

体数据的计算我基本照搬了上面链接中 shadertoy 的代码。计算过程放在 CPU 或者 GPU 上都可以。我最开始是在 CPU 上做的,但是发现实在是太慢了,光计算体数据就要花两三分钟;后来转到 GPU 上,用渲染到纹理的方式,依次把三维纹理每一层(三维纹理可以看成是由一系列二维纹理叠放组成的)的数值绘制到二维纹理上;每绘制一层就把那一层的数据读取(gl.readPixels)到一个 Uint8Array 中,最后合并成一个大的 Uint8Array 用于构建存储体数据的三维纹理,基本流程如下面的代码片段所示。这种方式非常快,几乎感觉不到计算的耗时。这一步创建好的体数据在光线步进(RayMarching)的时候采样。我把计算好的体数据存在了一个 json 文件里,如果有同学想直接拿到一份可用的体积云数据,可以到这里下载。

  const slice = 128; // 体数据是一张 128 * 128 * 128的三维纹理const data = new Uint8Array(slice * slice * slice * 4);for (let i = 0; i < slice; ++i) {// 清空上一次绘制的纹理renderClearCommand.execute(viewer.scene.frameState.context, viewer.scene.view.passState);// 离屏渲染三维纹理的一张二维切片数据renderColorCommand.execute(viewer.scene.frameState.context, viewer.scene.view.passState);// 读取一层二维切片的像数值const pixels = viewer.scene.context.readPixels({ framebuffer: renderFbo,x: 0,y: 0,width: slice,height: slice});// 存储像数值到体数据的 Uint8Arraydata.set(pixels, i * slice * slice * 4);}

 用GPGPU的方式生成体数据

 

光线步进(RayMarching)一般不会从相机位置就开始迭代,为了提升性能,都是将射线和体渲染的范围几何体求一个近处的交点、一个远处的交点,缩短步进的距离。我们要渲染全球范围的体积云,云层的最小高度和最大高度分别确定了两个球体,射线需要和这两个球体求交。下面代码参考自《Unity URP RayMarching 体积云》,其中 raySphereDst 函数用于计算射线和球体的相交情况,rayCloudLayerDst 函数计算从相机发出的射线和云层最小高度和最大高度分别确定的两个球体的相交情况,从而得到体积云渲染光线步进的起点和终点。

	/*计算射线和球体的相交情况sphereCenter 球体中心sphereRadius 球体半径rayOrigin 步进起点rayDir 步进方向返回值:dstToSphere  射线起点到球体的距离dstInSphere  射线穿过球体的距离*/vec2 raySphereDst(vec3 sphereCenter, float sphereRadius, vec3 rayOrigin, vec3 rayDir){vec3 oc = rayOrigin - sphereCenter;float b = dot(rayDir, oc);float c = dot(oc, oc) - sphereRadius * sphereRadius;float t = b * b - c; // t > 0有两个交点, = 0 相切, < 0 不相交float delta = sqrt(max(t, 0.0));float dstToSphere = max(-b - delta, 0.0);float dstInSphere = max(-b + delta - dstToSphere, 0.0);return vec2(dstToSphere, dstInSphere);}/*计算相机发出的射线与云层范围的相交情况返回值:dstToCloudLayer  到云层的最近距离dstInCloudLayer  在云层中穿过的距离*/vec2 rayCloudLayerDst(vec3 rayOrigin, vec3 rayDir){vec3 sphereCenter = vec3(0.0);vec2 cloudDstMin = raySphereDst(sphereCenter, (minCloudHeight + earthRadius) / (maxCloudHeight + earthRadius), rayOrigin, rayDir);vec2 cloudDstMax = raySphereDst(sphereCenter, 1.0, rayOrigin, rayDir);float cameraHeight = czm_eyeHeight;// 射线到云层的最近距离float dstToCloudLayer = 0.0;// 射线穿过云层的距离float dstInCloudLayer = 0.0;// 在地表上if (cameraHeight <= minCloudHeight){vec3 startPos = rayOrigin + rayDir * cloudDstMin.y;dstToCloudLayer = cloudDstMin.y;dstInCloudLayer = cloudDstMax.y - cloudDstMin.y;return vec2(dstToCloudLayer, dstInCloudLayer);}// 在云层内if (cameraHeight > minCloudHeight && cameraHeight <= maxCloudHeight){dstToCloudLayer = 0.0;dstInCloudLayer = cloudDstMin.y > 0.0 ? cloudDstMin.x: cloudDstMax.y;return vec2(dstToCloudLayer, dstInCloudLayer);}// 在云层外dstToCloudLayer = cloudDstMax.x;dstInCloudLayer = cloudDstMin.y > 0.0 ? cloudDstMin.x - cloudDstMax.x: cloudDstMax.y;return vec2(dstToCloudLayer, dstInCloudLayer);}

 计算光线步进的起点和终点

上面的步骤分别是体数据的生成和光线步进起止点的计算,体积云的正式渲染可以参考 three.js 官方给出的体积云示例,这个示例也只适合小场景离散的云朵渲染,结合上面的步骤可以在 Cesium 上拓展为全球体积云效果。该示例的实现比较简单,主要工作量是在通过噪声生成体数据以及片元着色器中相关的着色代码。

本文介绍的全球体积云实现方案主要是把现有的一些开源方案做了组合,它不需要依赖外部的噪声纹理,也不用再从片元向光源步进去计算云层的漫反射光颜色,优势是实现步骤简单明了,在仿真要求不是特别高的场景是够用的。最终可以得到如下图1和2所示的体积云效果。

图1 全球视角下的体积云效果

图2 近地面体积云效果 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/869881.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

c#调用c++ dll库报错System.BadImageFormatException

System.BadImageFormatException:“试图加载格式不正确的程序。 (异常来自 HRESULT:0x8007000B)” 1. dll需要选择release模式进行编译 2.选择相同位数&#xff0c;比如x64平台&#xff0c;c#也需要x64 3.不要设置c#不支持的函数供调用 比如&#xff1a; c可以输出到控制台…

Meta发布Llama 2驱动的AI代码生成器:Code Llama,开源来袭!

Meta 刚刚了号称是编程领域 “最先进的大语言模型”—— Code Llama &#xff0c;可根据 代码和自然语言提示 生成代码和有关代码的自然语言&#xff0c;支持多种主流编程语言&#xff0c; 包括 Python、C、Java、PHP、Typescript (Javascript)、C# 和 Bash 。 Code Llama 完全…

人话学Python-基础篇-输入输出,数据类型转换,注释

##以下内容均为自我学习的理解&#xff0c;大多数是本人学习的经验。 一&#xff1a;输入输出 Python的输出并不像其他语言一样那么复杂&#xff0c;有一大堆的标识符、占位符等。只需要一个简单易懂的单词就可以完成变量的输入。 print("Python is the best language i…

拆分盘究竟是什么?一篇文章带你了解!

拆分盘是一种特殊的理财产品或投资模式&#xff0c;它通常被描述为“只涨不跌”的投资方式&#xff0c;多指股票&#xff0c;但实质上与传统股市中的股票有本质区别。以下是对拆分盘的详细解析&#xff1a; 一、拆分盘的定义 拆分盘可以理解为一种只涨不跌的理财股票。其特点在…

EtherCAT主站IGH-- 12 -- IGH之foe_request.h/c文件解析

EtherCAT主站IGH-- 12 -- IGH之foe_request.h/c文件解析 0 预览一 该文件功能`foe_request.c` 文件功能函数预览二 函数功能介绍1. `ec_foe_request_init`2. `ec_foe_request_clear`3. `ec_foe_request_alloc`4. `ec_foe_request_copy_data`5. `ec_foe_request_timed_out`6. `e…

Nikola Tesla Quotes * 3

“If you only knew the magnificence of 3, 6 and 9, you would have a key to the universe”. “If you want to find the secrets of the universe, think in terms of energy, frequency and vibration”. “My brain is only a receiver, in the Universe there is a cor…

OpenGL3.3_C++_Windows(29)

Demo exposure 0.1f exposure 5.0f HDR色调映射 问题&#xff1a;有多个亮光源使这些数值总和超过了1.0&#xff0c;颜色值会被约束在1.0&#xff0c;从而导致场景混成一片&#xff0c;难以分辨&#xff1a;色调映射&#xff1a;用更大范围的颜色值渲染从而获取大范围的黑暗…

java数组的扩容与缩容

一、java扩容缩容的说明 我们前面学过&#xff0c;创建java数组会在内存中开辟一块连续的空间&#xff0c;该空间固定不允许扩大和缩小。那么一个java数组要怎么实现扩容和缩容操作呢&#xff1f; 也许你会想到。再创建一个大容量的数组&#xff0c;将旧数组中的元素拷贝到新数…

怎么将3张照片合并成一张?这几种拼接方法很实用!

怎么将3张照片合并成一张&#xff1f;在我们丰富多彩的日常生活里&#xff0c;是否总爱捕捉那些稍纵即逝的美好瞬间&#xff0c;将它们定格为一张张珍贵的图片&#xff1f;然而&#xff0c;随着时间的推移&#xff0c;这些满载回忆的宝藏却可能逐渐演变成一项管理挑战&#xff…

LeetCode 981, 219, 78

目录 981. 基于时间的键值存储题目链接标签思路代码 219. 存在重复元素 II题目链接标签思路代码 78. 子集题目链接标签思路代码 981. 基于时间的键值存储 题目链接 981. 基于时间的键值存储 标签 数组 二分查找 排序 思路 时间戳根据时间生成&#xff0c;时间越早&#x…

鸿蒙语言基础类库:【@ohos.util (util工具函数)】

util工具函数 说明&#xff1a; 本模块首批接口从API version 7开始支持。后续版本的新增接口&#xff0c;采用上角标单独标记接口的起始版本。开发前请熟悉鸿蒙开发指导文档&#xff1a;gitee.com/li-shizhen-skin/harmony-os/blob/master/README.md点击或者复制转到。 该模块…

Linux系统之lscpu命令的基本使用

Linux系统之lscpu命令的基本使用 一、lscpu命令介绍二、lscpu命令的使用帮助2.1 命令格式2.2 命令选项2.3 使用帮助 三、lscpu命令的基本使用3.1 查看lscpu版本3.2 直接使用lspcu命令3.3 可解析的格式打印cpu信息3.4 可扩展格式打印cpu信息 四、lscpu命令使用注意事项 一、lscp…

【题目/算法训练】:单调队列单调栈

&#x1f680; 前言&#xff1a; 【算法】单调队列&&单调栈 可以在看完这篇文章后&#xff0c;再来写下面的题目 一、绝对差不超过限制的最长连续子数组 思路&#xff1a; 1&#xff09; 就相当于滑动窗口&#xff0c;维护滑动窗口内的两个值&#xff0c;一个是最大值…

Linux常用选项和指令

目录 Linux指令使用注意 用户创建与删除 ls指令 ls指令介绍 ls常见选项 ls选项组合使用 pwd指令 Linux文件系统结构 多叉树结构文件系统介绍 多叉树结构文件系统的特点 cd指令 绝对路径 相对路径 cd指令介绍 家户目录 最近访问的目录 touch指令 ​编辑mkdir指…

3D模型格式转换工具HOOPS Exchange如何访问产品制造信息(PMI)?

在当今的制造和设计领域&#xff0c;产品制造信息&#xff08;PMI&#xff09;在确保零件和产品满足精确规格方面发挥着至关重要的作用。PMI&#xff0c;特别是几何尺寸和公差&#xff08;GD&T&#xff09;&#xff0c;提供了制造过程中必须遵循的详细指导。 随着技术的进…

保持边界感

人与人相处&#xff0c;如同刺猬抱团取暖&#xff1a;靠得太近&#xff0c;会刺痛对方&#xff1b;离得太远&#xff0c;又无法御寒。很多时候&#xff0c;我们与人相处&#xff0c;不是不懂得亲近&#xff0c;而是不懂得疏远。人与人交往&#xff0c;拥有边界感真的很重要。 …

Grind 75 | 3. merge two sorted lists

Leetcode 21. 合并两个有序链表 题目链接 思路&#xff1a; 和归并排序中 merge 部分一致 两个指针分别指向 2 个链表头每次选小的那个加入 res 中&#xff0c;对应指针后移一位;重复步骤2&#xff0c;直至一个指针到链表末尾将另一个剩余的全部 copy 到 res 中&#xff0c;链…

js ES6 part2

forEach遍历 forEach() 方法用于调用数组的每个元素&#xff0c;并将元素传递给回调函数 主要使用场景&#xff1a; 遍历数组的每个元素 语法 被遍历的数组.forEach(function(当前数组元素&#xff0c;当前元素索引号){ //函数体 }) 1. forEach 主要是遍历数组 2. 参数当前…

Milvus核心组件(1)- Architecture

目录 cluster 模式 数据请求处理流程 总流程 逻辑channel 到物理channel 数据维护流程 cluster 模式 上一篇其实已经说过 standalone 模式&#xff0c;其实集群模式大同小异&#xff0c;只是在不同机子间使用Kafka或者其他消息中间件保证数据及逻辑的一致性。 Log Broker…

Mac上配置多版本JDK

在Mac上配置多版本JDK可以通过以下步骤进行&#xff1a; 1. 下载并安装多个JDK版本 你可以从 Oracle 或 AdoptOpenJDK 下载你需要的JDK版本。安装完成后&#xff0c;这些JDK版本通常会被安装在 /Library/Java/JavaVirtualMachines 目录下。 2. 配置环境变量 你可以通过修改…