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 事件中更新这个参数就可以动起来啦,注意,这个角度是弧度。

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

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

相关文章

基于缎蓝园丁鸟算法优化概率神经网络PNN的分类预测 - 附代码

基于缎蓝园丁鸟算法优化概率神经网络PNN的分类预测 - 附代码 文章目录 基于缎蓝园丁鸟算法优化概率神经网络PNN的分类预测 - 附代码1.PNN网络概述2.变压器故障诊街系统相关背景2.1 模型建立 3.基于缎蓝园丁鸟优化的PNN网络5.测试结果6.参考文献7.Matlab代码 摘要&#xff1a;针…

[CANN训练营]UART通信笔记

文章目录 前言一、前提知识1.串行通信2.并行通信3.单工、半双工、全双工通信3.1单工通信3.2半双工通信3.3全双工通信 4.补充&#xff1a;通信速率 二、UART通信1.UART通信2.UART工作原理 总结 前言 在ROS学习中&#xff0c;我们在入门基础除了ROS的小乌龟外&#xff0c;在通信…

【云栖2023】林伟:大数据AI一体化的解读

本文根据2023云栖大会演讲实录整理而成&#xff0c;演讲信息如下&#xff1a; 演讲人&#xff1a;林伟 | 阿里云研究员&#xff0c;阿里云计算平台事业部首席架构师&#xff0c;阿里云人工智能平台PAI和大数据开发治理平台DataWorks负责人 演讲主题&#xff1a;大数据AI一体化…

UMI4 AntDesignProV5 如何修改favicon 及放置的位置(AntDesignProV5 V4对比)

项目场景&#xff1a; 修改系统的favicon.ico AntDesignProV4 修改方式 因为在V4版本中&#xff0c;有根目录文件&#xff0c;可在文件中直接指定&#xff1a; <!-- document.ejs --><head><link rel"icon" type"image/x-icon" href&quo…

一篇揭秘Linux高性能服务epoll 的本质

导语 epoll接口是为解决Linux内核处理大量文件描述符而提出的方案。该接口属于Linux下多路I/O复用接口中select/poll的增强。其经常应用于Linux下高并发服务型程序&#xff0c;特别是在大量并发连接中只有少部分连接处于活跃下的情况 (通常是这种情况)&#xff0c;在该情况下能…

卫星位置解算

武大GPS原理及应用 1.广播星历&#xff08;预报星历&#xff09; 预报星历所得的轨道精度有限&#xff0c;精度在2m左右。 2.精密星历 P 、卫星PRN、卫星在地心地固坐标系坐标&#xff08;与wgs84有点差别&#xff09;、卫星钟差。 通过内插或者拟合来获取任意时刻的卫星位…

python---数据库操作

python的错误和异常 异常&#xff1a; 运行期检测到的错误被称为异常。 try语句按照如下方式工作&#xff1a; 首先&#xff0c;执行try子句&#xff08;在关键字try和关键字except之间的语句&#xff09; 如果没有异常发生&#xff0c;忽略except子句&#xff0c;try子句执…

Federated Social Recommendation with Graph Neural Network

基于图神经网络的联合社交推荐 ACM-TIST CCF_B类 论文链接 代码地址 模型中梯度和embedding的聚合 在FeSog中&#xff0c;Server端维护一个整体的model&#xff0c;由于这里的model层网络和GraphAttentionLayer层网络中一共有10个要更新参数&#xff0c;所以当每次server端将…

对测试职业发展的思考

虽然在测试行业摸爬滚打了很年&#xff0c;随着年龄的增长&#xff0c;职位的升迁&#xff0c;似乎已经走到了尽头&#xff0c;因而还是时不时觉得自己的职业发展目标很模糊&#xff0c;这是最近对自己职业发展的一些思考&#xff0c;希望与大家进行分享和探讨&#xff1a; 1、…

3、Linux库的生成和使用(核心代码是程序员不可公开的小秘密)

目录 Linux库的概念 Linux 静态库 Linux 静态库作用 Linux 静态库的创建 1. 将.c文件生成.o文件 ​编辑 2. 将所有的.o文件归档为一个静态库.a文件 Linux 静态库的使用 Linux 动态库&#xff1a; Linux 动态库作用 Linux 动态库的创建 生成.so动态库文件 ​编辑 …

curl(八)时间和环境变量以及配置

一 时间 ① --connect-timeout 连接超时时间 ② -m | --max-time 数据最大传输时间 -m&#xff1a; 限制curl 完成时间(overall time limit)-m,--max-time <seconds> 整个交互完成的超时时间场景&#xff1a; 通过设置-m参数,可以避免请求时间过长而导致的超时错误…

jstack java堆栈跟踪工具

jstack java堆栈跟踪工具 1、jstack介绍 jstack&#xff08;stack trace for java&#xff09;是java虚拟机自带的一种堆栈跟踪工具。 jstack主要用于生成java虚拟机当前时刻的线程快照&#xff0c;线程快照是当前java虚拟机内每一条线程正在执行的方法 堆栈的集合&#xf…

MacOS Ventura 13 优化配置(ARM架构新手向导)

一、系统配置 1、About My MacBook Pro 2、在当前标签打开新窗口 桌面上创建目录的文件夹&#xff0c;每次新打开一个目录&#xff0c;就会创建一个窗口&#xff0c;这就造成窗口太多&#xff0c;不太好查看和管理&#xff0c;我们可以改成在新标签处打开新目录。需要在&…

数字滤波器设计---IIR 滤波器设计

数字滤波器设计---IIR 滤波器设计 IIR 与 FIR 滤波器的比较 与 FIR 滤波器相比&#xff0c;IIR 滤波器的主要优点是&#xff0c;要满足同一组设定&#xff0c;它的滤波器阶数通常远远低于 FIR 滤波器。虽然 IIR 滤波器具有非线性相位&#xff0c;但 MATLAB 软件中的数据处理通…

【数据分享】我国雏鹰企业数据(excel格式\shp格式)

企业是经济活动的参与主体。一个城市的企业数量决定了这个城市的经济发展水平&#xff01;比如一个城市的金融企业较多&#xff0c;那这个城市的金融产业肯定比较发达&#xff1b;一个城市的制造业企业较多&#xff0c;那这个城市的制造业肯定比较发达。 本次我们为大家带来的…

jumpserver任意密码重置漏洞-CVE-2023-42820

目录 jumpserver 环境搭建 这里用的是vulhub靶场 进入 jumpserver 的目录 修改配置文件 config.env 里面的 DOMAINS 参数为kali的地址 运行环境&#xff0c;第一次运行的话会拉取文件&#xff0c;要耐心等待。 命令&#xff1a; 查看docker容器 命令&#xff1a; 用浏…

【分享】Excel“只读方式”的两种模式

查阅Excel表格的时候&#xff0c;担心不小心修改了内容&#xff0c;可以给Excel设置以“只读方式”打开&#xff0c;这样就算修改了内容也不能直接保存表格。Excel表格可以设置两种“只读方式”&#xff0c;一起来看看吧&#xff01; “只读方式” 1&#xff1a; 打开Excel表…

找工作什么平台最可靠

吉鹿力招聘网是最可靠的找工作平台。可以直接和HR沟通岗位情况&#xff0c;方便快捷。同时&#xff0c;吉鹿力招聘网还有一些其他功能&#xff0c;比如可以找到更精准的人才&#xff0c;以及专业的招聘网站&#xff0c;可以帮助求职者找到合适的职位。吉鹿力招聘网还有一个特点…

【Linux进程】进程控制

目录 一、进程创建 1.2 fork函数初识 1.2 fork函数返回值 1.3 写时拷贝 1.4 fork常规用法 1.5 fork调用失败的原因 二、进程终止 2.1 进程退出场景 2.2 进程退出码 2.2.1 用strerror打印错误信息 2.2.2 errno全局变量 2.3 进程常见退出方法 2.3.1 进程正常退出 2…

Python高级语法----Python的元编程

文章目录 装饰器元类反射使用 `__getattr__`, `__setattr__`, 和 `__delattr__`元编程是一种编程技术,它允许程序员在运行时修改、增加或操作程序的结构。在Python中,元编程通常涉及到对类和函数的动态创建和修改,这是通过使用诸如装饰器、元类和反射等高级功能来实现的。 …