当前位置: 首页 > news >正文

Cocos Creator 3.8 后期效果 Shader 编写(2/2) 进阶篇

前言

在上一篇文章中,麒麟子给大家分享了如何在 Cocos Creator 3.8 中的自定义管线中,添加属于自己的后期效果 Shader。 但基于 BlitScreen 的方案,我们只能编写最简单后效 Shader,如果我们想要支持更多复杂的 Shader,比如模糊、景深等等效果,就需要配合代码才能实现。

今天麒麟子就用高斯模糊来演示如何编写一个多 Pass 的后效 Shader。

准备 Shader

高斯模糊的涉及到的内容比较多,什么正态分布、高斯函数、高斯卷积核。

如果对数学没兴趣,上面这些统统不用管。

简单来说,高斯模糊就是把图片上的每一个像素都用下面的流程处理一遍。

直白来说,就是一个简单的加权求和:采样目标像素的同时,再采样一些周围的像素;并且每一个像素给一个权重(权重和为1.0)。最终像素值 = 所有(像素 x 权重)的和。

如果想要效果好,就多迭代几次,迭代次数越多,画面越好,但性能开销越高。

我们创建一个 Cocos Shader 文件,命名为 “gaussian-blur.effect” 然后编写下面的内容。

CCEffect %{techniques:- passes:- vert: blur-hor-vsfrag: blur-fspass: blur-xdepthStencilState:depthTest: falsedepthWrite: false- vert: blur-vert-vsfrag: blur-fspass: blur-ydepthStencilState:depthTest: falsedepthWrite: false
}%CCProgram blur-hor-vs %{//...
}%
CCProgram blur-hor-vs %{//...
}%CCProgram blur-fs %{//...
}%//为了方便文章阅读
//完整 Shader 被放到了文末

可以看到,整个 Cocos Shader 只有两个 Pass,一个用来水平模糊,一个用来竖直模糊。

为了不影响阅读,完整 Shader 放在了文末。

注意:Cocos Creator 3.8.0 版本如果新增了后效 Shader,需要重启编辑器才能识别。后面版本中会优化这个流程。

编写属性组件

Cocos Creator 3.8 中,只需要在节点上添加对应的后期效果组件,就可以启动对应的后期效果。

自定义后期管线提供了一个 postProcess.PostProcessingSetting 组件类,我们可以通过继承它类来实现后效参数的可视化界面配置。

通过它自定义出来的后期效果 ,是完全可以达到和内置的后期效果一样的使用体验的。

新建一个 TS 脚本文件,起名为 “GaussianBlur.ts”,然后输入以下代码。

代码片段1:

import { _decorator, gfx, postProcess, Material, EffectAsset, renderer, rendering, Vec4 } from 'cc';
const { Format  } = gfxconst { ccclass, property, menu, executeInEditMode } = _decorator;@ccclass('GaussianBlur')
@menu('PostProcess/GaussianBlur')
@executeInEditMode
export class GaussianBlur extends postProcess.PostProcessSetting{@property(EffectAsset)_effectAsset: EffectAsset | undefined@property(EffectAsset)get effect () {return this._effectAsset;}set effect (v) {this._effectAsset = v;if(this._effectAsset == null){this._material = null;}else{if(this._material == null){this._material = new Material();}this._material.reset({effectAsset:this._effectAsset});}this.updateMaterial();}@propertyiterations = 3;@propertyget blurRadius(){return this._blurParams.x;}set blurRadius(v){this._blurParams.x = v;this.updateMaterial();}private _material:Material;public get material():Material{return this._material;}@propertyprivate _blurParams:Vec4 = new Vec4(1.0,0.0,0.0,0.0);public get blurParams():Vec4{return this._blurParams;}updateMaterial(){if(!this._material){return;}this._material.setProperty('blurParams', this.blurParams);}protected start(): void {if(this._effectAsset){this._material = new Material();this._material.initialize({effectAsset:this._effectAsset});this._material.setProperty('blurParams', this.blurParams);}}
}

接下来,我们就可以通过“添加组件“按钮,把 PostProcess/GansussianBlur 添加到后处理结点上了。

可以看到,在 Inspector 面板上,它接受 3 个参数。

  • effect:用于指定用于这个后期效果的 Shader 文件
  • iterations:用于指定需要迭代多少次,迭代次数越多越模糊
  • blurRadius:采样临近像素时的偏移,偏移量越大越模糊

这个时候是没有任何效果的,因为还没有实现对应的渲染代码。

编写渲染代码

Cocos Creator 3.8 中的后处理管线是基于新版自定义管线的,而新版的自定义管线是基于 RenderGraph 架构的。

你可以简单地认为,我们想要实现的单个后处理效果,对应的就是渲染流程图上的一个节点。

后处理管线提供了 postProcess.SettingPass 给我们,用来编写自己的后处理渲染效果。(也可以叫,自定义后处理节点的资源和行为)。

接下来,我们来做真正的渲染实现。 我们需要继承自定义管线中的 postProcess.SettingPass 类,并实现并要的代码。

  • get setting:获取配置信息,对应的就是上面实现的界面组件
  • checkEnable:用于判断此后效是否开启
  • name:后效的名字,一般保持和类名一致即可
  • outputNames:最终输出的 RT 数组。(临时用的 RT 不用放在这里)
  • render:用于执行渲染流程

完整编码如下。

代码片段2:

export class GaussianBlurPass extends postProcess.SettingPass {get setting () { return this.getSetting(GaussianBlur); }checkEnable (camera: renderer.scene.Camera) {let enable = super.checkEnable(camera);if (postProcess.disablePostProcessForDebugView()) {enable = false;}return enable && this.setting.material != null;}name = 'GaussianBlurPass';outputNames = ['GaussianBlurMap'];public render (camera: renderer.scene.Camera, ppl: rendering.Pipeline): void {const setting = this.setting;if(!setting.material){return;}let passContext = this.context;passContext.material = setting.material;const cameraID = this.getCameraUniqueID(camera);const cameraName = `Camera${cameraID}`;const passViewport = passContext.passViewport;passContext.clearBlack();const format = Format.RGBA8;let input = this.lastPass!.slotName(camera, 0);for(let i = 0; i < setting.iterations; ++i){passContext.updatePassViewPort().addRenderPass(`blur-x`, `blur-x${cameraID}`).setPassInput(input, 'outputResultMap').addRasterView('GaussianBlurMap_TMP', format).blitScreen(0).version();passContext.updatePassViewPort().addRenderPass(`blur-y`, `blur-y${cameraID}`).setPassInput('GaussianBlurMap_TMP', 'outputResultMap').addRasterView(this.slotName(camera), format).blitScreen(1).version();input = this.slotName(camera);}}
}

接下来,我们主要看看 render 处理了哪些事情。

准备工作

每一个 SettingPass 就是一个绘制节点。而节点的数据,我们存在了 context 中。

render 函数会每帧执行,所以需要调用 context.clearBack() 来清理背景。

然后,我们要将材质设置给 context

模糊的处理,需要使用上一个处理流程结束后的画面内容。因此,我们使用 this.lastPass.slotName(camera,0); 来获取。

一切准备就绪后,就进入到了绘制环节。

绘制

这里,我们使用了 iterations 属性来控制总共要迭代的次数。迭代一次,绘制流程就会走一遍。

我们来看看,绘制流程中每一步操作的用途。

  • updatePassViewPort:这个函数用来指定相对分辨率大小,这个根据算法需求来指定就行。如果要保持和后台缓冲区一样大,传入 1.0 即可。

  • addRenderPass:这个函数用来告诉管线,需要执行一次绘制流程。

    • layout:对应的是 Cocos Shader 中的 Pass 名称
    • passName:助记名称,便于调试查看
  • setPassInput:如果有用到自定义管线中的 RT 资源(比如上一次执行的结果),则需要在这里指定,方便自定义管线对资源进行管理。

    • inputName: 自定义管线资源分配的资源名称
    • shaderName: 对应 Cocos Shader 中的 uniform Sampler2D 名称。
  • addRasterView:可以简单理解为,输出结果

    • name:输出的 RT 名称,便于后续流程复用
    • format:输出的 RT 格式,比如,RGBA8888、RGBA16F 等等
  • blitScreen:执行绘制

    • passIdx:Cocos Shader 中的 Pass 索引(这个在后面的版本中会优化一下,到时候,后处理流程可以不用传这个值)。
  • version:无实际意义,可以忽略。

添加到管线

如果只是写好了上面的代码,不进行添加,也是不会生效的。

我们在文件末尾加上下面代码。

代码片段3:

let builder = rendering.getCustomPipeline('Custom') as postProcess.PostProcessBuilder;
if (builder) {builder.insertPass(new GaussianBlurPass(),postProcess.BlitScreenPass);
}

首先,我们获取到了 Custom 管线,然后把我们新写的效果添加进去。

回到编辑器中,调节参数,就可以看到我们新写的模糊效果生效咯。

后处理管线源码浅析

为了方便大家理解后处理的渲染流程,麒麟子简单说明一些关键的源码。

代码集

后效相关的类,大多都在 postProcess 下,建议先从 “cc” 引入 postProcess,再使用。

RT 管理

开启后处理管线后,开启了后处理管线效果的摄像机,会自动生成 RT,并设置它的 targetTexture

后效管线基于 RenderGraph 管线架构, Cocos 引擎中使用的 RenderGraph 是基于数据驱动的,会每帧收集需要的 RT 资源,并做统一管理。 因此不需要自己再手工新建 RT。

后效执行顺序

后处理效果的执行,不是按界面添加的顺序来的,而是按照处于数组中的顺序来的。我们通过源码可以看到它的内部顺序如下:

  // pipeline relatedthis.addPass(new HBAOPass());this.addPass(new ToneMappingPass());// user post-processingthis.addPass(new TAAPass());this.addPass(new FxaaPass());this.addPass(new ColorGradingPass());this.addPass(new BlitScreenPass());this.addPass(new BloomPass());// final outputthis.addPass(new FSRPass()); // fsr should be finalthis.addPass(forwardFinal);

后处理效果在渲染时,管线会遍历这个数组,依次执行可用的后处理效果。

for (let i = 0; i < passes.length; i++) {const pass = passes[i];if (!pass.checkEnable(camera)) {continue;}if (i === (passes.length - 1)) {passContext.isFinalPass = true;}pass.lastPass = lastPass;pass.render(camera, ppl);lastPass = pass;
}

**源码位置:**engine/cocos/rendering/post-process/post-process-builder.ts

后效注意事项

自定义后效添加

在将自己定义的后效 Shader 添加到管线中时,需要注意几个问题:

  1. 必须添加在 ForwardFinal 之前,否则没有效果。所以 builder.addPass 不建议使用
  2. 使用 builder.insterPass 添加新的后效时,如果新后效与旧的后效重名,会先移除旧的后效
  3. builder.insterPass 会将新的后效插入到第二个参数类型指定的后效后面,通常情况下,建议使用 postProcess.BlitScreenPass

效果

  1. TAA+FXAA+FSR 同时开启,才能达到很好的抗锯齿效果,因为 TAA 主要负责动态(帧间)抗锯齿,但会让画面略微变糊,FXAA 主要负责边缘抗锯齿,而 FSR 可以让糊了的画面变清晰。
  2. ColorGrading 是最划算的后期效果,能够快速提升项目画面颜值
  3. 不要过渡依赖 HBAO,因为中低端机跑不动。场景优先使用光照图烘焙出 AO。这样即使HBAO 关闭的情况下,效果依然不会差。
  4. Bloom 的阈值调小,强度调低,可实现全屏泛光,柔和画面。 Bloom 的阈值调大,强度调高,可实现亮部像素曝光效果。

内存

  1. 虽然 RenderGraph会自动管理和复用 RT,但是 后效依然会对内存有所消耗,可自行测试当所需后效开启后,上涨了多少内存。
  2. 内存开销受分辨率决定,请以实机测试为准。
  3. 可以通过 shadingScale 来降低分辨率,从而减少内存和减少渲染开销,提升性能的目的。这个值可以根据当前实际分辨率与设定的机型可用的最大后效分辨率来决定。

性能

  1. 后效会多次读写帧缓存,对 GPU 带宽和像素填充率,纹理填充率要求高。 在中低端机型上很可能造成性能锐减。

  2. 做好高、中、低端机型管理 ,在不同档次的机型上,开启对应的后效组合,确保性能和效果的平衡。

  3. 许多单 Pass 的后效,可以合成一个。 比如 ColorGrading,FXAA,Vignette,FinalPass 可以合成一个。这样可以减少 BlitScreen 次数,提升性能。

  4. HBAO 能够极大地提升空间关系,但也是性能开销大户,谨慎使用。

写在最后

使用后处理效果能极大地提升画面质感,但也需要注意后处理效果带来的额外内存开销和填充率开销。

后效对低端机型很不友好,请做好在低端机上根据性能测试结果做好分档。

另外,后效的种类繁多,且根据具体的项目需求,又可以做特殊优化。因此引擎只能内置常见的后效供大家使用,更多后效果需求,还得开发者们自己来。

希望今天的分享能够对大家有帮助,谢谢大家!

附1:完整源码

新建一个 TS 脚本,将代码片段1代码片段2代码片段3 复制到这个脚本中即可。

附2:完整 Shader

新建一个 Cocos Shader 文件,将下面 Shader 代码复制到文件中即可。

CCEffect %{techniques:- passes:- vert: blur-hor-vsfrag: blur-fspass: blur-xdepthStencilState:depthTest: falsedepthWrite: false- vert: blur-vert-vsfrag: blur-fspass: blur-ydepthStencilState:depthTest: falsedepthWrite: false
}%CCProgram blur-hor-vs %{precision highp float;#include <legacy/input-standard>#include <builtin/uniforms/cc-global>#include <common/common-define>uniform MyConstants {vec4 blurParams;};out vec2 v_uv;out vec2 v_uv1;out vec2 v_uv2;out vec2 v_uv3;out vec2 v_uv4;void main () {StandardVertInput In;CCVertInput(In);CC_HANDLE_GET_CLIP_FLIP(In.position.xy);gl_Position = In.position;gl_Position.y = gl_Position.y;v_uv = a_texCoord;vec2 texelSize = cc_nativeSize.zw;float blurOffsetX = blurParams.x * texelSize.x;v_uv1 = v_uv + vec2(blurOffsetX * 1.0, 0.0);v_uv2 = v_uv - vec2(blurOffsetX * 1.0, 0.0);v_uv3 = v_uv + vec2(blurOffsetX * 2.0, 0.0);v_uv4 = v_uv - vec2(blurOffsetX * 2.0, 0.0);}
}%CCProgram blur-vert-vs %{precision highp float;#include <legacy/input-standard>#include <builtin/uniforms/cc-global>#include <common/common-define>uniform MyConstants {vec4 blurParams;};out vec2 v_uv;out vec2 v_uv1;out vec2 v_uv2;out vec2 v_uv3;out vec2 v_uv4;void main () {StandardVertInput In;CCVertInput(In);CC_HANDLE_GET_CLIP_FLIP(In.position.xy);gl_Position = In.position;gl_Position.y = gl_Position.y;v_uv = a_texCoord;vec2 texelSize = cc_nativeSize.zw;float blurOffsetY = blurParams.x * texelSize.y;v_uv1 = v_uv + vec2(0.0, blurOffsetY * 1.0);v_uv2 = v_uv - vec2(0.0, blurOffsetY * 1.0);v_uv3 = v_uv + vec2(0.0, blurOffsetY * 2.0);v_uv4 = v_uv - vec2(0.0, blurOffsetY * 2.0);}
}%CCProgram blur-fs %{precision highp float;#include <builtin/uniforms/cc-global>in vec2 v_uv;in vec2 v_uv1;in vec2 v_uv2;in vec2 v_uv3;in vec2 v_uv4;#pragma rate outputResultMap passuniform sampler2D outputResultMap;layout(location = 0) out vec4 fragColor;void main () {vec3 weights = vec3(0.4026,0.2442,0.0545);vec3 sum = texture(outputResultMap, v_uv).rgb * weights.x;sum += texture(outputResultMap, v_uv1).rgb * weights.y;sum += texture(outputResultMap, v_uv2).rgb * weights.y;sum += texture(outputResultMap, v_uv3).rgb * weights.z;sum += texture(outputResultMap, v_uv4).rgb * weights.z;fragColor = vec4(sum, 1.0);}
}%

关于麒麟子

深耕游戏引擎与游戏开发15年,每一滴干货都源自商业项目实践。

用技术资源赋能行业商机,提供实用解决方案、项目分析、技术指导与干货教程!

欢迎私信!

相关文章:

Cocos Creator 3.8 后期效果 Shader 编写(2/2) 进阶篇

前言 在上一篇文章中&#xff0c;麒麟子给大家分享了如何在 Cocos Creator 3.8 中的自定义管线中&#xff0c;添加属于自己的后期效果 Shader。 但基于 BlitScreen 的方案&#xff0c;我们只能编写最简单后效 Shader&#xff0c;如果我们想要支持更多复杂的 Shader&#xff0c…...

【JS自用模板】自动点击选课的操作模板

以激动点击课程为案例复习一下基本前端&#xff0c;容易涉及的问题包括如何提取object类的数字&#xff0c;setTimeout为什么不起作用&#xff1f; 具体思路是&#xff0c;此处会立刻选中符合条件的页面元素打开&#xff0c;然后1小时后会刷新页面&#xff0c;相应地播放页面也…...

TENNECO EDI 项目——X12与XML之间的转换

近期为了帮助广大用户更好地使用 EDI 系统&#xff0c;我们根据以往的项目实施经验&#xff0c;将成熟的 EDI 项目进行开源。用户安装好知行之桥EDI系统之后&#xff0c;只需要下载我们整理好的示例代码&#xff0c;并放置在知行之桥指定的工作区中&#xff0c;即可开始使用。 …...

C++项目:在线五子棋对战(网页版)

项目介绍 本项⽬主要实现⼀个⽹⻚版的五⼦棋对战游戏&#xff0c;其主要⽀持以下核⼼功能&#xff1a; • 用户管理:实现用户注册&#xff0c;用户登录、获取用户信息、用户天梯分数记录、用户比赛场次记录等。 • 匹配对战:实现两个玩家在网页端根据天梯分数匹配游戏对⼿&…...

flutter遇到的小问题记录

flutter-getx的Get.bottomSheet组件改变高度 Get.bottomSheet( isScrollControlled: true,) isScrollControlled: true 就是控制高度 (无语) 截取视频第一针 返回的是本地url 或者Uint8List的数据 String? videoStr await VideoThumbnail.thumbnailFile(video: videoPath,…...

Golang bitset 基本使用

安装&#xff1a; go get github.com/bits-and-blooms/bitset下面代码把fmtx换成fmt就行 //------------基本操作------------//构建一个64bit长度的bitsetb : bitset.New(64)//放入一个数b.Set(10)fmtx.Println("add-10&#xff1a;", b.DumpAsBits()) // 0000000…...

sql 分组讨论,二级分组(非2个字段分组),使用 窗口函数和普通分组实现

1. 二级分组需求 先按照一个字段分组&#xff0c;在按照 第二个字段分组。之后&#xff0c;如果 这个 二级分组中的数据&#xff0c;是 > 1条的。就筛选出来。 比如&#xff1a; 先按照 站点分组&#xff0c;再按照 设备分组&#xff0c; 即&#xff1a;如果站点上配置了…...

业务中如何过滤敏感词

在我们访问网站的时候&#xff0c;如果发现我们发布的内容有色情暴力的东西等等&#xff0c;会屏蔽掉&#xff0c;这种行为就是过滤敏感词。 从技术层面实现起来&#xff0c;其实比较简单&#xff0c;因为我们输入的内容就是一个大型的字符串&#xff0c;我们要调用某些api来判…...

用服务器搭建网站需要做什么

网站建设是一个广义的术语&#xff0c;涵盖了许多不同的技能和学科中所使用的生产和维护的网站。不同领域的网页设计&#xff0c;网页图形设计&#xff0c;界面设计&#xff0c;创作&#xff0c;其中包括标准化的代码和专有软件&#xff0c;用户体验设计和搜索引擎优化。许多人…...

clickhouse 删除操作

OLAP 数据库设计的宗旨在于分析适合一次插入多次查询的业务场景&#xff0c;市面上成熟的 AP 数据库在更新和删除操作上支持的均不是很好&#xff0c;当然 clickhouse 也不例外。但是不友好不代表不支持&#xff0c;本文主要介绍在 clickhouse 中如何实现数据的删除&#xff0c…...

C 语言中,「.」与「->」有什么区别?

使用“.”的话&#xff0c;只需要声明一个结构体。格式是结构体类型名结构体名。然后通过结构体名加上“.”再加上域名&#xff0c;就可以引用结构体的域了。因为结构体的内存是自动分配的&#xff0c;就像使用int a;一样。而使用“->”的话&#xff0c;需要声明一个结构体的…...

github pages 用法详解 发布自己的网站

github pages 基础用法 URL 规则 假设你的 github 帐号为 mygithub&#xff0c;需要发布的仓库名为 myrepo&#xff0c;那么 pages 的 URL 为&#xff1a; https://mygithub.github.io/myrepo 添加内容 用任意编辑器写好&#xff08;或者生成&#xff09;标准的网页内容&a…...

坤简炫酷的JQuery轮播图插件

介绍&#xff1a; 找到了一个炫酷的JQuery轮播图插件&#xff0c;只需要配置三四行代码就可以实现很多二维三维炫酷的切换效果。 视频效果及教程&#xff1a; https://www.bilibili.com/video/BV1Fu4y1d776/ 代码&#xff1a; https://github.com/w-x-x-w/AwesomeWeb 使用…...

C# 条件编译

C# 条件编译 C# 条件编译&#xff1a;根据不同的需求&#xff0c;编译生成不同的程序版本&#xff0c;条件编译是一种编译预处理命令&#xff0c;它是在编译代码之前对源代码进行处理。它可以根据条件&#xff0c;决定是否编译某段代码 条件编译的三种形式&#xff1a; 第一种…...

IntelliJ IDEA如何重新弹出git身份验证窗口

1、点击File菜单—>点击Settings—>点击Appearance & Behavior—>点击System Settings—>点击Passwords—>选中Do not save, forget passwords after restart—>点击Apply—>点击OK&#xff0c;如下所示&#xff1a; 2、重启IntelliJ IDEA—>通过g…...

【雕爷学编程】Arduino动手做(200)---WS2812B幻彩LED灯带4

37款传感器与模块的提法&#xff0c;在网络上广泛流传&#xff0c;其实Arduino能够兼容的传感器模块肯定是不止37种的。鉴于本人手头积累了一些传感器和执行器模块&#xff0c;依照实践出真知&#xff08;一定要动手做&#xff09;的理念&#xff0c;以学习和交流为目的&#x…...

【雕爷学编程】Arduino动手做(201)---DFRobot 行空板03

37款传感器与模块的提法&#xff0c;在网络上广泛流传&#xff0c;其实Arduino能够兼容的传感器模块肯定是不止37种的。鉴于本人手头积累了一些传感器和执行器模块&#xff0c;依照实践出真知&#xff08;一定要动手做&#xff09;的理念&#xff0c;以学习和交流为目的&#x…...

Spring中Bean的“一生”(生命周期)

文章目录 一、图解二、文字解析总结 一、图解 >注&#xff1a;处于同一行的执行顺序是从左往右 二、文字解析 SpringBean的生命周期总体分为四个阶段&#xff1a;实例化>属性注入>初始化>销毁 Step1 实例化Bean&#xff1a;根据配置文件中Bean的定义&#xff0c;…...

安卓:LitePal操作数据库

目录 一、LitePal介绍 常用方法&#xff1a; 1、插入数据&#xff1a; 2、更新数据&#xff1a; 3、删除数据&#xff1a; 4、查询数据&#xff1a; 二、LitePal的基本用法&#xff1a; 1、集成LitePal&#xff1a; 2、创建LitePal配置文件&#xff1a; 3、创建模型类…...

【JavaEE初阶】了解JVM

文章目录 一. JVM内存区域划分二. JVM类加载机制2.1 类加载整体流程2.2 类加载的时机2.3 双亲委派模型(经典) 三. JVM垃圾回收机制(GC)3.1 GC实际工作过程3.1.1 找到垃圾/判定垃圾1. 引用计数(不是java的做法,Python/PHP)2. 可达性分析(Java的做法) 3.1.2 清理垃圾1. 标记清除2…...

零门槛NAS搭建:WinNAS如何让普通电脑秒变私有云?

一、核心优势&#xff1a;专为Windows用户设计的极简NAS WinNAS由深圳耘想存储科技开发&#xff0c;是一款收费低廉但功能全面的Windows NAS工具&#xff0c;主打“无学习成本部署” 。与其他NAS软件相比&#xff0c;其优势在于&#xff1a; 无需硬件改造&#xff1a;将任意W…...

C# 类和继承(抽象类)

抽象类 抽象类是指设计为被继承的类。抽象类只能被用作其他类的基类。 不能创建抽象类的实例。抽象类使用abstract修饰符声明。 抽象类可以包含抽象成员或普通的非抽象成员。抽象类的成员可以是抽象成员和普通带 实现的成员的任意组合。抽象类自己可以派生自另一个抽象类。例…...

Java入门学习详细版(一)

大家好&#xff0c;Java 学习是一个系统学习的过程&#xff0c;核心原则就是“理论 实践 坚持”&#xff0c;并且需循序渐进&#xff0c;不可过于着急&#xff0c;本篇文章推出的这份详细入门学习资料将带大家从零基础开始&#xff0c;逐步掌握 Java 的核心概念和编程技能。 …...

【Go语言基础【12】】指针:声明、取地址、解引用

文章目录 零、概述&#xff1a;指针 vs. 引用&#xff08;类比其他语言&#xff09;一、指针基础概念二、指针声明与初始化三、指针操作符1. &&#xff1a;取地址&#xff08;拿到内存地址&#xff09;2. *&#xff1a;解引用&#xff08;拿到值&#xff09; 四、空指针&am…...

前端高频面试题2:浏览器/计算机网络

本专栏相关链接 前端高频面试题1&#xff1a;HTML/CSS 前端高频面试题2&#xff1a;浏览器/计算机网络 前端高频面试题3&#xff1a;JavaScript 1.什么是强缓存、协商缓存&#xff1f; 强缓存&#xff1a; 当浏览器请求资源时&#xff0c;首先检查本地缓存是否命中。如果命…...

在Zenodo下载文件 用到googlecolab googledrive

方法&#xff1a;Figshare/Zenodo上的数据/文件下载不下来&#xff1f;尝试利用Google Colab &#xff1a;https://zhuanlan.zhihu.com/p/1898503078782674027 参考&#xff1a; 通过Colab&谷歌云下载Figshare数据&#xff0c;超级实用&#xff01;&#xff01;&#xff0…...

精益数据分析(98/126):电商转化率优化与网站性能的底层逻辑

精益数据分析&#xff08;98/126&#xff09;&#xff1a;电商转化率优化与网站性能的底层逻辑 在电子商务领域&#xff0c;转化率与网站性能是决定商业成败的核心指标。今天&#xff0c;我们将深入解析不同类型电商平台的转化率基准&#xff0c;探讨页面加载速度对用户行为的…...

手动给中文分词和 直接用神经网络RNN做有什么区别

手动分词和基于神经网络&#xff08;如 RNN&#xff09;的自动分词在原理、实现方式和效果上有显著差异&#xff0c;以下是核心对比&#xff1a; 1. 实现原理对比 对比维度手动分词&#xff08;规则 / 词典驱动&#xff09;神经网络 RNN 分词&#xff08;数据驱动&#xff09…...

安宝特案例丨寻医不再长途跋涉?Vuzix再次以AR技术智能驱动远程医疗

加拿大领先科技公司TeleVU基于Vuzix智能眼镜打造远程医疗生态系统&#xff0c;彻底革新患者护理模式。 安宝特合作伙伴TeleVU成立30余年&#xff0c;沉淀医疗技术、计算机科学与人工智能经验&#xff0c;聚焦医疗保健领域&#xff0c;提供AR、AI、IoT解决方案。 该方案使医疗…...

centos挂载目录满但实际未满引发系统宕机

测试服务器应用系统突然挂了&#xff0c;经过排查发现是因为磁盘“满了”导致的&#xff0c;使用df -h查看磁盘使用情况/home目录使用率已经到了100%,但使用du -sh /home查看发现实际磁盘使用还不到1G&#xff0c;推测有进程正在写入或占用已删除的大文件&#xff08;Linux 系统…...