【初体验 threejs】【学习】【笔记】hello,正方体 3!
前言
为了满足工作需求,我已着手学习 Three.js,并决定详细记录这一学习过程。在此旅程中,如果出现理解偏差或有其他更佳的学习方法,请大家不吝赐教,在评论区给予指正或分享您的宝贵建议,我将不胜感激。
项目基础
请参考hello,正方体 2!。
1. 抗锯齿
除非直线完全水平或垂直,否则使用方形像素绘制直线是很困难的。我们将使用一种称为抗锯齿(AA) 的技术来解决这个问题。
1.1 多重采样抗锯齿 (MSAA)
抗锯齿是使用内置的 WebGL 方法执行的,即 多重采样抗锯齿 (MSAA)。
1.2 启用抗锯齿
renderer.js: 添加如下
import { WebGLRenderer } from "three";/*** @description - 创建渲染器* @returns {WebGLRenderer} - 渲染器实例*/
export const createRenderder = () => {// 创建WebGLRenderer类的一个实例// 打开抗锯齿antialiasconst renderer = new WebGLRenderer({ antialias: true });// 启用物理上正确的光照renderer.physicallyCorrectLights = true;return renderer;
};
注意
一旦创建了渲染器,就无法更改此设置。
2. 浏览器窗口大小变化
目的是为了用户调整预览窗口的大小,场景适应新的大小。
2.1 扩展 Resizer 类
- 因为初始化时要设置初始大小,页面变化时也要重新设置大小,所有要把这部分设置代码移入到一个函数里。
- 将使用 ResizeObserver 来监听容器的大小变化。
- 因为我们只调用了.render 一次,它在画布中绘制了一个帧。当画布被调整大小时,这个框架被拉伸以适应新的大小。所以要创建一个 onResize 钩子,每次调整大小事件触发时生成一个新帧。
- 在 World 中使用 onResize 函数。
Resizer.js:添加如下
import { PerspectiveCamera, WebGLRenderer } from "three";
class Resizer {#container; // 容器#camera; // 相机#renderer; // 渲染器onResize = () => {}; // 页面大小变化钩子函数/*** @param {Element} container - 容器* @param {PerspectiveCamera} camera - 相机* @param {WebGLRenderer} renderer - 渲染器*/constructor(container, camera, renderer) {this.#container = container;this.#camera = camera;this.#renderer = renderer;// 初始化sizethis.#setSize();// 监听容器大小变化const resizeObserver = new ResizeObserver(() => {this.#setSize();this.onResize();});resizeObserver.observe(container);}#setSize() {this.#camera.aspect =this.#container.clientWidth / this.#container.clientHeight;this.#camera.updateProjectionMatrix();this.#renderer.setSize(this.#container.clientWidth,this.#container.clientHeight);this.#renderer.setPixelRatio(window.devicePixelRatio);}
}
export { Resizer };
World.js:添加如下
import { createScene } from "./components/scene";
import { createCamera } from "./components/camera";
import { createCude } from "./components/cube";
import { createLights } from "./components/lights";
import { createRenderder } from "./systems/renderer";
import { Resizer } from "./systems/Resizer";
class World {#scene; // 场景#camera; // 相机#renderer; // 渲染/*** @param {Element} container - 容器*/constructor(container) {this.#scene = createScene();this.#camera = createCamera();this.#renderer = createRenderder();container.append(this.#renderer.domElement);const cube = createCude();const light = createLights();this.#scene.add(cube, light);const resizer = new Resizer(container, this.#camera, this.#renderer);resizer.onResize = () => {this.render();};}/*** @description - 渲染函数*/render() {this.#renderer.render(this.#scene, this.#camera);}
}export { World };
3. 动画循环(运动)
为立方体添加一个简单的旋转动画。
- 创建一个动画循环
创建 systems/Loop.js 模块并在其中创建一个新 Loop 类
import { PerspectiveCamera, Scene, WebGLRenderer } from "three";
class Loop {#camera; // 相机#scene; // 场景#renderer; // 渲染/*** @param {PerspectiveCamera} camera - 相机* @param {Scene} scene - 场景* @param {WebGLRenderer} renderer - 渲染*/constructor(camera, scene, renderer) {this.#camera = camera;this.#scene = scene;this.#renderer = renderer;}/*** @description - 动画循环开始*/start() {}/*** @description - 动画循环结束*/stop() {}
}export { Loop };
- 在 World 中,将这个新类添加到导入列表中:
World.js: 添加如下
import { createScene } from "./components/scene";
import { createCamera } from "./components/camera";
import { createCude } from "./components/cube";
import { createLights } from "./components/lights";
import { createRenderder } from "./systems/renderer";
import { Resizer } from "./systems/Resizer";
import { Loop } from "./systems/Loop";
class World {#scene; // 场景#camera; // 相机#renderer; // 渲染#loop; // 动画循环/*** @param {Element} container - 容器*/constructor(container) {this.#scene = createScene();this.#camera = createCamera();this.#renderer = createRenderder();this.#loop = new Loop(this.#camera, this.#scene, this.#renderer);container.append(this.#renderer.domElement);const cube = createCude();const light = createLights();this.#scene.add(cube, light);const resizer = new Resizer(container, this.#camera, this.#renderer);resizer.onResize = () => {this.render();};}/*** @description - 渲染函数*/render() {this.#renderer.render(this.#scene, this.#camera);}/*** @description - 动画循环开始*/start() {this.#loop.start();}/*** @description - 动画循环结束*/stop() {this.#loop.stop();}
}export { World };
- 启动动画循环
main.js:添加如下
import { World } from "../World/World";function main() {// 获取容器const container = document.querySelector("#scene-container");// 创建一个world类实例const world = new World(container);// 开始动画循环world.start();
}main();
- 使用.setAnimationLoop 创建循环
在内部,循环是使用 .requestAnimationFrame。这种内置的浏览器方法可以智能地安排帧与显示器的刷新率同步,如果您的硬件跟不上,它会平滑地降低帧率。然而,.setAnimationLoop 还有一点额外的魔力可以确保循环在虚拟现实和增强现实环境中工作。
Loop.js:添加如下
import { PerspectiveCamera, Scene, WebGLRenderer } from "three";
class Loop {#camera; // 相机#scene; // 场景#renderer; // 渲染/*** @param {PerspectiveCamera} camera - 相机* @param {Scene} scene - 场景* @param {WebGLRenderer} renderer - 渲染*/constructor(camera, scene, renderer) {this.#camera = camera;this.#scene = scene;this.#renderer = renderer;}/*** @description - 动画循环开始*/start() {this.#renderer.setAnimationLoop(() => {this.#renderer.render(this.#scene, this.#camera);});}/*** @description - 动画循环结束*/stop() {this.#renderer.setAnimationLoop(null);}
}export { Loop };
- 移除 onResize 钩子
现在循环正在运行,每当我们调整窗口大小时,都会在循环的下一次迭代中生成一个新帧。这足够快,不会注意到任何延迟,因此我们不再需要在调整大小时手动重绘场景。从 World 中移除 resizer.onResize 钩子
World.js:添加如下
import { createScene } from "./components/scene";
import { createCamera } from "./components/camera";
import { createCude } from "./components/cube";
import { createLights } from "./components/lights";
import { createRenderder } from "./systems/renderer";
import { Resizer } from "./systems/Resizer";
import { Loop } from "./systems/Loop";
class World {#scene; // 场景#camera; // 相机#renderer; // 渲染#loop; // 动画循环/*** @param {Element} container - 容器*/constructor(container) {this.#scene = createScene();this.#camera = createCamera();this.#renderer = createRenderder();this.#loop = new Loop(this.#camera, this.#scene, this.#renderer);container.append(this.#renderer.domElement);const cube = createCude();const light = createLights();this.#scene.add(cube, light);new Resizer(container, this.#camera, this.#renderer);}/*** @description - 渲染函数*/render() {this.#renderer.render(this.#scene, this.#camera);}/*** @description - 动画循环开始*/start() {this.#loop.start();}/*** @description - 动画循环结束*/stop() {this.#loop.stop();}
}export { World };
4. 动画系统
每次循环运行时,希望通过将它们向前移动一帧来更新所有这些动画。就在我们渲染每一帧之前,我们会让立方体旋转一点点, 几乎是肉眼无法看到的微小量,但随着时间的推移会产生流畅的动画效果。
- Loop.tick 方法
为了处理所有这些,我们需要一个更新所有动画的函数,并且这个函数应该在每一帧开始时运行一次。
Loop.js:添加如下
import { PerspectiveCamera, Scene, WebGLRenderer } from "three";
class Loop {#camera; // 相机#scene; // 场景#renderer; // 渲染/*** @param {PerspectiveCamera} camera - 相机* @param {Scene} scene - 场景* @param {WebGLRenderer} renderer - 渲染*/constructor(camera, scene, renderer) {this.#camera = camera;this.#scene = scene;this.#renderer = renderer;}/*** @description - 动画循环开始*/start() {this.#renderer.setAnimationLoop(() => {this.tick();this.#renderer.render(this.#scene, this.#camera);});}/*** @description - 动画循环结束*/stop() {this.#renderer.setAnimationLoop(null);}/*** @description - 更新动画*/tick() {}
}export { Loop };
- cube.tick 方法
为动画对象创建一个.tick 方法,更新自身。
cube.js: 添加如下
import { BoxGeometry, Mesh, MeshStandardMaterial, MathUtils } from "three";
/*** @description - 创建立方体* @returns {Mesh} - 网格实例*/export const createCude = () => {// 创建边长为2的几何体(就是边长2米)const geometry = new BoxGeometry(2, 2, 2);// 创建一个高质量、通用、物理精确的材料 设置颜色为紫色const material = new MeshStandardMaterial({ color: "purple" });// 创建一个网格添加几何体和材质const cube = new Mesh(geometry, material);// 旋转立方体cube.rotation.set(-0.5, -0.1, 0.8);// 添加更新动画方法tickcube.tick = () => {cube.rotation.z += 0.01;cube.rotation.x += 0.01;cube.rotation.y += 0.01;};return cube;
};
注意
像这样在运行时向现有类添加属性称为 猴子补丁。
- Loop.#updatables
循环类中的动画对象列表。
Loop.js:添加如下
import { PerspectiveCamera, Scene, WebGLRenderer, Object3D } from "three";
class Loop {#camera; // 相机#scene; // 场景#renderer; // 渲染#updatables = []; // 循环里的动画对象列表/*** @param {PerspectiveCamera} camera - 相机* @param {Scene} scene - 场景* @param {WebGLRenderer} renderer - 渲染* @param {Object3D[]} updatables - 循环里的动画对象列表*/constructor(camera, scene, renderer, updatables = []) {this.#camera = camera;this.#scene = scene;this.#renderer = renderer;this.#updatables = updatables;}/*** @description - 动画循环开始*/start() {this.#renderer.setAnimationLoop(() => {this.tick();this.#renderer.render(this.#scene, this.#camera);});}/*** @description - 动画循环结束*/stop() {this.#renderer.setAnimationLoop(null);}/*** @description - 更新动画*/tick() {for (const object of this.#updatables) {object.tick();}}/*** @description - 添加动画对象* @param {...Object3D} object*/addObjectToUpdatables(...object) {this.#updatables.push(...object);}/*** @description - 删除动画对象* @param {...Object3D} object*/removeObjectFromUpdatables(...object) {this.#updatables = this.#updatables.filter((obj) => !object.includes(obj));}
}export { Loop };
- 添加 cube 到 Loop.#updatables
World.js:添加如下
import { createScene } from "./components/scene";
import { createCamera } from "./components/camera";
import { createCude } from "./components/cube";
import { createLights } from "./components/lights";
import { createRenderder } from "./systems/renderer";
import { Resizer } from "./systems/Resizer";
import { Loop } from "./systems/Loop";
class World {#scene; // 场景#camera; // 相机#renderer; // 渲染#loop; // 动画循环/*** @param {Element} container - 容器*/constructor(container) {this.#scene = createScene();this.#camera = createCamera();this.#renderer = createRenderder();this.#loop = new Loop(this.#camera, this.#scene, this.#renderer);container.append(this.#renderer.domElement);const cube = createCude();this.#loop.addObjectToUpdatables(cube);const light = createLights();this.#scene.add(cube, light);new Resizer(container, this.#camera, this.#renderer);}/*** @description - 渲染函数*/render() {this.#renderer.render(this.#scene, this.#camera);}/*** @description - 动画循环开始*/start() {this.#loop.start();}/*** @description - 动画循环结束*/stop() {this.#loop.stop();}
}export { World };
小结
立方体应该立即开始旋转。
5. 将动画速度与帧速率解耦
我们动画的速度取决于观看它的设备。我们的动画循环不会以固定速率生成帧。这意味着,在 60Hz 屏幕上,目标帧率为 60FPS,在 90Hz 屏幕上,目标帧率为 90FPS,以此类推。
在每一种情况下,动画循环都会以较低的速率生成帧,并且这个速率可能会因为许多因素从一个时刻到下一个时刻波动。这称为可变帧速率。
为了防止这种情况,我们需要将动画速度与帧速率解耦。我们将这样做:当我们告诉一个对象.tick 前进一帧时,我们将根据前一帧花费的时间来缩放移动的大小。
- 使用 Clock 类
用 Clock.getDelta 来衡量前一帧花了多长时间。.getDelta 告诉我们自上次调用.getDelta 以来已经过去了多少时间。
将结果保存在一个名为 delta 的变量中,然后我们将其传递给每个动画对象的.tick 方法。
Loop.js:添加如下
import {PerspectiveCamera,Scene,WebGLRenderer,Object3D,Clock,
} from "three";
class Loop {#camera; // 相机#scene; // 场景#renderer; // 渲染#updatables = []; // 循环里的动画对象列表#clock; // 秒表/*** @param {PerspectiveCamera} camera - 相机* @param {Scene} scene - 场景* @param {WebGLRenderer} renderer - 渲染* @param {Object3D[]} updatables - 循环里的动画对象列表*/constructor(camera, scene, renderer, updatables = []) {this.#camera = camera;this.#scene = scene;this.#renderer = renderer;this.#updatables = updatables;this.#clock = new Clock();}/*** @description - 动画循环开始*/start() {this.#renderer.setAnimationLoop(() => {this.tick();this.#renderer.render(this.#scene, this.#camera);});}/*** @description - 动画循环结束*/stop() {this.#renderer.setAnimationLoop(null);}/*** @description - 更新动画*/tick() {// .getDelta告诉我们自上次调用.getDelta以来已经过去了多少时间。const delta = this.#clock.getDelta();for (const object of this.#updatables) {object.tick(delta);}}/*** @description - 添加动画对象* @param {...Object3D} object*/addObjectToUpdatables(...object) {this.#updatables.push(...object);}/*** @description - 删除动画对象* @param {...Object3D} object*/removeObjectFromUpdatables(...object) {this.#updatables = this.#updatables.filter((obj) => !object.includes(obj));}
}export { Loop };
cube.js:添加如下
import { BoxGeometry, Mesh, MeshStandardMaterial, MathUtils } from "three";
/*** @description - 创建立方体* @returns {Mesh} - 网格实例*/export const createCude = () => {// 创建边长为2的几何体(就是边长2米)const geometry = new BoxGeometry(2, 2, 2);// 创建一个高质量、通用、物理精确的材料 设置颜色为紫色const material = new MeshStandardMaterial({ color: "purple" });// 创建一个网格添加几何体和材质const cube = new Mesh(geometry, material);// 旋转立方体cube.rotation.set(-0.5, -0.1, 0.8);// 度数转弧度const radiansPerSecond = MathUtils.degToRad(30);// 添加更新动画方法tickcube.tick = (delta) => {const radian = radiansPerSecond * delta;cube.rotation.z += radian;cube.rotation.x += radian;cube.rotation.y += radian;};return cube;
};
6. 总结
至此已经全部完成。你好,正方体 3!如果出现理解偏差或有其他更佳的学习方法,请大家不吝赐教,在评论区给予指正或分享您的宝贵建议,我将不胜感激。
主要文献
three.js 官网
《discoverthreejs》
相关文章:
【初体验 threejs】【学习】【笔记】hello,正方体 3!
前言 为了满足工作需求,我已着手学习 Three.js,并决定详细记录这一学习过程。在此旅程中,如果出现理解偏差或有其他更佳的学习方法,请大家不吝赐教,在评论区给予指正或分享您的宝贵建议,我将不胜感激。 项…...

第04章:IDEA的安装与使用
第04章:随堂复习与企业真题(IDEA安装与使用) 一、随堂复习 1. IDEA的认识 IDEA(集成功能强大、符合人体工程学(设置人性化))Eclipse 2. IDEA的下载、安装、卸载 卸载:使用控制面板进行卸载,…...
[原创][Delphi多线程]使用TMonitor, TEvent和TQueue配合实现TThreadQueue的经典使用案例.
[简介] 常用网名: 猪头三 出生日期: 1981.XX.XX QQ: 643439947 个人网站: 80x86汇编小站 https://www.x86asm.org 编程生涯: 2001年~至今[共22年] 职业生涯: 20年 开发语言: C/C、80x86ASM、PHP、Perl、Objective-C、Object Pascal、C#、Python 开发工具: Visual Studio、Delph…...

6.12ctf练习
[西湖论剑 2022]Node Magical Login 源码在这里:GitHub - CTF-Archives/2022-xhlj-web-node_magical_login: A web challenge in 2022 西湖论剑大赛打开 打开环境是个登录框,先进行了扫描和抓包都没有看见什么有价值的东西,看源码 大致连接…...

海豚调度异常处理: 使用 arthas 在内存中删除启动失败的工作流
💡 本系列文章是 DolphinScheduler 由浅入深的教程,涵盖搭建、二开迭代、核心原理解读、运维和管理等一系列内容。适用于想对 DolphinScheduler了解或想要加深理解的读者。祝开卷有益。大数据学习指南 大家好,我是小陶,DolphinSch…...
在Qt中,QSerialPort::write(data) 和 readAll() 有什么关联和联系
在Qt中,QSerialPort::write(data) 和 readAll() 是与串行通信相关的两个不同的函数,它们属于 QSerialPort 类。这两个函数在串行通信中扮演不同的角色,但它们之间存在一定的联系: QSerialPort::write(data) 这个函数用于将数据发…...

第 2 章:Spring Framework 中的 IoC 容器
控制反转(Inversion of Control,IoC)与 面向切面编程(Aspect Oriented Programming,AOP)是 Spring Framework 中最重要的两个概念,本章会着重介绍前者,内容包括 IoC 容器以及容器中 …...
构造函数、实例、原型对象三者之间的关系
在 JavaScript 中,构造函数、实例和原型对象之间有着密切的关系。下面是对它们之间关系的详细解析和代码示例: 构造函数:构造函数是一个特殊的函数,用于创建对象的模板。它定义了对象的属性和方法。构造函数通常以大写字母开头&a…...

人工智能抢走了他们的工作。现在他们得到报酬,让它听起来像人类
人工智能抢走了他们的工作。现在他们得到报酬,让它听起来像人类 如果你担心人工智能会如何影响你的工作,那么广告文案的世界或许能让你窥见未来。 作家本杰明米勒(化名)在2023年初非常红火。他领导了一个由60多名作家和编辑组成的团队,发表博…...

大模型微调出错的解决方案(持续更新)
大家好,我是herosunly。985院校硕士毕业,现担任算法研究员一职,热衷于机器学习算法研究与应用。曾获得阿里云天池比赛第一名,CCF比赛第二名,科大讯飞比赛第三名。拥有多项发明专利。对机器学习和深度学习拥有自己独到的见解。曾经辅导过若干个非计算机专业的学生进入到算法…...

企业多云策略的优势与实施指南
企业在选择云服务提供商时,常见的选项包括亚马逊AWS、微软Azure、谷歌云GCP、阿里云、腾讯云和华为云。为了避免过度依赖单一供应商,许多企业选择采用多云策略,这样可以充分利用不同云服务的优势,同时避免重复工作和其他额外的工作…...

vue分页
先看效果 再看代码 <!-- 分页 --><div v-if"pageParams.pageCount > 1" class"flex justify-end mt-6"><n-paginationv-model:page"pageParams.page" v-model:page-size"pageParams.pageSize" :page-count"pa…...
服务器上设置pnpm环境变量
首先,确认 pnpm 是否已经安装: ls /www/server/nodejs/v20.10.0/bin/pnpm如果输出包含 pnpm,那么说明 pnpm 已经安装。 如果没有看到 pnpm,你可能需要重新安装它: npm install -g pnpm接下来,确保 PATH …...
Java中BIO、NIO、AIO详解
参考: https://blog.csdn.net/s2152637/article/details/98777686 https://blog.csdn.net/bigorsmallorlarge/article/details/137292669 1、几个基本概念 Java中IO模型简介 在Java中,主要有三种IO模型,分别是: 同步阻塞IO&…...

cloud_enum:一款针对不同平台云环境安全的OSINT工具
关于cloud_enum cloud_enum是一款功能强大的云环境安全OSINT工具,该工具支持AWS、Azure和Google Cloud三种不同的云环境,旨在帮助广大研究人员枚举目标云环境中的公共资源,并尝试寻找其中潜在的安全威胁。 功能介绍 当前版本的cloud_enum支…...

图像的对比度和亮度
目标 访问像素值用0来初始化矩阵cv::saturate_cast像素转换提高一张图像的亮度 原理 图像处理 图像变换可以被视作两个步骤: 点操纵(像素转换)相邻区域转换(以面积为基础) 像素转换 在这种图像处理的转换过程中…...

手撕设计模式——计划生育之单例模式
1.业务需求 大家好,我是菠菜啊。80、90后还记得计划生育这个国策吗?估计同龄的小伙伴们,小时候常常被”只生一个好“”少生、优生“等宣传标语洗脑,如今国家已经放开并鼓励生育了。话说回来,现实生活中有计划生育&…...

Mac M3 Pro 部署Flink-1.16.3
目录 1、下载安装包 2、解压及配置 3、启动&测试 4、测试FlinkSQL读取hive数据 以上是mac硬件配置 1、下载安装包 官网:Downloads | Apache Flink 网盘: Flink 安装包 https://pan.baidu.com/s/1IN62_T5JUrnYUycYMwsQqQ?pwdgk4e Flink 已…...

Mysql 的分布式策略
1. 前言 MySQL 作为最最常用的数据库,了解 Mysql 的分布式策略对于掌握 MySQL 的高性能使用方法和更安全的储存方式有非常重要的作用。 它同时也是面试中最最常问的考点,我们这里就简单总结下 Mysq 的常用分布式策略。 2. 复制 复制主要有主主复制和…...

记录一个利用winhex进行图片隐写分离的
前提 是一次大比武里面的题目,属实给我开了眼,跟我之前掌握的关于隐写合并的操作都不一样。 它不是直接在文件里面进行输入文件隐写,叫你输入密码,或者更改颜色,或者偏移位置; 它不是单纯几个文件合并&a…...
浅谈 React Hooks
React Hooks 是 React 16.8 引入的一组 API,用于在函数组件中使用 state 和其他 React 特性(例如生命周期方法、context 等)。Hooks 通过简洁的函数接口,解决了状态与 UI 的高度解耦,通过函数式编程范式实现更灵活 Rea…...

地震勘探——干扰波识别、井中地震时距曲线特点
目录 干扰波识别反射波地震勘探的干扰波 井中地震时距曲线特点 干扰波识别 有效波:可以用来解决所提出的地质任务的波;干扰波:所有妨碍辨认、追踪有效波的其他波。 地震勘探中,有效波和干扰波是相对的。例如,在反射波…...

C++_核心编程_多态案例二-制作饮品
#include <iostream> #include <string> using namespace std;/*制作饮品的大致流程为:煮水 - 冲泡 - 倒入杯中 - 加入辅料 利用多态技术实现本案例,提供抽象制作饮品基类,提供子类制作咖啡和茶叶*//*基类*/ class AbstractDr…...

8k长序列建模,蛋白质语言模型Prot42仅利用目标蛋白序列即可生成高亲和力结合剂
蛋白质结合剂(如抗体、抑制肽)在疾病诊断、成像分析及靶向药物递送等关键场景中发挥着不可替代的作用。传统上,高特异性蛋白质结合剂的开发高度依赖噬菌体展示、定向进化等实验技术,但这类方法普遍面临资源消耗巨大、研发周期冗长…...

基于当前项目通过npm包形式暴露公共组件
1.package.sjon文件配置 其中xh-flowable就是暴露出去的npm包名 2.创建tpyes文件夹,并新增内容 3.创建package文件夹...
postgresql|数据库|只读用户的创建和删除(备忘)
CREATE USER read_only WITH PASSWORD 密码 -- 连接到xxx数据库 \c xxx -- 授予对xxx数据库的只读权限 GRANT CONNECT ON DATABASE xxx TO read_only; GRANT USAGE ON SCHEMA public TO read_only; GRANT SELECT ON ALL TABLES IN SCHEMA public TO read_only; GRANT EXECUTE O…...
Frozen-Flask :将 Flask 应用“冻结”为静态文件
Frozen-Flask 是一个用于将 Flask 应用“冻结”为静态文件的 Python 扩展。它的核心用途是:将一个 Flask Web 应用生成成纯静态 HTML 文件,从而可以部署到静态网站托管服务上,如 GitHub Pages、Netlify 或任何支持静态文件的网站服务器。 &am…...

OPENCV形态学基础之二腐蚀
一.腐蚀的原理 (图1) 数学表达式:dst(x,y) erode(src(x,y)) min(x,y)src(xx,yy) 腐蚀也是图像形态学的基本功能之一,腐蚀跟膨胀属于反向操作,膨胀是把图像图像变大,而腐蚀就是把图像变小。腐蚀后的图像变小变暗淡。 腐蚀…...
Java线上CPU飙高问题排查全指南
一、引言 在Java应用的线上运行环境中,CPU飙高是一个常见且棘手的性能问题。当系统出现CPU飙高时,通常会导致应用响应缓慢,甚至服务不可用,严重影响用户体验和业务运行。因此,掌握一套科学有效的CPU飙高问题排查方法&…...
使用Matplotlib创建炫酷的3D散点图:数据可视化的新维度
文章目录 基础实现代码代码解析进阶技巧1. 自定义点的大小和颜色2. 添加图例和样式美化3. 真实数据应用示例实用技巧与注意事项完整示例(带样式)应用场景在数据科学和可视化领域,三维图形能为我们提供更丰富的数据洞察。本文将手把手教你如何使用Python的Matplotlib库创建引…...