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
	});
	
	// 存储像数值到体数据的 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 近地面体积云效果 

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

java数组之线性查找、二分法查找

一、线性查找 思想&#xff1a;如果想在一个数组中查找是否有某个元素&#xff0c;最容易想到的办法就是遍历数组&#xff0c;将数组中元素与想要查找的元素逐个对比&#xff0c;如果相等表示找到了&#xff0c;如果不等&#xff0c;则表示没找到。这就是线性查找的思想。 案例…

如何在微信小程序中对接微信支付

个人名片 &#x1f393;作者简介&#xff1a;java领域优质创作者 &#x1f310;个人主页&#xff1a;码农阿豪 &#x1f4de;工作室&#xff1a;新空间代码工作室&#xff08;提供各种软件服务&#xff09; &#x1f48c;个人邮箱&#xff1a;[2435024119qq.com] &#x1f4f1…

流模型flow

流模型 Flow 超详解&#xff0c;基于 Flow 的生成式模型&#xff0c;从思路到基础到公式推导到模型理解与应用&#xff08;Flow-based Generative Model&#xff09;_generative flows-CSDN博客

软考《信息系统运行管理员》-3.1信息系统设施运维的管理体系

3.1信息系统设施运维的管理体系 1 信息系统设施运维的对象 基础环境 主要包括信息系统运行环境(机房、设备间、配线室、基站、云计算中心 等)中的空调系统、供配电系统、通信应急设备系统、防护设备系统(如消防系统、安全系统) 等&#xff0c;能维持系统安全正常运转&#xf…

食物链之带权并查集解法

直接看题&#xff1a;https://www.acwing.com/problem/content/description/242/ 下面就是代码的实现了&#xff0c;因为自己与自己肯定是同类我们初始化为0. 下面是AC代码&#xff1a; #include<bits/stdc.h> using namespace std; int n,k; int fk,x,y; int fa[10001…

C++ STL IO流介绍

目录 一&#xff1a;IO流的继承关系&#xff1a; 二&#xff1a;输入输出功能 1. 基本用法 2. 格式化输入 3.非格式化输入 4. 格式化输出 三&#xff1a;流 1. 字符流 2. 向字符流中写入数据 3. 从字符流中读出数据 4. 清空字符流 5.完整的例子 四&#xff1a;文件…

RISC-V异常处理流程概述(2):异常处理机制

RISC-V异常处理流程概述(2):异常处理机制 一、异常处理流程和异常委托1.1 异常处理流程1.2 异常委托二、RISC-V异常处理中软件相关内容2.1 异常处理准备工作2.2 异常处理函数2.3 Opensbi系统调用的注册一、异常处理流程和异常委托 1.1 异常处理流程 发生异常时,首先需要执…

生物打印后的生物力学过程

生物打印后的生物力学过程 3D生物打印技术在组织工程领域展现出巨大的潜力&#xff0c;但打印后组织的生物力学特性对其最终成功至关重要。本文将详细介绍打印后组织的生物力学特性及其在组织工程中的应用。 1. 打印后水凝胶交联 原位交联可以在生物打印过程中提供足够的机械…

开发个人Go-ChatGPT--5 模型管理 (一)

开发个人Go-ChatGPT–5 模型管理 (一) 背景 开发一个chatGPT的网站&#xff0c;后端服务如何实现与大模型的对话&#xff1f;是整个项目中开发困难较大的点。 如何实现上图的聊天对话功能&#xff1f;在开发后端的时候&#xff0c;如何实现stream的响应呢&#xff1f;本文就…

SprintBoot创建遇到的问题

最近使用IDEA版本为2022.3.1&#xff0c;java版本为21.0.3&#xff0c;现在做一个创建SprintBoot3的一个大体流程 1.先下载Maven&#xff0c;解压到一个位置 maven下载 2.配置setting.xml文件 这路径自己配置&#xff0c;这里不多演示 代码如下&#xff1a; <mirror>&…

开源网页终端webssh容器镜像制作与使用

1.Dockerfile编写&#xff1a; # 指定镜像目标平台与镜像名 alpine表示基础镜像 第一层镜像 FROM --platform$TARGETPLATFORM alpine # 添加元数据到镜像 LABEL maintainer"Jrohy <euvkzxgmail.com>" # 编译时变量 ARG TARGETARCH # 执行编译命令&#xff0c;…

代码随想录算法训练营第四十九天| 647. 回文子串、 516.最长回文子序列

647. 回文子串 题目链接&#xff1a;647. 回文子串 文档讲解&#xff1a;代码随想录 状态&#xff1a;不会 思路&#xff1a; dp[i][j] 表示字符串 s 从索引 i 到索引 j 这一段子串是否为回文子串。 当s[i]与s[j]不相等&#xff0c;那没啥好说的了&#xff0c;dp[i][j]一定是fa…

柔性测斜仪:监测钻孔位移的核心利器

柔性测斜仪&#xff0c;作为一款创新的测量工具&#xff0c;凭借其卓越的设计与性能&#xff0c;在地下建筑、桥梁、隧道及水利水电工程等领域展现出非凡的应用价值。其安装便捷、操作简便、高精度及长寿命等特性&#xff0c;使之成为监测钻孔垂直与水平位移的理想选择。以下是…

打卡第8天-----字符串

进入字符串章节了,我真的特别希望把leetcode上的题快点全部都给刷完,我是社招准备跳槽才选择这个训练营的,面试总是挂算法题和编程题,希望通过这个训练营我的算法和编程的水平能有所提升,抓住机会,成功上岸。我现在的这份工作,真的是一天都不想干了,但是下家工作单位还…

VS Code 扩展如何发布到私有Nexus的正确姿势

VS Code扩展的发布 VS Code 扩展的发布需要使用到vsce&#xff0c;vsce是一个用于打包、发布和管理 VS Code 扩展的命令行工具。可以通过 npm 来全局安装它&#xff1a; npm install -g vsce发布扩展到微软的应用市场 VS Code 的应用市场基于微软自己的 Azure DevOps。要发布…

计算机网络--tcpdump和iptable设置、内核参数优化策略

tcpdump工具 tcpdump命令&#xff1a; 选项字段&#xff1a; 过滤表达式&#xff1a; 实用命令&#xff1a; TCP三次握手抓包命令&#xff1a; #客户端执行tcpdump 抓取数据包 tcpdump -i etho tcp and host 192.168.12.36 and port 80 -W timeout.pcapnetstat命令 netst…

element el-table实现表格动态增加/删除/编辑表格行,带校验规则

本篇文章记录el-table增加一行可编辑的数据列&#xff0c;进行增删改。 1.增加空白行 直接在页面mounted时对form里面的table列表增加一行数据&#xff0c;直接使用push() 方法增加一列数据这个时候也可以设置一些默认值。比如案例里面的 产品件数 。 mounted() {this.$nextTi…

[嵌入式 C 语言] 按位与、或、取反、异或

若协议中如下图所示&#xff1a; 注意&#xff1a; 长度为1&#xff0c;表示1个字节&#xff0c;也就是0xFF&#xff0c;也就是 1111 1111 &#xff08;这里0xFF只是单纯表示一个数&#xff0c;也可以是其他数&#xff0c;这里需要注意的是1个字节的意思&#xff09; 一、按位…

URI:URL、URN

名称解释&#xff1a; URI:统一资源标识符&#xff1b; URL:统一资源定位符; URN:统一资源命名符&#xff1b; URI、URL、URN关系 URI是URL和URN的超集,也就是说URI有两种方式&#xff0c;一种是URL一种是URN,不过URL的方式用的比较多。 看了一个视频&#xff0c;博主解释非…

xcode配置swift使用自定义主题颜色或者使用RGB或者HEX颜色

要想在xcode中使用自定义颜色或者配置主题色&#xff0c;需要在Assets中配置&#xff0c;打开Assets文件&#xff0c;然后点击添加Color Set&#xff1a; 输入颜色的名称&#xff0c;然后选中这个颜色&#xff0c;会出现两个颜色&#xff1a; Any Appearance表示亮色模式下使用…