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

3D渲染原理及朴素JavaScript实现【不使用WebGL】

在网页中显示图像和其他平面形状非常容易。 然而,当涉及到显示 3D 形状时,事情就变得不那么容易了,因为 3D 几何比 2D 几何更复杂。 为此,你可以使用专用技术和库,例如 WebGL 和 Three.js。

但是,如果你只想显示一些基本形状(例如立方体),则不需要这些技术。 此外,它们不会帮助你了解它们的工作原理以及我们如何在平面屏幕上显示 3D 形状。

本教程的目的是解释如何在没有 WebGL 的情况下为 Web 构建一个简单的 3D 引擎。 我们将首先了解如何存储 3D 形状。 然后,我们将了解如何在两个不同的视图中显示这些形状。
在这里插入图片描述

在线工具推荐: Three.js AI纹理开发包 - YOLO合成数据生成器 - GLTF/GLB在线编辑 - 3D模型格式在线转换 - 可编程3D场景编辑器

1、所有形状都是多面体

虚拟世界与现实世界有一个主要区别:没有什么是连续的,一切都是离散的。 例如,你无法在屏幕上显示完美的圆形,但可以通过绘制具有很多边的正多边形来实现它:边越多,圆就越“完美”。

在 3D 中,这是同样的事情,每个形状都必须用多边形的 3D 等价物来处理:多面体(Polyhedron)。在这种 3D 形状中,我们只能找到平面,而不是球体中的弯曲侧面。 当我们谈论已经是多面体的形状(例如立方体)时,这并不奇怪,但当我们想要显示其他形状(例如球体)时,需要记住这一点。

在这里插入图片描述

2、存储多面体

为了猜测如何存储多面体,我们必须记住如何在数学中识别这样的东西。 你在上学期间肯定已经学过一些基本的几何图形。 例如,要识别一个正方形,你可以将其称为 ABCD,其中 A、B、C 和 D 指的是构成正方形每个角的顶点。

对于我们的 3D 引擎来说,也是一样的。 我们将从存储形状的每个顶点开始。 然后,这个形状将列出它的面,每个面将列出它的顶点。

为了表示一个顶点,我们需要正确的结构。 这里我们创建一个类来存储顶点的坐标。

var Vertex = function(x, y, z) {this.x = parseFloat(x);this.y = parseFloat(y);this.z = parseFloat(z);
};

现在可以像任何其他对象一样创建顶点:

var A = new Vertex(10, 20, 0.5);

接下来,我们创建一个代表多面体的类。 我们以立方体为例。 该类的定义如下,后面有解释。

var Cube = function(center, size) {// Generate the verticesvar d = size / 2;this.vertices = [new Vertex(center.x - d, center.y - d, center.z + d),new Vertex(center.x - d, center.y - d, center.z - d),new Vertex(center.x + d, center.y - d, center.z - d),new Vertex(center.x + d, center.y - d, center.z + d),new Vertex(center.x + d, center.y + d, center.z + d),new Vertex(center.x + d, center.y + d, center.z - d),new Vertex(center.x - d, center.y + d, center.z - d),new Vertex(center.x - d, center.y + d, center.z + d)];// Generate the facesthis.faces = [[this.vertices[0], this.vertices[1], this.vertices[2], this.vertices[3]],[this.vertices[3], this.vertices[2], this.vertices[5], this.vertices[4]],[this.vertices[4], this.vertices[5], this.vertices[6], this.vertices[7]],[this.vertices[7], this.vertices[6], this.vertices[1], this.vertices[0]],[this.vertices[7], this.vertices[0], this.vertices[3], this.vertices[4]],[this.vertices[1], this.vertices[6], this.vertices[5], this.vertices[2]]];
};

使用这个类,我们可以通过指示其中心和边的长度来创建虚拟立方体:

var cube = new Cube(new Vertex(0, 0, 0), 200);

Cube 类的构造函数首先生成立方体的顶点,根据指示中心的位置计算。 一个模式会更清晰,所以请看下面我们生成的八个顶点的位置:
在这里插入图片描述

然后,我们列出面。 每个面都是一个正方形,因此我们需要为每个面指示四个顶点。 在这里,我选择用数组来表示一个面,但如果你需要,也可以为此创建一个专用类。

当我们创建一个面时,我们使用四个顶点。 我们不需要再次指示它们的位置,因为它们存储在 this.vertices[i] 对象中。 这很实用,但我们这样做还有另一个原因。

默认情况下,JavaScript 尝试使用尽可能少的内存。 为了实现这一点,它不会复制作为函数参数传递的对象,甚至不会复制存储到数组中的对象。 对于我们的例子来说,这是完美的行为。

事实上,每个顶点都包含三个数字(它们的坐标),如果我们需要添加它们,还可以加上几个方法。 如果对于每个面,我们存储顶点的副本,我们将使用大量内存,这是无用的。 在这里,我们拥有的只是引用(Reference):坐标(和其他方法)被存储一次,并且仅存储一次。 由于每个顶点由三个不同的面使用,通过存储引用而不是副本,我们将所需的内存除以3(或多或少)!

3、我们需要三角形吗?

如果你玩过 3D(例如使用 Blender 等软件,或 WebGL 等库),也许听说过我们应该使用三角形。 在这里,我选择不使用三角形。

这种选择背后的原因是本文是对该主题的介绍,我们将展示立方体等基本形状。 在我们的例子中,使用三角形来显示正方形比其他任何东西都更复杂。

但是,如果你计划构建一个更完整的渲染器,那么需要知道,一般来说,三角形是首选。 这有两个主要原因:

  • 纹理:出于某些数学原因,为了在面上显示图像,我们需要三角形;
  • 奇怪的面:三个顶点总是在同一平面上。 但是,你可以添加不在同一平面中的第四个顶点,并且可以创建连接这四个顶点的面。 在这种情况下,要绘制它,我们别无选择:我们必须将其分成两个三角形(只需尝试用一张纸即可!)。 通过使用三角形,你可以保持控制并选择分割发生的位置)。

4、作用于多面体

存储引用而不是副本还有另一个优点。 当我们想要修改多面体时,使用这样的系统也会将所需的操作数除以三。

为了理解为什么,让我们再回忆一下我们的数学课。 当你想要平移一个正方形时,你并没有真正平移(translate)它。 事实上,你平移四个顶点,然后加入平移。

在这里,我们也会做同样的事情:我们不会触摸脸部。 我们对每个顶点应用所需的操作,然后就完成了。 当面使用参考时,面的坐标会自动更新。 例如,看看我们如何平移之前创建的立方体:

for (var i = 0; i < 8; ++i) {cube.vertices[i].x += 50;cube.vertices[i].y += 20;cube.vertices[i].z += 15;
}

我们知道如何存储 3D 对象以及如何对它们进行操作。 现在是时候看看如何查看它们了! 但是,首先我们需要一点理论背景,以便理解我们要做什么。

5、投影

目前,我们存储 3D 坐标。 然而,屏幕只能显示 2D 坐标,因此我们需要一种方法将 3D 坐标转换为 2D 坐标:这就是我们在数学中所说的投影。 3D 到 2D 投影是由称为虚拟相机的新对象进行的抽象操作。 该相机获取 3D 对象并将其坐标转换为 2D 坐标,将它们发送到渲染器,渲染器将它们显示在屏幕上。 我们假设我们的相机放置在 3D 空间的原点,因此它的坐标是 (0,0,0)。

从本文开始,我们就讨论了坐标,由三个数字表示:x、y 和 z。 但要定义坐标,我们需要一个基础:z是垂直坐标吗? 它是到顶部还是到底部? 没有通用的答案,也没有惯例,因为事实是你可以选择想要的任何内容。 唯一需要记住的是,当你对 3D 对象进行操作时,必须保持一致,因为公式会根据它而变化。 在本文中,我选择了可以在上面的立方体架构中看到的基础:x 从左到右,y 从后到前,z 从下到上。

现在,我们知道该怎么做了:我们有 (x,y,z) 基础上的坐标,为了显示它们,我们需要将它们转换为 (x,z) 基础上的坐标:因为它是一个平面,我们将 能够显示它们。

不只有一种投影。 更糟糕的是,存在无数种不同的投影! 在本文中,我们将看到两种不同类型的投影,它们是实践中最常用的投影。

6、如何渲染我们的场景

在投影我们的3D对象之前,让我们先编写显示它们的函数。 该函数接受一个数组作为参数,该数组列出了要渲染的对象、必须用于显示对象的画布上下文以及在正确位置绘制对象所需的其他详细信息。

该数组可以包含多个要渲染的对象。 这些对象必须尊重一件事:有一个名为 faces 的公共属性,它是一个列出对象所有面的数组(就像我们之前创建的立方体)。 这些面可以是任何东西(正方形、三角形,甚至十二边形,如果你愿意的话):它们只需要是列出其顶点的数组即可。

让我们看一下该函数的代码,然后是解释:

function render(objects, ctx, dx, dy) {// For each objectfor (var i = 0, n_obj = objects.length; i < n_obj; ++i) {// For each facefor (var j = 0, n_faces = objects[i].faces.length; j < n_faces; ++j) {// Current facevar face = objects[i].faces[j];// Draw the first vertexvar P = project(face[0]);ctx.beginPath();ctx.moveTo(P.x + dx, -P.y + dy);// Draw the other verticesfor (var k = 1, n_vertices = face.length; k < n_vertices; ++k) {P = project(face[k]);ctx.lineTo(P.x + dx, -P.y + dy);}// Close the path and draw the facectx.closePath();ctx.stroke();ctx.fill();}}
}

这个函数值得一些解释。 更准确地说,我们需要解释这个 project()函数是什么,以及这些 dx和 dy参数是什么。 剩下的基本上就是列出物体,然后画出每个面。

顾名思义, project() 函数的作用是将 3D 坐标转换为 2D 坐标。 它接受 3D 空间中的顶点并返回 2D 平面中如下定义的顶点:

var Vertex2D = function(x, y) {this.x = parseFloat(x);this.y = parseFloat(y);
};

我没有将坐标命名为 x 和 z,而是选择将 z 坐标重命名为 y,以保持我们在 2D 几何中常见的经典约定,但如果你愿意,也可以保留 z。

project() 的确切内容是我们将在下一节中看到的内容:它取决于你选择的投影类型。 但无论这种类型是什么, render() 函数都可以保持原样。

一旦我们在平面上有了坐标,就可以将它们显示在画布上,这就是我们所做的…有一个小技巧:我们并没有真正绘制由 project()函数返回的实际坐标。

事实上, project() 函数返回虚拟 2D 平面上的坐标,但其原点与我们为 3D 空间定义的坐标原点相同。 然而,我们希望原点位于画布的中心,这就是我们平移坐标的原因:顶点 (0,0) 不在画布的中心,但 (0 + dx,0 + dy) 是, 如果我们明智地选择 dx 和 dy。 由于我们希望 (dx,dy) 位于画布的中心,因此我们没有真正的选择,因此我们定义 dx = canvas.width / 2 和 dy = canvas.height / 2。

最后,最后一个细节:为什么我们使用 -y 而不是直接使用 y? 答案在于我们选择的基础:z 轴指向顶部。 然后,在我们的场景中,具有正 z 坐标的顶点将向上移动。 然而,在画布上,y 轴指向底部:具有正 y 坐标的顶点将向下移动。 这就是为什么我们需要将画布上的 y 坐标定义为场景的 z 坐标的反转。

现在 render() 函数已经很清楚了,是时候看看 project() 了。

7、正交视图

让我们从正交投影(Orthographic Projection)开始。 因为它是最简单的,所以很容易理解我们要做什么。

我们有三个坐标,但我们只需要两个。 在这种情况下,最简单的做法是什么? 删除其中一个坐标。 这就是我们在正交视图中所做的。 我们将删除表示深度的坐标:y 坐标。

function project(M) {return new Vertex2D(M.x, M.z);
}

你现在可以测试自本文开头以来我们编写的所有代码:有效! 恭喜,你刚刚在平面屏幕上显示了 3D 对象!

该功能演示可以在这个CodePen查看,你可以通过鼠标旋转立方体来与其进行交互:

在这里插入图片描述

有时,正交视图正是我们想要的,因为它具有保留平行线的优点。 然而,这并不是最自然的景象:我们的眼睛并不是这样看的。 这就是为什么我们会看到第二个投影:透视图。

6、透视图

透视投影(Perspective Projection)比正交投影稍微复杂一些,因为我们需要做一些计算。 然而,这些计算并不那么复杂,你只需要知道一件事:如何使用截距定理(Intercept Theorem)。

为了理解原因,让我们看一下表示正交视图的模式。 我们以正交方式将点投影到平面上:
在这里插入图片描述

但是,在现实生活中,我们的眼睛的行为更像是以下模式:

在这里插入图片描述

基本上我们有两个步骤:

  • 将原始顶点和相机原点连接起来;
  • 投影是这条线与平面的交线。
  • 与正交视图不同,在透视视图中投影平面的确切位置很重要:如果将平面放置在远离相机的位置,则不会获得与将其放置在靠近相机时相同的效果。 这里我们将其放置在距相机距离 d 处。

从 3D 空间中的顶点 M(x,y,z) 开始,我们要计算平面上投影 M’ 的坐标 (x’,z’)。
在这里插入图片描述

为了猜测我们将如何计算这些坐标,让我们从另一个角度来看与上面相同的模式,但从顶部看:
在这里插入图片描述

我们可以识别截距定理中使用的配置。 在上面的模式中,我们知道一些值:x、y 和 d 等。我们想要计算 x’,因此我们应用截距定理并得到这个方程: x’ = d / y * x。

现在,如果你从侧面观察同一场景,会得到一个类似的模式,允许通过 z、y 和 d 获得 z’ 的值: z’ = d / y * z。

我们现在可以使用透视图编写 project()函数:

function project(M) {// Distance between the camera and the planevar d = 200;var r = d / M.y;return new Vertex2D(r * M.x, r * M.z);
}

这个功能可以在这个CodePen实例中进行测试,你可以再次与立方体进行交互:
在这里插入图片描述

7、结束语

我们的(非常基本的)3D 引擎现在已准备好显示我们想要的任何 3D 形状。 你可以采取一些措施来增强它。 例如,我们可以看到自己形状的每一个面,甚至是后面的脸。 要隐藏它们,你可以实施背面剔除(back-face culling)。

另外,我们没有讨论纹理(texture)。 在这里,我们所有的形状都具有相同的颜色。 例如,你可以通过在对象中添加颜色属性来更改它,以了解如何绘制它们。 你甚至可以为每个面选择一种颜色,而无需更改很多内容。 你还可以尝试在面上显示图像。 然而,这更困难,并且详细说明如何做这样的事情需要整篇文章。

其他事情可以改变。 我们将相机放置在空间的原点,但你可以移动它(在投影顶点之前需要更改基础)。 另外,这里绘制了放置在相机后面的顶点,这不是我们想要的。 裁剪平面可以解决这个问题(易于理解,但不太容易实现)。

正如你所看到的,我们在这里构建的 3D 引擎还远未完成,这也是我自己的解释。 你可以添加自己的其他类:例如,Three.js 使用专用类来管理相机和投影。 此外,我们使用基本数学来存储坐标,但如果你想创建更复杂的应用程序,并且例如需要在帧期间旋转大量顶点,你将不会获得流畅的体验。 为了优化它,你将需要一些更复杂的数学:齐次坐标和四元数。


原文链接:3D渲染原理与JS实现 — BimAnt

相关文章:

3D渲染原理及朴素JavaScript实现【不使用WebGL】

在网页中显示图像和其他平面形状非常容易。 然而&#xff0c;当涉及到显示 3D 形状时&#xff0c;事情就变得不那么容易了&#xff0c;因为 3D 几何比 2D 几何更复杂。 为此&#xff0c;你可以使用专用技术和库&#xff0c;例如 WebGL 和 Three.js。 但是&#xff0c;如果你只…...

解决《荒野大镖客》提示emp.dll文件丢失问题,总结5个修复方法

在当今数字时代&#xff0c;游戏已经成为人们休闲娱乐的重要方式。作为一名游戏爱好者&#xff0c;笔者在近期体验《荒野大镖客》这款游戏时&#xff0c;遇到了一个令人苦恼的问题——emp.dll文件丢失。这个问题让游戏的无法启动进行。本文将围绕这一问题&#xff0c;探讨其原因…...

maven重新加载后Target bytecode version总是变回1.8

现象 Load Maven Changes后 Settings - Build, Execution, Deployment - Java Compiler - Target bytecode version总是变为1.8 Project Structure - Modules - Language level总是变为1.8 解决方法 方法一 pom.xml中包含 <project>[...]<build>[...]<plug…...

react+星火大模型,构建上下文ai问答页面(可扩展)

前言 最近写的开源项目核心功能跑通了&#xff0c;前两天突发奇想。关于项目可否介入大模型来辅助用户使用平台&#xff0c;就跑去研究了最近比较活火的国内大模型–讯飞星火大模型。 大模型api获取 控制台登录 地址&#xff1a;https://console.xfyun.cn/app/myapp 新建应…...

python---设计模式

python中设计模式-单例模式 基于__new__方法实现 第一个设计&#xff1a; class MySingleton:def __init__(self):passdef __new__(cls, *args, **kwargs):passmysingleton1 MySingleton() mysingleton2 MySingleton() print(mysingleton1) print(mysingleton2) print(id(…...

Java编写xml文件时,文件中特殊字符如何解决?

有一个使用Java创建XML文件的需求&#xff0c;但标签里面有以下特殊字符<、>、&等 在未解决之前&#xff0c;创建出的XML是这样的 <?xml version"1.0" encoding"UTF-8"?><actionlist><update><jobno>1111</jobno&…...

vue3 ts pinia openapi vue-query pnpm docker前端架构小记

1.引言 开发中&#xff0c;我们是否经常遇到以下痛点&#xff1a; 项目越大&#xff0c;启动和热更新越来越慢&#xff0c;启动都要花个3-5分钟以上没有类型保障&#xff0c;接口返回的Object不拿到真实数据都不知道有哪些字段&#xff0c;接手别人js项目(无类型)很痛苦需要手…...

ARM day4

LED灯亮灭控制 .text .global _start _start: 1ldr r0,0x50000a28ldr r1,[r0]orr r1,r1,#(0x3<<4)str r1,[r0] 2ldr r0,0x50006000ldr r1,[r0]bic r1,r1,#(0x3<<20)orr r1,r1,#(0x1<<20)bic r1,r1,#(0x3<<16)orr r1,r1,#(0x1<<16)str r1,[r0]…...

3.30每日一题(多元函数微分学)

1、判断连续&#xff1a;再分界点的极限值等于该点的函数值&#xff1b; 如何求极限值&#xff1a; 初步判断&#xff1a;分母都为二次幂开根号&#xff0c;所以分母为一次幂&#xff1b;分子为二次&#xff0c;一般来说整体为0&#xff1b; 如何说明极限为零&#xff08;常用…...

《OSTEP》条件变量(chap30)

〇、前言 本文是对《OSTEP》第三十章的实践与总结。 一、条件变量 #include <pthread.h> #include <stdio.h> #include <assert.h>int buffer; int count 0; // 资源为空// 生产,在 buffer 中放入一个值 void put(int value) {assert(count 0);count 1…...

MySQL的索引和复合索引

由于MySQL自动将主键加入到二级索引&#xff08;自行建立的index&#xff09;里&#xff0c;所以当select的是主键或二级索引就会很快&#xff0c;select *就会慢。因为有些列是没在索引里的 假设CA有1kw人咋整&#xff0c;那我这个索引只起了前一半作用。 所以用复合索引&am…...

关于mac下pycharm旧版本没删除的情况下新版本2023安装之后闪退

先说结论&#xff0c;我用的app cleaner 重新删除的pycharm &#xff0c;再重新安装即可。在此记录一下 之前安装的旧版的2020的pycharm&#xff0c;因为装不了新的插件&#xff0c;没办法就升级了。新装2023打开之后闪退&#xff0c;重启系统也不行&#xff0c;怀疑是一起破解…...

Django中如何让DRF的接口针对前后台返回不同的字段

在Django中&#xff0c;使用Django Rest Framework&#xff08;DRF&#xff09;时&#xff0c;可以通过序列化器&#xff08;Serializer&#xff09;和视图&#xff08;View&#xff09;的组合来实现前后台返回不同的字段。这通常是因为前后台对数据的需求不同&#xff0c;或者…...

【机器学习】Kmeans聚类算法

一、聚类简介 Clustering (聚类)是常见的unsupervised learning (无监督学习)方法&#xff0c;简单地说就是把相似的数据样本分到一组&#xff08;簇&#xff09;&#xff0c;聚类的过程&#xff0c;我们并不清楚某一类是什么&#xff08;通常无标签信息&#xff09;&#xff0…...

getid3 获取视频时长

1、首先&#xff0c;我们需要先下载一份PHP类—getid3https://codeload.github.com/JamesHeinrich/getID3/zip/master 2.我在laravel6.0 中使用 需要在composer.json 自动加载 否则系统访问不到 在命令行 执行 composer dump-autoload $getID3 new \getID3();//视频文件需要放…...

如何知道一个程序为哪些信号注册了哪些信号处理函数?

https://unix.stackexchange.com/questions/379694/is-there-a-way-to-know-if-signals-are-present-in-your-application-and-which-sign 使用 strace...

34 mysql limit 的实现

前言 这里来看一下 我们常见的 mysql 分页的 limit 的相的处理 这个问题的主要是来自于 之前有一个需要处理 大数据量的数据表的信息, 将数据转移到 es 中 然后就是用了最简单的 “select * from tz_test limit $pageOffset, $pageSize ” 来分页处理 但是由于 数据表的数…...

jbase实现申明式事务

对有反射的语言&#xff0c;申明式事务肯定不可少。没必要没个人都try&#xff0c;catch写事务&#xff0c;写的不好的话还经常容易锁表&#xff0c;为此给框架引入申明式事务。申明式既字面意思&#xff0c;在需要事务的方法前面加一个申明&#xff0c;那么框架保证事务。 首…...

如何在在线Excel文档中规范单元格输入

在日常的工作中&#xff0c;我们常常需要处理大量的数据。为了确保数据的准确性和可靠性。我们需要对输入的数据进行规范化和验证。其中一个重要的方面是规范单元格输入。而数据验证作为Excel中一种非常实用的功能&#xff0c;它可以帮助用户规范单元格的输入&#xff0c;从而提…...

力扣138:随机链表的复制

力扣138&#xff1a;随机链表的复制 题目描述&#xff1a; 给你一个长度为 n 的链表&#xff0c;每个节点包含一个额外增加的随机指针 random &#xff0c;该指针可以指向链表中的任何节点或空节点。 构造这个链表的 深拷贝。 深拷贝应该正好由 n 个 全新 节点组成&#xff…...

理解 MCP 工作流:使用 Ollama 和 LangChain 构建本地 MCP 客户端

&#x1f31f; 什么是 MCP&#xff1f; 模型控制协议 (MCP) 是一种创新的协议&#xff0c;旨在无缝连接 AI 模型与应用程序。 MCP 是一个开源协议&#xff0c;它标准化了我们的 LLM 应用程序连接所需工具和数据源并与之协作的方式。 可以把它想象成你的 AI 模型 和想要使用它…...

系统设计 --- MongoDB亿级数据查询优化策略

系统设计 --- MongoDB亿级数据查询分表策略 背景Solution --- 分表 背景 使用audit log实现Audi Trail功能 Audit Trail范围: 六个月数据量: 每秒5-7条audi log&#xff0c;共计7千万 – 1亿条数据需要实现全文检索按照时间倒序因为license问题&#xff0c;不能使用ELK只能使用…...

JVM垃圾回收机制全解析

Java虚拟机&#xff08;JVM&#xff09;中的垃圾收集器&#xff08;Garbage Collector&#xff0c;简称GC&#xff09;是用于自动管理内存的机制。它负责识别和清除不再被程序使用的对象&#xff0c;从而释放内存空间&#xff0c;避免内存泄漏和内存溢出等问题。垃圾收集器在Ja…...

学习STC51单片机31(芯片为STC89C52RCRC)OLED显示屏1

每日一言 生活的美好&#xff0c;总是藏在那些你咬牙坚持的日子里。 硬件&#xff1a;OLED 以后要用到OLED的时候找到这个文件 OLED的设备地址 SSD1306"SSD" 是品牌缩写&#xff0c;"1306" 是产品编号。 驱动 OLED 屏幕的 IIC 总线数据传输格式 示意图 …...

HBuilderX安装(uni-app和小程序开发)

下载HBuilderX 访问官方网站&#xff1a;https://www.dcloud.io/hbuilderx.html 根据您的操作系统选择合适版本&#xff1a; Windows版&#xff08;推荐下载标准版&#xff09; Windows系统安装步骤 运行安装程序&#xff1a; 双击下载的.exe安装文件 如果出现安全提示&…...

Robots.txt 文件

什么是robots.txt&#xff1f; robots.txt 是一个位于网站根目录下的文本文件&#xff08;如&#xff1a;https://example.com/robots.txt&#xff09;&#xff0c;它用于指导网络爬虫&#xff08;如搜索引擎的蜘蛛程序&#xff09;如何抓取该网站的内容。这个文件遵循 Robots…...

Swagger和OpenApi的前世今生

Swagger与OpenAPI的关系演进是API标准化进程中的重要篇章&#xff0c;二者共同塑造了现代RESTful API的开发范式。 本期就扒一扒其技术演进的关键节点与核心逻辑&#xff1a; &#x1f504; 一、起源与初创期&#xff1a;Swagger的诞生&#xff08;2010-2014&#xff09; 核心…...

GC1808高性能24位立体声音频ADC芯片解析

1. 芯片概述 GC1808是一款24位立体声音频模数转换器&#xff08;ADC&#xff09;&#xff0c;支持8kHz~96kHz采样率&#xff0c;集成Δ-Σ调制器、数字抗混叠滤波器和高通滤波器&#xff0c;适用于高保真音频采集场景。 2. 核心特性 高精度&#xff1a;24位分辨率&#xff0c…...

html-<abbr> 缩写或首字母缩略词

定义与作用 <abbr> 标签用于表示缩写或首字母缩略词&#xff0c;它可以帮助用户更好地理解缩写的含义&#xff0c;尤其是对于那些不熟悉该缩写的用户。 title 属性的内容提供了缩写的详细说明。当用户将鼠标悬停在缩写上时&#xff0c;会显示一个提示框。 示例&#x…...

Mobile ALOHA全身模仿学习

一、题目 Mobile ALOHA&#xff1a;通过低成本全身远程操作学习双手移动操作 传统模仿学习&#xff08;Imitation Learning&#xff09;缺点&#xff1a;聚焦与桌面操作&#xff0c;缺乏通用任务所需的移动性和灵活性 本论文优点&#xff1a;&#xff08;1&#xff09;在ALOHA…...