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

news2024/11/15 23:18:21

原生 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
	});
	
	// 存储像数值到体数据的 Uint8Array
	data.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.coloradmin.cn/o/1915456.html

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

相关文章

软件架构之软件架构概述及质量属性

软件架构之软件架构概述及质量属性 第 9 章&#xff1a;软件架构设计9.1 软件架构概述9.1.1 软件架构的定义9.1.2 软件架构的重要性9.1.3 架构的模型 9.2 架构需求与软件质量属性9.2.1 软件质量属性9.2.2 6 个质量属性及实现 第 9 章&#xff1a;软件架构设计 像学写文章一样&…

java通过poi-tl导出word实战详细步骤

文章目录 与其他模版引擎对比1.引入maven依赖包2.新建Word文档exportWprd.docx模版3.编写导出word接口代码4.导出成果 poi-tl是一个基于Apache POI的Word模板引擎&#xff0c;也是一个免费开源的Java类库&#xff0c;你可以非常方便的加入到你的项目中&#xff0c;并且拥有着让…

模板语法之插值语法{{}}——01

<主要研究&#xff1a;{{ 这里可以写什么}} 1.在data中声明的变量函数都可以 2.常量 3.只要是合法的JavaScript的表达式&#xff0c;都可以 4. 模板表达式都被放在沙盒中&#xff0c;只能访问全局变量的一个白名单&#xff0c;如 Math 和 Date <body> <div i…

进程间的通信--管道

文章目录 一、进程通信的介绍1.1进程间为什么需要通信1.2进程如何通信 二、管道2.1匿名管道2.1.1文件描述符理解管道2.1.2接口使用2.1.3管道的4种情况2.1.4管道的五种特征 2.2管道的使用场景2.2.1命令行中的管道2.2.2进程池 2.命名管道2.1.1原理2.2.2接口2.2.3代码实例 一、进程…

网络编程:各协议头(数据报格式)

一、mac头 二、ip头 protocol——tcp/udp &#xff08;7&#xff09;TTL——生存时间 三、tcp头 四、udp头

最新2023年行政区划、路网、土壤质地矢量数据

行政区划矢量数据是指用矢量格式表示的地理信息系统&#xff08;GIS&#xff09;数据&#xff0c;其中包含了行政区域的边界信息&#xff0c;如国家、省份、城市、区县、乡镇甚至村级的界限。这些数据通常以点、线、面的几何图形来表示具体的地理实体&#xff0c;并且每个实体都…

傅里叶级数的3D表示 包括源码

傅里叶级数的3D表示 包括源码 flyfish 傅里叶级数的基本形式 &#xff1a; y ( t ) ∑ n 1 , 3 , 5 , … N 4 A n π sin ⁡ ( n π T t ) y(t) \sum_{n1,3,5,\ldots}^{N} \frac{4A}{n\pi} \sin\left(\frac{n\pi}{T} t\right) y(t)n1,3,5,…∑N​nπ4A​sin(Tnπ​t) 其中&…

python爬虫基础入门

步骤 获取网页内容&#xff1a; http请求 python的Requests库 解析网页内容 html网页结构 python的Beautiful Soup库 储存或分析数据 储存进数据库 作为ai分析的数据 转化为图表显示出来 DDoS攻击 通过给服务器发送海量高频请求&#xff0c;大量消耗网页资源&#…

548M高精度全球国界数据

我们在《2024版有审图号的SHP行政区划》一文中为你分享过全国行政区划&#xff0c;现在再为你分享一份高精度的全球国界数据。 如果你需要该全球国界数据&#xff0c;请在文末查看该数据的领取方法。 548M全球国界数据 数据来源于GADM&#xff0c;全称Database of Global Ad…

51.通过获取数据快速实现一个辅助

上一个内容&#xff1a;50.破坏性更小的代码跳转功能完善&#xff08;无敌秒杀&#xff09; 原理是&#xff1a;找一个现成的辅助&#xff0c;使用PCHunter工具看现成辅助对目标游戏做了那些hook操作&#xff0c;然后再使用Ollydbg.exe工具分析现成辅助为何这样做。 下图左边…

图论---无向图中国邮路的实现

开始编程前分析设计思路和程序的整体的框架&#xff0c;以及作为数学问题的性质&#xff1a; 程序流程图&#xff1a; 数学原理&#xff1a; 本质上是找到一条欧拉回路&#xff0c;考虑图中的边权重、顶点的度数以及如何通过添加最少的额外边来构造欧拉回路&#xff0c;涉及到欧…

「媒体邀约」上海请媒体的费用

传媒如春雨&#xff0c;润物细无声&#xff0c;大家好&#xff0c;我是51媒体网胡老师。 上海无疑是最具活动的城市之一&#xff0c;各种大大小小的论坛、发布会、展览展会应接不暇&#xff0c;那么在上海做活动想邀请媒体进行宣传报道&#xff0c;需要多少费用呢&#xff1a;…

Outlook邮件提醒通知功能详解:设置教程!

Outlook邮件提醒通知使用指南&#xff1f;如何个性设计邮件通知&#xff1f; 为了帮助用户更好地管理邮件&#xff0c;Outlook提供了强大的邮件提醒通知功能。AokSend将详细介绍如何设置和使用Outlook邮件提醒通知功能&#xff0c;以提高工作效率和管理时间的能力。 Outlook邮…

昇思25天学习打卡营第12天|应用实践之基于MindSpore通过GPT实现情感分类

基本介绍 今天的应用实践是基于MindSpore通过GPT实现情感分类&#xff0c;这与之前的使用BERT模型实现情绪分类有异曲同工之妙&#xff0c;本次使用的模型是OpenAI开源的GPT&#xff0c;数据集是MindNLP内置的数据集imdb。我们将会使用该数据集对GPT进行训练&#xff0c;然后进…

鲁棒控制器设计方法:systune,hinfsyn,musyn,slTuner

systune和hinfsyn更侧重于基于数学模型的控制器设计&#xff0c;而musyn则特别考虑了系统的不确定性。slTuner则提供了在Simulink环境中进行控制器设计和调整的能力。 指定结构的控制器整定&#xff1a;systune, hinfstruct广义控制对象整定&#xff1a;musyn, mixed musyn, h…

LabVIEW自动测控与故障识别系统

使用LabVIEW 2019在Win10 64位系统上开发自动测控软件&#xff0c;通过与基恩士NR-X100数据采集仪通讯&#xff0c;实时采集和分析数据&#xff0c;自动识别判断产品是否合格&#xff0c;并增加数据记录和仿真功能。 具体解决方案&#xff1a; 1. 系统架构设计 硬件接口&#…

设计模式之工厂模式(简单工厂、工厂方法、抽象工厂)

写在前面&#xff1a;本文是个人在学习设计模式时的所思所想&#xff0c;汇总了其他博主及自己的感悟思考&#xff0c;可能存在出入&#xff0c;请大家理性食用~~ 工厂模式 在工厂模式中&#xff0c;父类决定实例的生成方式&#xff0c;但并不决定所要生成的具体的类&#xf…

带你了解“Java新特性——模块化”

Java平台从Java 8向Java 9及更高版本的进化&#xff0c;其中引入了一个重要的新特性——模块系统&#xff08;Project Jigsaw&#xff09;。模块系统的目的是解决大型应用的依赖管理问题&#xff0c;提升性能&#xff0c;简化JRE&#xff0c;增强兼容性和安全性&#xff0c;并提…

求整数数组的子集【C语言】

方法1&#xff1a;通过二进制位&#xff0c;因为n个整数数组的子集有2的n次方个&#xff0c;例如整数数组为{1,2,3},子集有2的3次方&#xff0c;8个&#xff1b; 期望的输出形式 其中需要了解关注的是 n&1判断最低位是否有数。如果一个子集为{2}&#xff0c;利用二进制位…

C++初阶:类与对象(一)

✨✨所属专栏&#xff1a;C✨✨ ✨✨作者主页&#xff1a;嶔某✨✨ 类的定义 定义格式 • class为定义类的关键字&#xff0c;后面跟类的名字&#xff0c;{}中为类的主体&#xff0c;注意类定义结束时后⾯分号不能省略。类体中内容称为类的成员&#xff1b;类中的变量称为类的…