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

【初体验 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 类
  1. 因为初始化时要设置初始大小,页面变化时也要重新设置大小,所有要把这部分设置代码移入到一个函数里。
  2. 将使用 ResizeObserver 来监听容器的大小变化。
  3. 因为我们只调用了.render 一次,它在画布中绘制了一个帧。当画布被调整大小时,这个框架被拉伸以适应新的大小。所以要创建一个 onResize 钩子,每次调整大小事件触发时生成一个新帧。
  4. 在 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. 动画循环(运动)

为立方体添加一个简单的旋转动画。

  1. 创建一个动画循环
    创建 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 };
  1. 在 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 };
  1. 启动动画循环
    main.js:添加如下
import { World } from "../World/World";function main() {// 获取容器const container = document.querySelector("#scene-container");// 创建一个world类实例const world = new World(container);// 开始动画循环world.start();
}main();
  1. 使用.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 };
  1. 移除 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. 动画系统

每次循环运行时,希望通过将它们向前移动一帧来更新所有这些动画。就在我们渲染每一帧之前,我们会让立方体旋转一点点, 几乎是肉眼无法看到的微小量,但随着时间的推移会产生流畅的动画效果。

  1. 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 };
  1. 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;
};

注意
像这样在运行时向现有类添加属性称为 猴子补丁。

  1. 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 };
  1. 添加 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 前进一帧时,我们将根据前一帧花费的时间来缩放移动的大小。

  1. 使用 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环境变量

首先&#xff0c;确认 pnpm 是否已经安装&#xff1a; ls /www/server/nodejs/v20.10.0/bin/pnpm如果输出包含 pnpm&#xff0c;那么说明 pnpm 已经安装。 如果没有看到 pnpm&#xff0c;你可能需要重新安装它&#xff1a; npm install -g pnpm接下来&#xff0c;确保 PATH …...

Java中BIO、NIO、AIO详解

参考&#xff1a; https://blog.csdn.net/s2152637/article/details/98777686 https://blog.csdn.net/bigorsmallorlarge/article/details/137292669 1、几个基本概念 Java中IO模型简介 在Java中&#xff0c;主要有三种IO模型&#xff0c;分别是&#xff1a; 同步阻塞IO&…...

cloud_enum:一款针对不同平台云环境安全的OSINT工具

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

图像的对比度和亮度

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

手撕设计模式——计划生育之单例模式

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

Mac M3 Pro 部署Flink-1.16.3

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

Mysql 的分布式策略

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

记录一个利用winhex进行图片隐写分离的

前提 是一次大比武里面的题目&#xff0c;属实给我开了眼&#xff0c;跟我之前掌握的关于隐写合并的操作都不一样。 它不是直接在文件里面进行输入文件隐写&#xff0c;叫你输入密码&#xff0c;或者更改颜色&#xff0c;或者偏移位置&#xff1b; 它不是单纯几个文件合并&a…...

接口测试中缓存处理策略

在接口测试中&#xff0c;缓存处理策略是一个关键环节&#xff0c;直接影响测试结果的准确性和可靠性。合理的缓存处理策略能够确保测试环境的一致性&#xff0c;避免因缓存数据导致的测试偏差。以下是接口测试中常见的缓存处理策略及其详细说明&#xff1a; 一、缓存处理的核…...

云原生核心技术 (7/12): K8s 核心概念白话解读(上):Pod 和 Deployment 究竟是什么?

大家好&#xff0c;欢迎来到《云原生核心技术》系列的第七篇&#xff01; 在上一篇&#xff0c;我们成功地使用 Minikube 或 kind 在自己的电脑上搭建起了一个迷你但功能完备的 Kubernetes 集群。现在&#xff0c;我们就像一个拥有了一块崭新数字土地的农场主&#xff0c;是时…...

盘古信息PCB行业解决方案:以全域场景重构,激活智造新未来

一、破局&#xff1a;PCB行业的时代之问 在数字经济蓬勃发展的浪潮中&#xff0c;PCB&#xff08;印制电路板&#xff09;作为 “电子产品之母”&#xff0c;其重要性愈发凸显。随着 5G、人工智能等新兴技术的加速渗透&#xff0c;PCB行业面临着前所未有的挑战与机遇。产品迭代…...

从零实现富文本编辑器#5-编辑器选区模型的状态结构表达

先前我们总结了浏览器选区模型的交互策略&#xff0c;并且实现了基本的选区操作&#xff0c;还调研了自绘选区的实现。那么相对的&#xff0c;我们还需要设计编辑器的选区表达&#xff0c;也可以称为模型选区。编辑器中应用变更时的操作范围&#xff0c;就是以模型选区为基准来…...

ffmpeg(四):滤镜命令

FFmpeg 的滤镜命令是用于音视频处理中的强大工具&#xff0c;可以完成剪裁、缩放、加水印、调色、合成、旋转、模糊、叠加字幕等复杂的操作。其核心语法格式一般如下&#xff1a; ffmpeg -i input.mp4 -vf "滤镜参数" output.mp4或者带音频滤镜&#xff1a; ffmpeg…...

WEB3全栈开发——面试专业技能点P2智能合约开发(Solidity)

一、Solidity合约开发 下面是 Solidity 合约开发 的概念、代码示例及讲解&#xff0c;适合用作学习或写简历项目背景说明。 &#x1f9e0; 一、概念简介&#xff1a;Solidity 合约开发 Solidity 是一种专门为 以太坊&#xff08;Ethereum&#xff09;平台编写智能合约的高级编…...

Kafka入门-生产者

生产者 生产者发送流程&#xff1a; 延迟时间为0ms时&#xff0c;也就意味着每当有数据就会直接发送 异步发送API 异步发送和同步发送的不同在于&#xff1a;异步发送不需要等待结果&#xff0c;同步发送必须等待结果才能进行下一步发送。 普通异步发送 首先导入所需的k…...

uniapp手机号一键登录保姆级教程(包含前端和后端)

目录 前置条件创建uniapp项目并关联uniClound云空间开启一键登录模块并开通一键登录服务编写云函数并上传部署获取手机号流程(第一种) 前端直接调用云函数获取手机号&#xff08;第三种&#xff09;后台调用云函数获取手机号 错误码常见问题 前置条件 手机安装有sim卡手机开启…...

【JVM】Java虚拟机(二)——垃圾回收

目录 一、如何判断对象可以回收 &#xff08;一&#xff09;引用计数法 &#xff08;二&#xff09;可达性分析算法 二、垃圾回收算法 &#xff08;一&#xff09;标记清除 &#xff08;二&#xff09;标记整理 &#xff08;三&#xff09;复制 &#xff08;四&#xff…...

并发编程 - go版

1.并发编程基础概念 进程和线程 A. 进程是程序在操作系统中的一次执行过程&#xff0c;系统进行资源分配和调度的一个独立单位。B. 线程是进程的一个执行实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。C.一个进程可以创建和撤销多个线程;同一个进程中…...