Cesium深入浅出之自定义材质

引子

做为一名技术宅却没有能拿得出手的技术无疑是最可悲的事情。三年前,当我第一次接触Cesium的时候就被它强大和炫丽所折服,最关键的是它还是开源的。以前我一直是机械地敲着业务代码,好像计算机程序就只能干这点事情一样,而 Cesium 就像打开了一扇窗,原来里面的世界如此精彩,原来计算机程序还可以变幻出如此多的花样来。于是,我好像找到了人生方向一般,如饥似渴地踏入了 Cesium 的殿堂。可能是因为以前从未接触过图形学,让我觉得 Cesium 里面涉及的知识都那么高深,那么玄奥,同时学习过程也是那么痛苦。然心向往之,故痛并快乐着。差不多半年有余,对 Cesium 算是初窥门径了,搭上一个框架,堆上一堆功能花架子,在当年也算很不错了。然而,Cesium 的核心技术是图形学,可以说想真正把 Cesium 学好,图形学是必经之路。如果Cesium是修真体系,那么图形学就是功法,而且是高阶功法,想入门都很困难,更遑论小成、大成,圆满更是不敢想象,这也是很多人被挡在门外或只能浅尝辄止的缘故。也是在那时萌生了深入浅出系列的想法,本意就是想大道化简,人人都得以修炼,于是我便开启了深入浅出之路。要说一开始还是挺顺利的,出产了几篇勉强过得去的修炼心得。可是后来因为陷入项目导致这条路中断了,长达两年之久。以至于后来与人交流的时候都明显感觉自己的技术跟不上了,毕竟在你懈怠的时候别人还在努力修炼。果然修炼一途如逆水行舟,不进则退。意识到自己再也不能这样下去了,所以再次捡起修炼。如果按修真的等级划分的话,我现在最多只能算练气期九层,甚至还没筑基!用仙界的话说:筑基之下皆是蝼蚁!艾玛,太恐怖了!不废话了,开始我们的筑基之路。

预期效果

今天要讲的是自定义材质,篇幅可能会很长,很有可能一次更新不完,因为想写的东西太多了,一次能更多少是多少吧。Cesium 的材质我们都很熟了,毕竟从入门开始就使用到材质。只不过通常我们使用的是 Cesium 自带的材质,当我们想实现一个稍微复杂点效果时自带的材质库就不够用了,这时候就该自定义材质登场了。我记得刚接触 Cesium 那会想做一个雷达扫描的效果,自己啥也不会,于是就到网上搜罗了一些案例。有的是使用 Entity 实现的,思路很简单,先在地图上画一个圆,然后贴上预先绘制好的一张静态的雷达图片做为材质,通过 CallbackProperty 的方式让图形旋转起来,这方法的弊端是需要图片资源,不够灵活。还有一种思路是用 PostProcessStage 实现的,这种方式在当时看起来就很高大上,不需要引入图片资源,而且还涉及到了当时的知识盲区 Shader,一堆glsl语言直接让我放弃研究,当起了大自然的搬运工,不过这种方式实现的有瑕疵,就是距离太近的时候雷达出现断层,想要优化也无能为力,只得放弃。其实现在想来,这个效果其实及其简单,只需一个自定义材质就能搞定。

实现原理

照例我们还是先了解一下 Cesium 的材质结构,上API。

构造函数

new Cesium.Material(options)

原文:A Material defines surface appearance through a combination of diffuse, specular, normal, emission, and alpha components. These values are specified using a JSON schema called Fabric which gets parsed and assembled into glsl shader code behind-the-scenes.

翻译:材质是用来定义一个表面外观的,主要是通过漫反射、镜面反射、 法线、自发光和 Alpha  这些分量。这些值是使用 Fabric 方式以 JSON 格式进行赋值的,它们在后台它被解析并组装成 glsl 着色器代码。

选项参数

名字类型描述
options对象可选 具有以下属性的对象:
名字类型默认值描述
strictbooleanfalse可选 严格模式。这种模式下,通常会被忽略的问题(如未使用的 uniforms 或  materials)将会引发错误。
translucentboolean | functiontrue可选 当值为 ture 或函数的返回值为 true 的时候 ,几何图形的材质半透明。
minificationFilterTetureMinificationFilterTextureMinificationFilter.LINEAR可选 纹理缩小筛选器。
magnificationFilterTetureMagnificationFilterTextureMagnificationFilter.LINEAR可选 纹理放大筛选器。
fabricObject用于生成材质的JSON。

成员

materials : object

Maps sub-material names to Material objects.

将子材质名称映射到材质对象。

shaderSource : string

The glsl shader source for this material.

此材质的glsl着色器源码。

translucent : boolean|function

When true or a function that returns true, the geometry is expected to appear translucent.

材质半透明。

type : string

The material type. Can be an existing type or a new type. If no type is specified in fabric, type is a GUID.

材质类型。可以是已存的类型也可以是新类型。如果 fabric 中未指定 type 值,则 type 值未一个 GUID 值。

uniforms : object

Maps uniform names to their values.

统一名称和值的映射。

方法

static Cesium.Material.fromType(type, uniforms) → Material

静态方法,根据类型获取材质。这里获取的是 Cesium 自带的材质,不过如果你将自定义的材质注册到 Cesium,理论上也是能获取到的。

destroy() → void

销毁材质。

isDestroyed() → boolean

返回材质是否已被销毁。如果一个材质可能被销毁了,那么调用材质之前最好先调用这个方法,否则会抛出异常。

isTranslucent() → boolean

返回材质是否为半透明。

以上就是 Material 的基本 API 了,还有一些自带材质类型的静态成员我就不列出来了,毕竟我们今天讲的是自定义材质。

网上看到有很多人写自定义类的时候喜欢二次改造,就是把 Cesium 某个类的源码拷贝过来,然后在上面一顿操作猛如虎,改造完之后连亲妈都不认得了,毕竟 Cesium 底层还是 ES5 时代的代码,那成品一出来我只能说是惨不忍睹。所以我们要善用 ES6 “类”的继承,这会让你的代码看起来更简洁,更易懂。来,上示例!

import {Color, Material} from 'cesium';/*** 雷达扫描材质。** @class* @author Helsing* @since 2023/11/10*/
export class RadarScanCircleMaterial extends Material {/*** 构造雷达扫描材质。** @param options 选项。* @constructor*/constructor(options={}) {const newOptions = {fabric: {type: options.type || 'RadarScanCircle',uniforms: options.uniforms || {radians: options.radians ?? 0.00,color: options.color || new Color(0, 1, 1),sectorColor: options.sectorColor || new Color(0, 1, 1),time: options.time ?? 0.00,count: options.count ?? 5.0,gradient: options.gradient ?? 0.01,},source: options.shaderSource ?? '这里是你要写glsl的地方',},translucent: options.translucent ?? true,};super(newOptions);}
}

看吧,简简单单几行代码就完成了自定义材质的框架,接下来你只需要关心 shaderSource 的代码了。我们程序员应该向雷布斯学习,写代码要像写诗一样优雅,别人舒心,自己也舒心。下面来看看今天的正主——shaderSource

shaderSource 即材质着色器,我们前面的文章有讲过顶点着色器和片元着色器,它们都是使用相同的语言 glsl。虽然前面也曾接着某个功能详细讲解过着色器代码,但总觉得不够浅,不够浅就无法深入,不符合深入浅出的理念,所以今天要从幼儿园讲起。

一个最简单的材质实现:

uniform vec4 color;czm_material czm_getMaterial(czm_materialInput materialInput)
{czm_material material = czm_getDefaultMaterial(materialInput);material.diffuse = color.rgb;material.alpha = 0.5;return material;
}

上面代码实现了一个颜色为 color 透明度为0.5的材质。

下面我们来简单复习下基础知识: 

uniform 传递了颜色变量 color,颜色值是由四通道的向量类型定义的,分别是 rgba,使用 uniform  关键字定义的变量都是通过 uniforms 从外部来传递进来的。

czm_ 是 Cesium 的专属标记,我们一看到它就知道它是系统函数或变量了,czm_getMaterial 就是 Cesium 的材质函数,我们只要实现这个函数就可以完成自定义材质的编写了。

czm_getDefaultMaterial 函数可以获取到默认的材质,那么这个默认的材质是什么样的呢?没错,是一片漆黑。这是因为材质默认的颜色是黑色,默认的透明度为1。那么我们通过修改默认材质的颜色和透明度就可以实现材质自定义了,即修改 diffuse alpha 值。其实材质的神奇之处就在于这里,简简单单两个属性可以衍生出出千变万化来。

下面我们看一下几种简单常用的材质设置。

纹理x轴方向渐变
uniform vec4 color;czm_material czm_getMaterial(czm_materialInput materialInput)
{czm_material material = czm_getDefaultMaterial(materialInput);vec2 st = materialInput.st;material.diffuse = color.rgb;material.alpha = st.s;return material;
}

st 即纹理的 xy 值,分量取值范围0-1。我们知道 alpha 取值范围也是0-1,如果我们把 材质的 alpha 值设置成 xy 分量值,会出现什么样的效果呢?如下图。

没错,我们惊喜地发现,我们居然实现了渐变的材质嘞!其实想想原理就很好解释了,纹理从左到右,数值从0到1逐渐增大,对应着透明度也从透明逐渐变为不透明。同学想一想,如果 alpha 值设置成 st.t,会呈现什么样的效果呢?

纹理y轴方向渐变
uniform vec4 color;czm_material czm_getMaterial(czm_materialInput materialInput)
{czm_material material = czm_getDefaultMaterial(materialInput);vec2 st = materialInput.st;material.diffuse = color.rgb;material.alpha = st.t;return material;
}

相信同学们都猜到了,材质呈现了从下到上的渐变效果。由此我们可以看出材质的纹理坐标是从左下角向右上角渐进的。

纹理中心向外部渐变

现在我们已经打开了自定材质的大门了,上面我们一不小心就实现了纹理渐变,不过都是从一侧到另一侧渐变的,如果我们想从中心往四周渐变又该如何做呢?说到这个问题,我们已经能联想到那些扫描特效了,几何形状都是圆形的,从中心往四周扩散的材质无疑比较适合圆形。

uniform vec4 color;czm_material czm_getMaterial(czm_materialInput materialInput)
{czm_material material = czm_getDefaultMaterial(materialInput);vec2 st = materialInput.st;float dis = distance(st, vec2(0.5));material.diffuse = color.rgb;material.alpha = dis;return material;
}

首先我们要确定一下纹理的中心坐标,左下角坐标是(0.0, 0.0),右上角坐标是(1.0, 1.0),那么中心坐标就是(0.5, 0.5)。中心点的 alpha 值设为0,以中心点为圆心,以0.5为半径,绘制一个圆,在这个圆面上的点,距离中心点越远值越大。因此我们就由了解决方案了,就是计算中心点的距离,使用到的函数是 distance(),里面传入起始点参数即可计算出距离。

看上图发现最外圈边缘颜色比较浅,与预想的不太一样。细想一下上面的圆半径是0.5,所以最大值也只能到0.5。那么我们只要将 dis 乘以2即可,得到效果如下图。

在职在矩形上的效果如下图。

纹理外部向中心渐变

这个很好理解,只须将上面的结果反转一下即可,即用1减去距离。

uniform vec4 color;czm_material czm_getMaterial(czm_materialInput materialInput)
{czm_material material = czm_getDefaultMaterial(materialInput);vec2 st = materialInput.st;float dis = distance(st, vec2(0.5));material.diffuse = color.rgb;material.alpha = 1.0 - dis * 2.0;return material;
}

 

查看效果图发现,材质在圆形上很完美,但在矩形上出现了奇怪的现象,这白边是哪里来的呢?仔细一想,在矩形材质中最大距离并不是0.5,而是\frac{\sqrt{2}}{2},约等于0.707,这个通过简单的三角函数就能知道了。上述代码出现了负值,所以导致了白边的出现。这时候我们可以使用clamp函数来解决,即当值大于0.707的时候,我们就将数值置为0。

clamp(x, min, max) 函数:返回 min 和 max 之间的值,如果超出范围,则如果 x < min 返回 min,如果 x > max 返回 max。

uniform vec4 color;czm_material czm_getMaterial(czm_materialInput materialInput)
{czm_material material = czm_getDefaultMaterial(materialInput);vec2 st = materialInput.st;float dis = distance(st, vec2(0.5));material.diffuse = color.rgb;material.alpha = clamp(1.0 - dis * 2.0, 0.0, 1.0);return material;
}

我们来看一下所有材质的效果。

具体实现 

上面讲了自定义材质的原理,其实是已经将自定义材质的具体实现过程都完成了。不过我们开篇的时候提到的要实现雷达扫描的效果的,这个就稍微复杂些了,不过万变不离其宗,我相信只要你看懂了上面的内容,接下来的内容你将毫不费力。

添加圆形

这个算最基础的添加图元操作了,我完全可以省略掉这部分,但本着完整的原则,还是放上来了。直接上代码。

const position = Cartesian3.fromDegrees(80,40);
const modelMatrix = Transforms.eastNorthUpToFixedFrame(position);
const radius = 40000.0;
viewer.scene.primitives.add({geometryInstances:[new GeometryInstance({geometry: new EllipseGeometry({center: position,semiMajorAxis: radius,semiMinorAxis: radius,})})],appearance: new MaterialAppearance({material: new RadarScanCircleMaterial({color: 'rgb(0,255,50)',sectorColor: 'rgb(0,255,50)',radians: Math.PI * 3 / 8,offset: 0.2}),flat: false,faceForward: false,translucent: true,closed: false}),asynchronous: false,modelMatrix: modelMatrix
});

添加雷达扫描材质

通过上面的学习,现在我们已经会创建自定义材质了。首先要做的是创建一个雷达扫描材质类,然后加入下面的着色器代码即可。

uniform vec4 color;
uniform vec4 sectorColor;
uniform float width;
uniform float radians;
uniform float offset;czm_material czm_getMaterial(czm_materialInput materialInput)
{czm_material material = czm_getDefaultMaterial(materialInput);vec2 st = materialInput.st;float dis = distance(st, vec2(0.5));float sp = 1.0 / 5.0 / 2.0;float m = mod(dis, sp);float alpha = step(sp * (1.0 - width * 10.0), m);alpha = clamp(alpha, 0.2, 1.0);material.alpha = alpha;material.diffuse = color.rgb;// 绘制十字线if ((st.s > 0.5 - width / 2.0 && st.s < 0.5 + width / 2.0) || (st.t > 0.5 - width / 2.0 && st.t < 0.5 + width / 2.0)) {alpha = 1.0;material.diffuse = color.rgb;material.alpha = alpha;}// 绘制光晕float ma = mod(dis + offset, 0.5);if (ma < 0.25){alpha = ma * 3.0 + alpha;} else{alpha = 3.0 * (0.5 - ma) + alpha;}                           material.alpha = alpha;material.diffuse = sectorColor.rgb;// 绘制扇区vec2 xy = materialInput.st;float rx = xy.x - 0.5;float ry = xy.y - 0.5;float at = atan(ry, rx);// 半径float radius = sqrt(rx * rx + ry * ry);// 扇区叠加旋转角度float current_radians = at + radians;xy = vec2(cos(current_radians) * radius, sin(current_radians) * radius);xy = vec2(xy.x + 0.5, xy.y + 0.5);// 扇区渐变色渲染if (xy.y - xy.x < 0.0 && xy.x > 0.5 && xy.y > 0.5){material.alpha = alpha + 0.2;material.diffuse = sectorColor.rgb;}return material;
}

 我感觉上面的代码注释已经很详细了,结合我前面讲的材质纹理原理,应该非常容易理解。好了,让我们看看最终的效果吧。

小结 

总算是写完啦,简简单单的一篇文章花了我两天时间,效率低到姥姥家了。主要是期间把之前的 SimpleCesium 捡起来了,文章案例都是在上面完成的。三年未更新了,GitHub上面下载下来竟然跑不起来了,要说Cesium也真是可以,新版本改了引用路径之后,连老版本也受影响,索性直接升到最新版本了,一番升级改造算是勉强能看了。看了文章还不会用的同学可以去看看,再不行您就进组织呗,暗号:854943530。

PS

雷达效果是做出来了,要让里面的扇叶转起来才酷嘛。其实很简单,预留参数 radians 就是干这个的,在 preRender 事件中更新这个参数就可以动起来啦,注意,这个角度是弧度。

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

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

相关文章

Qt文档阅读笔记-Fetch More Example解析

Fetch More Example这个例子说明了如何在视图模型上添加记录。 这个例子由一个对话框组成&#xff0c;在Directory的输入框中&#xff0c;可输入路径信息。应用程序会载入路径信息的文件信息等。不需要按回车键就能搜索。 当有大量数据时&#xff0c;需要对视图模型进行批量增…

Git忽略文件.gitignore的使用

1.为什么使用? 当你使用git add .的时候有没有遇到把你不想提交的文件也添加到了缓存中去&#xff1f;比如项目的本地配置信息&#xff0c;如果你上传到Git中去其他人pull下来的时候就会和他本地的配置有冲突&#xff0c;所以这样的个性化配置文件我们一般不把它推送到git服务…

数据库 并发控制

多用户数据库系统&#xff1a;允许多个用户同时使用同一个数据库的数据库系统 交叉并发方式&#xff1a;在单处理机系统中&#xff0c;事务的并行执行实际上是这些并行事务的并行操作轮流交叉运行 同时并发方式&#xff1a;在多处理机系统中&#xff0c;每个处理机可以运行一个…

Java设计模式-结构型模式-代理模式

代理模式 代理模式静态代理动态代理JDK动态代理CGlib动态代理 代理模式 创建一个代理对象来控制对原始对象的访问&#xff0c;可以用来扩展原始对象的功能&#xff0c;同时保护原始对象 一般使用代理模式的目的有两个&#xff1a; 保护目标对象增强目标对象 代理模式有两种实现…

【OpenCV实现图像:用OpenCV图像处理技巧之巧用直方图】

文章目录 概要前置条件统计数据分析直方图均衡化原理小结 概要 图像处理是计算机视觉领域中的重要组成部分&#xff0c;而直方图在图像处理中扮演着关键的角色。如何巧妙地运用OpenCV库中的图像处理技巧&#xff0c;特别是直方图相关的方法&#xff0c;来提高图像质量、改善细…

stm32超声波测距不准的解决方法(STM32 delay_us()产生1us)及stm32智能小车超声波测距代码(C语言版本)

首先要说明一下原理&#xff1a;使用stm32无法准确产生1us的时间&#xff0c;但是超声波测距一定要依赖时间&#xff0c;时间不准&#xff0c;距离一定不准&#xff0c;这是要肯定的&#xff0c;但是在不准确的情况下&#xff0c;要测量一个比较准确的时间&#xff0c;那么只能…

同一个Unity项目打开两个Unity Editor实例

特殊情况下&#xff0c;同一个项目需要同时打开两个编辑器做测试&#xff0c;如多人在线游戏&#xff0c;或者有通信功能的时候就有这样的需求。同时也为了方便调试和观察日志。并且修改的是同一份代码。 命令介绍&#xff1a; 实现思路&#xff1a; 使用 mklink 命令 分别创建…

使用 huggingface_hub 镜像下载 大模型

download.py &#x1f447; import os # 配置 hf镜像 os.environ[HF_ENDPOINT] https://hf-mirror.com# 设置保存的路径 local_dir "XXXXXX"# 设置仓库id model_id "sensenova/piccolo-large-zh"cmd f"huggingface-cli download --resume-downlo…

【MySQL】库的相关操作 + 库的备份和还原

库的操作 前言正式开始创建数据库删除数据库编码集查看系统默认字符集以及校验规则字符集校验规则 所有支持的字符集和校验规则所有字符集所有校验规则 指明字符集和校验规则创建数据库相同的字符集用不同的校验规则读取会出现什么情况 alter修改数据库show create databasealt…

瑞萨e2studio(29)----SPI速率解析

瑞萨e2studio.29--SPI速率解析 概述视频教学时钟配置解析RA4M2的BRR值时钟速率7.5M下寄存器值3K下寄存器值 概述 在嵌入式系统的设计中&#xff0c;串行外设接口&#xff08;SPI&#xff09;的通信速率是一个关键参数&#xff0c;它直接影响到系统的性能和稳定性。瑞萨电子的…

C# Onnx LSTR 基于Transformer的端到端实时车道线检测

目录 效果 模型信息 项目 代码 下载 效果 模型信息 lstr_360x640.onnx Inputs ------------------------- name&#xff1a;input_rgb tensor&#xff1a;Float[1, 3, 360, 640] name&#xff1a;input_mask tensor&#xff1a;Float[1, 1, 360, 640] -----------------…

适配器模式 rust和java的实现

文章目录 适配器模式介绍何时使用应用实例优点缺点使用场景 实现java实现rust 实现 rust代码仓库 适配器模式 适配器模式&#xff08;Adapter Pattern&#xff09;是作为两个不兼容的接口之间的桥梁。这种类型的设计模式属于结构型模式&#xff0c;它结合了两个独立接口的功能…

【每日一题】区域和检索 - 数组可修改

文章目录 Tag题目来源解题思路方法一&#xff1a;分块方法二&#xff1a;线段树方法三&#xff1a;树状数组 写在最后 Tag 【树状数组】【线段树】【分块】【前缀和】【设计类】【2023-11-13】 题目来源 307. 区域和检索 - 数组可修改 解题思路 使用前缀和解决不行吗&#x…

centos利用find提权反弹shell

需要说明的是利用find命令进行提权的方式已经不存在了&#xff0c;因为Linux默认不会为find命令授予suid权限&#xff0c;这里只是刻意的制造出了一种存在提权的环境 首先我们先介绍一下find命令&#xff0c;find命令主要用来在Linux中查找文件使用&#xff0c;它可以进行最基础…

Brute Force

Brute Force "Brute Force"&#xff08;暴力破解&#xff09;指的是一种通过尝试所有可能的组合来获取访问、解密或破解信息的攻击方法。这种攻击方法通常是基于暴力和不断尝试的&#xff0c;不依赖漏洞或弱点。通常用于破解密码、破坏系统或获取未经授权的访问权限…

数据分析实战 | 逻辑回归——病例自动诊断分析

目录 一、数据及分析对象 二、目的及分析任务 三、方法及工具 四、数据读入 五、数据理解 六、数据准备 七、模型训练 八、模型评价 九、模型调参 十、模型预测 一、数据及分析对象 CSV文件——“bc_data.csv” 数据集链接&#xff1a;https://download.csdn.net/d…

MongoDB基础知识~

引入MongoDB&#xff1a; 在面对高并发&#xff0c;高效率存储和访问&#xff0c;高扩展性和高可用性等的需求下&#xff0c;我们之前所学习过的关系型数据库(MySql,sql server…)显得有点力不从心&#xff0c;而这些需求在我们的生活中也是随处可见的&#xff0c;例如在社交中…

JVM如何运行,揭秘Java虚拟机运行时数据区

目录 一、概述 二、程序计数器 三、虚拟机栈 四、本地方法栈 五、本地方法接口 六、堆 &#xff08;一&#xff09;概述 &#xff08;二&#xff09;堆空间细分 七、方法区 一、概述 不同的JVM对于内存的划分方式和管理机制存在部分差异&#xff0c;后续针对HotSpot虚…

CSS特效007:绘制3D文字,类似PS效果

总第 007 篇文章&#xff0c; 查看专栏目录 本专栏记录的是经常使用的CSS示例与技巧&#xff0c;主要包含CSS布局&#xff0c;CSS特效&#xff0c;CSS花边信息三部分内容。其中CSS布局主要是列出一些常用的CSS布局信息点&#xff0c;CSS特效主要是一些动画示例&#xff0c;CSS花…

2023年数维杯国际大学生数学建模挑战赛A题

当大家面临着复杂的数学建模问题时&#xff0c;你是否曾经感到茫然无措&#xff1f;作为2022年美国大学生数学建模比赛的O奖得主&#xff0c;我为大家提供了一套优秀的解题思路&#xff0c;让你轻松应对各种难题。 cs数模团队在数维杯前为大家提供了许多资料的内容呀&#xff0…