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.rhkb.cn/news/194908.html

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

相关文章

CSS---关于font文本属性设置样式总结

目录 1、color属性 2、font-size属性 3、font-weight属性 4、font-family属性 5、text-align属性 6、line-height属性 7、text-indent属性 8、letter-spacing属性 9、word-spacing属性 10、word-break属性 11、white-space属性 12、text-transform 12、writing-mo…

【开源】基于Vue.js的社区买菜系统的设计和实现

项目编号&#xff1a; S 011 &#xff0c;文末获取源码。 \color{red}{项目编号&#xff1a;S011&#xff0c;文末获取源码。} 项目编号&#xff1a;S011&#xff0c;文末获取源码。 目录 一、摘要1.1 项目介绍1.2 项目录屏 二、系统设计2.1 功能模块设计2.1.1 数据中心模块2.1…

IgH Master环境搭建

目标&#xff1a;实时linux内核中运行IgH主站&#xff0c;ethercat的用户态工具能看到主站信息。 一、需要的软件 1&#xff0c;vbox虚拟机 https://www.virtualbox.org/&#xff0c;下载VirtualBox 7.0, 备注&#xff1a;windows中不要用wsl开发&#xff0c;wsl运行unbuntu…

Accelerate 0.24.0文档 三:超大模型推理(内存估算、Sharded checkpoints、bitsandbytes量化、分布式推理)

文章目录 一、内存估算1.1 Gradio Demos1.2 The Command 二、使用Accelerate加载超大模型2.1 模型加载的常规流程2.2 加载空模型2.3 分片检查点&#xff08;Sharded checkpoints&#xff09;2.4 示例&#xff1a;使用Accelerate推理GPT2-1.5B2.5 device_map 三、bitsandbytes量…

内网Jenkins 部署.net(dotnet)项目

一、前置条件 内网部署Jenkins&#xff0c;并安装好所需插件 此篇内容需承接内网搭建Jenkins自动化远程部署项目到Windows服务器_jenkins内网安装-CSDN博客 &#xff0c;才更好操作与理解 二、在Jenkins中创建项目 三、配置项目 General Source Code Management Build Envi…

iddata函数合成的数据中采样间隔时间的两种设置方式及其程序举例

iddata函数生成的数据中采样间隔时间的两种设置方式及其程序举例 在对iddata合成的结构体数据&#xff0c;对采样间隔时间的设置可以通过两种方式实现&#xff0c;本文简要讲解其设置方式。 在设置采样间隔时候&#xff0c;会用到属性“ts”。 (1)对于单次实验&#xff0c;ts…

Python语言 range()函数用方法

range() 是Python的一个内置函数&#xff0c;返回的是一个可迭代对象。用于创建数字序列。 语法格式&#xff1a; range(start, stop, step) 即&#xff1a; range(初值, 终值, 步长) range()函数中使用一个参数&#xff1a; 比如&#xff1a;range(6) 返回从0到6&#x…

Java实现俄罗斯方块游戏

俄罗斯方块游戏本身的逻辑&#xff1a; 俄罗斯方块游戏的逻辑是比较简单的。它就类似于堆砌房子一样&#xff0c;各种各样的方地形状是不同的。但是&#xff0c;俄罗斯方块游戏的界面被等均的分为若干行和若干列&#xff0c;因此方块的本质就是占用了多少个单元。 首先来考虑…

文本格式清理工具 TextSoap mac中文版软件特色

TextSoap mac是一款文本格式清理工具。TextSoap可以帮助用户清除掉text文档内的文字格式&#xff0c;还可以将文档内的url转换成超链接&#xff0c;简单方便&#xff0c;是你日常办公不可缺少的工具。 TextSoap for mac软件特色 1、清洁界面 2、集成文本编辑器 3、100多个内…

FPGA模块——IIC协议(FPGA做主机操作24C64)

FPGA模块——IIC协议&#xff08;FPGA做主机操作24C64&#xff09; EEPROM&#xff08;24C64&#xff09;向器件写数据时序向器件读数据时序 IIC协议FPGA主机代码IIC读寄存器驱动&#xff08;指定地址单次读写&#xff09;使用 IIC模块 EEPROM&#xff08;24C64&#xff09; 掉…

Linux基础命令(2)

现在class03下面有这些 用ls -R看到test里面也是有东西的&#xff0c;也就是test目录文件非空 那么现在在03下面mkdir建一个空的目录文件tes&#xff0c;刚建好里面还什么都没有放 那么想要删除操作的话——要用什么命令 1.rmdir&#xff1a;用来删除空的目录文件的命令 删除刚…

K8s 命令行

前言&#xff1a;关于k8s 与 docker Docker和Kubernetes&#xff08;通常简称为K8s&#xff09;是两个在容器化应用程序方面非常流行的开源工具。 Docker: Docker 是一种轻量级的容器化平台&#xff0c;允许开发者将应用程序及其所有依赖项打包到一个称为容器的可移植容器中…

VIM去掉utf-8 bom头

Windows系统的txt文件在使用utf-8编码保存时会默认在文件开头插入三个不可见的字符&#xff08;0xEF 0xBB 0xBF&#xff09;称为BOM头 BOM头文件 0.加上BOM标记&#xff1a; :set bomb 1.查询当前UTF-8编码的文件是否有BOM标记&#xff1a; :set bomb? :set bomb? 2.BOM头:文…

CentOs 7 PHP安装和配置

目录 1 安装epel源 2 安装REMI源 3 安装yum源管理工具 4 安装PHP7.3 5 启动php服务 6 设置PHP 6.1 查找安装包 6.2 查找PHP安装位置 6.3 查找php配置文件位置 6.4 配置PHP 6.5 设置快捷命令 6.6 查看php版本 6.7 更新php 1 安装epel源 yum -y install epel-release 2 安…

深度学习之基于YoloV5苹果新鲜程度检测识别系统

欢迎大家点赞、收藏、关注、评论啦 &#xff0c;由于篇幅有限&#xff0c;只展示了部分核心代码。 文章目录 一项目简介 深度学习之基于 YOLOv5 苹果新鲜程度检测识别系统介绍YOLOv5 简介苹果新鲜程度检测系统系统架构应用场景 二、功能三、系统四. 总结 一项目简介 深度学习之…

OSPF开放最短路径优先(Open Shortest Path First)协议

OSPF开放最短路径优先(Open Shortest Path First)协议 为克服RIP的缺点(限制网络规模&#xff0c;坏消息传得慢)在1989年开发出来的原理很简单&#xff0c;但实现很复杂使用了Dijkstra提出的最短路径算法SPF(Shortest Path First)采用分布式的链路状态协议(link state protoco…

Windows Server 2012 R2系统服务器远程桌面服务多用户登录配置分享

Windows Server 2012系统在没有安装远程多界面的情况下&#xff0c;最多只能同时运行2个远程桌面&#xff0c;如果是有多个技术员、合伙人同时操作或是像游戏开发需要用到多界面&#xff0c;但是没有安装就很不方便&#xff0c;今天飞飞来和你们分享Windows server 2012R2系统远…

泛型编程 -- 模板详解

一、模板 在没有模板之前&#xff0c;如果我们写一个swap()两数交换函数&#xff0c;因为我们要支持 int 与int 交换 、double 与 double 交换等等情况&#xff0c;所以要实现swap()函数的多个重载&#xff0c;显得很繁琐&#xff0c;于是就引入了模板。 模板就是在需要模板的地…

Python基础入门----如何通过conda搭建Python开发环境

文章目录 使用 conda 搭建Python开发环境是非常方便的,它可以帮助你管理Python版本、依赖库、虚拟环境等。以下是一个简单的步骤,演示如何通过 conda 搭建Python开发环境: 安装conda: 如果你还没有安装 conda,首先需要安装Anaconda或Miniconda。Anaconda是一个包含很多数据…

初学UE5 C++②

目录 导入csv表格数据 创建、实例化、结构体 GameInstance Actor camera 绑定滚轮控制摇臂移动 碰撞绑定 角色碰撞设定 按钮 UI显示 单播代理 多播和动态多播 写一个接口 其他 NewObject 和 CreateDefaultSubobject区别 导入csv表格数据 创建一个object的C类 …