TIDE.JS:三维空间数字化渲染引擎

简介: 为了满足我们三维空间渲染各个业务的需求,并且提供易用SDK方便第三方开发人员快速搭建自己的应用或平台,我们自研了TIDE.JS——一款组件化、轻量化、高性能、大规模的面向空间数字化应用的渲染引擎。

一、背景介绍

近几年,随着3D扫描设备成本降低,三维扫描硬件、3D建模或重建技术以及VR/AR技术的逐渐成熟,三维数字化逐渐开始在生活中普及。如VR看房、VR购物、VR探厂、VR导航、数字沙盘、CIM平台等,并且诞生了一批三维扫描、智慧运维、VR/AR的创业公司。特别是近两年的疫情也加速了三维数字化进程,足不出户在家就可以线上体验的需求变得更加迫切。

d01e0b7992a34df09b5886da1b42a953.png

1.1 一些业务形态(上图部分来自网络):虚拟展厅、数字沙盘、智慧运维


为此,阿里云-人工智能实验室在三维空间数字化和渲染方面做出了许多有益的尝试,并在此领域硕果颇丰,实现了包括3D激光扫描及室内重建服务、城市级高精路网重建技术、全自动BIM翻模算法、城市级倾斜摄影重建算法、智慧建筑/园区/城市等。为了满足我们三维空间渲染各个业务的需求,并且提供易用SDK方便第三方开发人员快速搭建自己的应用或平台,我们自研了TIDE.JS——一款组件化、轻量化、高性能、大规模的面向空间数字化应用的渲染引擎。

二、TIDE.JS引擎

团队在业务探索的过程中,尝试过各种不同的渲染引擎,例如ThreeJS,BabylonJS等,他们都是非常优秀的渲染引擎或者游戏引擎,但是在业务尝试的过程中,发现这些引擎在某些方面无法满足我们的业务需求。我们希望的引擎是:1.一个具有组件架构的渲染引擎、2.底层渲染层可以剥离便于自己掌控细节、3.在保证渲染能力强的前提下需要尽可能的轻量化。用比较通俗的话来说,我们需要一个麻雀虽小五脏俱全的渲染引擎。在这样的目标驱使下,我们开发的引擎具有一下特点:

组件化:TIDE.JS使用了EC/ECS(Entity & Component;Entity Component system)混用的组件开发方式搭建,与unity等主流引擎保持一致。针对拥有unity,unreal,cocos2dx开发经验的同学来说,有效降低了学习成本。下图展示了我们组件化架构,在具体的业务中发挥的作用。组件化设计便于我们沉淀特定业务组件,任意组合功能给其他业务,提高技术复用性。下图(2.1)展示了业务如何沉淀组件和组件的可复用性,以及支持的上层业务。

e8927da775d646fba8d9eb6576ca10fe.png

2.1 引擎的整体层级结构,并示意了各业务沉淀业务组件、组件共享复用的情况

组件由管理器统一进行管理,并执行其周期函数。目前我们支持的周期函数(运行时)如下,足以满足大多数互动需求。Editor模式下,还会有GUI、DrawGizmo、OnSelected等周期函数。周期函数会定义组件的具体行为,比如上图中提到的“倾斜摄影”业务下的“第一人称控制组件”,使用WASD/QE 来控制摄像机的飞行:

它在onStart时缓存要控制的transform对象,在update函数中,去取得键盘状态,并计算transform对象的新姿态;开发者不用关心onStart,update在后台是如何被调用的,只需要知道onStart在组件生命周期里只执行一次,而update每帧执行一次就足够了。

这个“第一人称控制”组件,如果添加到摄像机上,就可以让摄像机具备响应WASD/QE操控的能力;如果添加到“万花筒业务”的虚拟导购对象上,也可以让虚拟导购对象接受WASD/QE的控制。这也是组件复用的基本含义。

50e3c4775f22474caa61db18a6031d1a.png

2.2 组件的周期函数(runtime)

组件的另一个核心主题,就是序列化/反序列化,这个操作是为了将“逻辑”复用。我们是通过Prefab/Package系统来完成这个任务的。Prefab管理器将Entity 对象序列化成一个json,只包括每个组件的参数,并不包括组件的代码。Package会将这个json、相关组件的代码、组件引用的资源文件等,打包到一起,用于项目间的复用。关于Prefab/Package系统本文不再累述,会在稍后的系列文章中做详细介绍。下图展示了Prefab与“逻辑复用”的情况:

 a.互动A:产生了一个不错的机器人,有符合业务需求的行为:飞行、说话、做鬼脸等等;

 b.用户通过编辑器也生成了一个不错的女孩,并配置了行为;

 c.prefab.tojson 将机器人/女孩导出成一个json,并以组件的形式,序列化其业务逻辑;

 d.在当前的互动中,通过prefab.fromJson 能将其放置到当前场景,它的行为模式与之前一模一样。

333f9a79d74642cf8da8b0d1d1b19b84.png

2.3 prefab进行资源复用

对于AI的行为,我们也内置了两种典型的处理方案:有限状态机、行为树,并在实际的业务中使用起来;状态机的管理器也是基于组件的,所以Prefab管理器会将其直接序列化到json,并支持其他业务复用。更多关于有限状态机、行为树的内容将在之后的系列文章中做详细介绍。

轻量化:组件化的另一个优势是可以大大降低代码体积。每个业务只需要包含他需要的组件,以及渲染所需的基础渲染内核,即LLEF层(包名:@ali/llef)。LLEF层体积非常小(<50KB),是Tidejs使用的基础渲染器,与上层业务逻辑解耦,直接与图形api打交道,包括对基本的WebGL1.0/2.0的封装以及高效实现所有基础的绘图指令。在将来,可以使用任何新的渲染器内核替换,无论是基于webgl,webassembly,还是webgpu,只要遵循相应的接口即可。

高性能:我们的引擎在渲染性能上做出了很多努力,TIDE.JS引擎主要在基础渲染性能和绘制性能上做出了一些优化。
在基础渲染性能上,我们通过三个方向进行改进,1.降低图形API的调用频率,2.提升CPU端的更新速度,3.尽可能的减少drawcall。首先,我们通过动态记录状态并通过渲染状态组合,尽可能做到每次drawcall的时候各类状态相似度最大化,从而尽可能的减少图形API调用量。经过对比,我们当前平均每drawcall的图形API调用量是不做优化前完整调用的37%。其次,对于CPU端的更新消耗,我们通过EC架构及面向数据而非面向对象的编程模式来进行优化,这样对于大批量物体的更新,可以做到更好的缓存一致性从而提升性能。最后,对于drawcall数量的优化,我们通过对物体进行视锥体裁剪和动态合批来进行优化。在采取以上的优化措施后,我们的渲染引擎可以在基础绘图性能上有一个较大的优势,同时在非基础绘图性能上,我们引擎开放了完整的渲染流水线自定义,用户可以自己编写延迟渲染管线,从而在特定业务条件下实现性能的进一步提升。
在绘制性能上,我们使用了完整GPU驱动的骨骼动画,不同于其他引擎CPU驱动或者部分GPU驱动的骨骼动画,我们的骨骼动画系统利用GPU的高并行性完整的使用GPU进行加速。经过比较,在特定移动端设备上,某引擎的骨骼系统在处理47根骨骼时帧率已经掉到了27FPS,而我们的系统还是能维持在60FPS,从而赋能业务方可以在更多的平台上推广带有骨骼动画的业务。

d64e788ea3234e4894f2e68d2d9e14ab.gif

2.4 我们引擎骨骼动画系统驱动的高性能骨骼动画及交互

大规模:为了满足园区、市区、城市级规模的大场景渲染,我们引擎提供了多种大规模场景渲染的组件。包括LOD倾斜摄影渲染组件、点云渲染组件、矢量化高精路网组件等,可以支持城市级倾斜摄影模型/点云/高精地图渲染。为了高效的渲染大规模数据,我们从以下几个点出发对渲染流程做出了优化:

1.渐进式LOD数据和加载。常规的LOD(Level of Details)数据是层与层之间独立的,数据没有相关性,因此会产生不少的冗余数据。为此,我们从数据源出发,转换成我们自己的数据相关性LOD,避免数据冗余。具体来说,每一层的构建都是类似沙漏一层层向下漏的过程,也就是说,当LOD构建完成后,所有层级的LOD数据加起来,刚好是原始的数据,没有冗余。这样做不但可以节省数据传输的带宽,更重要的是,我们可以进行渐进式加载,即当特定时刻相机静止的时候,我们可以渐进式的把所有点云进行渲染,这样对于一些需要高密度的点云渲染但是因为数据量太大无法直接渲染的业务场景,是一个非常实用的解决方案。具体的应用案例会在下文进行阐述。

2.记录式的场景树遍历,大规模场景的树结构往往非常庞大,例如一个城市的数据,树深可以达到15-20层,部分情况下可以达到25层。对如此庞大的树结构进行遍历找到特定节点的父亲和孩子等操作,消耗是非常大的。为了优化这个问题,我们并不存储树本身,而是运行时动态存储每个节点的孩子以及祖先列表,这样可以将单个节点树遍历时间复杂度从O(logN)提升到O(1),从而大大提升运行时树结构的遍历速度,支撑整个大场景的渲染。作为比较,某点云渲染平台渲染点云的时候是每帧从根节点开始遍历整颗树,当树深较大时,这一个遍历操作往往需要1-2s,已经严重阻碍渲染的流程,而我们的算法,则完全没有这方面的性能问题,树的遍历都可以在1帧时间(16ms)内完成。

基于TIDE.JS大规模渲染的能力,我们可以保证城市级别的地图在PC和手机上流畅渲染,例如下图(图2.5)渲染的杭州市点云地图,包含大部分主城区、未来科技城和下沙部分地区。

3407a43e44174456b384fd66d88cac21.png

f782cff490674636b3bdd3d56c5b0018.png

2.5 杭州市路网点云高精渲染

高质量:渲染的最终输出是屏幕上的图像,因此渲染的质量是一个引擎所必须关注的,我们的引擎从各个方面入手提高渲染的质量。

首先,是基本的光照效果,这一部分,我们使用了完整PBR(Physicallly-Based Rendering)算法配和IBL(Image-Based Lighting)算法处理物体的基本光照,该光照模型进行完整实现后可以涵盖多种材质和多种光源的光照计算,如图2.6所示。有了这样基于物理的光照后,我们可以提供一个在场景中比较真实的渲染物体的能力。

00f3ce46b02441dc8b33b18a26ed86ab.png

2.6 同一物体在不同材质下使用一套光照算法模型进行模拟的效果

此外,由于基础光照计算的是直接光照,还没有计算全局光照的光照信息,因此在视觉上会有一种空间层次感不够明显的感觉,而如果计算全局光照往往需要离线的光线追踪进行计算,在计算量有限的前提下,为了提升细节的层次感,我们使用DO(Directional Occlusion)算法配合自研的屏幕空间G2G(Glossy to Glossy)算法来模拟物体被全局光照遮蔽以及基本的全局光照情况。如图2.7所示,该算法通过在屏幕空间中进行环境光照的拾取和积分从而计算空间临域内对当前渲染点的间接光照贡献,从而拟合计算当前渲染点接收的全局光照信息。

808761b9a7b344028166fcb0f5090064.png

4077ecf524884a8faef71d4628a3b98e.png

89167d0112b149c7ac7d3278039ac2fd.png

2.7 Glossy to Glossy Directional Occlusion算法原理示意以及效果展示

同时,为了在大规模场景中进行光照计算,光在空气中传播产生的散射以及投射的阴影计算也会变得复杂,对于散射,我们实现了预计算的大气散射积分系统,从而可以模拟太阳光在各个时刻通过大气层达到地面时的光照散射质量(图2.8左),从而提升大场景的绘制效果。而对于阴影计算,传统的通过单张阴影图来计算全局阴影会因为场景规模的提升而无法取得离线的渲染质量,为了解决这个问题,我们引入了CSM(cascaded shadow mapping)算法来进行阴影的渲染,该算法通过对当前视锥体进行分析,实时的从光源角度切分视锥体(图2.8右),并生成多个阴影图来计算阴影,这样可以保证我们视野内的阴影质量永远是维持清晰状态的。

4ea20142d2ea40cf8f62f4ef29c4ff4d.png

cbb3fcf8f7a54aa1ab80b71960cd5327.png

2.8 上:大气散射效果示例,下:视锥体切分示例

下面介绍一下TIDE.JS引擎在各类空间业务中的应用。

三、支撑业务成果展示

3.1 全息店铺

全息店铺项目,致力于服务万千中小企业、商家做店铺、工厂线上数字化,利用线下设备扫描、云端构建构建数字资产,提供室内场景3D数字化云服务(详情见阿里云服务市场),将线下实景搬到线上,为客户提供VR看房、VR购物、VR探厂等各类场景的服务,在手机、电脑等各类终端上即可以三维实景方式呈现线下场景。目前已经和ICBU、CBU、三维家等进行合作,服务了全国各地的门数千家中小企业门店数字化。参考案例可访问我们的全息世界主页https://www.holoworld.com.cn/

7fc342a694c5458db0f88548dd58307f.png

3.1 全息店铺解决方案

该终端展示基于我们的引擎实现,主要是H5为主,当然在特定业务下,也可以作为native的内嵌页面使用,比如云展的3D展厅部分。终端展示我们默认提供三种经典模式:鸟瞰、俯视图(户型图)、全景。

9b4b725b621e465baf5957d24cb3fa63.png


3.2 三种模式:鸟瞰、俯视图(户型图)、全景

在具体的交互上,我们提供了包括自动漫游、讲房、3D弹幕、虚拟导购等有趣味的互动,针对每种业务场景,我们分别定制和搭配了多种模板样式和交互方式,以增强用户体验,提高用户粘性。

aee0ab46d0fe4a05a8bec405ae84d924.png


3.3 互动示例:3d弹幕、虚拟导购与跟随漫游。

目前我们上线的链接已经达到万级,支持服务了十几家生态伙伴,包括但不限于下面所列:三维家、口碑、CBU、ICBU、手淘等;项目也涵盖了云展、淘宝心选、义乌商场、中小企业建站、云栖大会、95公益周等几个项目。为了支撑生态伙伴自己建设全息店铺,我们也提供了两种SDK:数据中台、 前端H5,并支撑了多个实际业务的落地。

下面介绍一下我们TIDE.JS引擎在全息店铺支持的各类业务。

虚拟厂房

在疫情期间,为了帮助阿里巴巴国际买家客户足不出户就可以远程探厂验真,ICBU借助我们的三维扫描技术,帮助国内卖家完成厂房数字化,将厂房搬到线上,使他们探厂逛厂成为可能,促进洽商转化。我们的TIDE.JS引擎提供了同屏带看、自动漫游、语音解说、3D视频融合、3D标签等功能(部分功能即将上线),进一步提升用户体验和运营转化率。

a1f7a2616ae8445bba08dba724f26114.png

3.4 全息店铺应用案例:ICBU 3D Showroom

虚拟展会

我们还在人工智能实验室的云展会以及其他展会项目中进行了合作,帮助参展企业或展会主办方将他们的展览厅和展会搬上线上。我们引擎定制化开发的3D弹幕、虚拟角色动画、360商品展示等一系列3D互动元素提升了线上逛展的乐趣,让参会人员用丰富多样的交互方式了解展会活动、了解参展企业产品。我们引擎还支持3D虚拟展厅,让客户在线交互式定制自己的展厅海报内容,打造企业对外宣传的3D名片。

a2f26f6de0a549e1888df85348694d76.png

3.5 虚拟展会应用案例

虚拟商店

我们还和手机淘宝的很多客户进行合作,通过我们的3D实景技术将他们线下的实体店搬到手淘,让普通用户在家就可以享受身临其境逛实体店的体验。虚拟堆头、3D虚拟导购、3D商品展示、自动漫游、3D音/视频、虚拟地图和导航等一系列技术提升逛商场和看商品的用户体验。

23038be99725439dad2c20048c42f27f.png

3.6 全息店铺应用案例:95公益周、3D全景虚拟展厅、成都1919旗舰店、背景华伦天奴店

3.2 房产展销

房产展销业务带给了用户具有较强沉浸感以及交互性的购房体验,例如用户可以全3D的查看整个楼盘的周边配套,基于我们的大气系统、PBR、SSAO、CSM等技术,进入到某一个特定房间内查看24小时的阳光投射变化以及窗外的景色的真实感渲染,从而让一房一景的购房体验得以实现,而在室外,甚至可以自己在楼盘地面行走体验楼盘的绿化和设施覆盖。我们的引擎在房产展销项目中支撑了以下业务:

数字沙盘
我们的引擎在数字沙盘业务中提供了一种全息房产能力,在技术上,全息房产渲染中很重要的一点是强调真实性,尤其是能和开发商的效果图打通,因此整体的渲染风格是一种带有效果图风格的真实感渲染风格。我们通过在各个环节加入特定的算法来达到这样的渲染效果:首先是基本的光照材质,我们基于PBR并在此基础上调整算法细节,以支持玻璃,墙体,草坪等材质的真实感渲染。其次是大场景的阴影,为了能够准确表现出整个小区的宏观阴影以及各个屋子的细节阴影照射情况,我们采用了CSM阴影算法技术,该技术可以动态的依据相机视锥体渲染合适的阴影图从而使我们看到的永远是有较多细节的阴影。最后,为了营造整个小区的空间层次感,我们使用了屏幕空间环境光遮蔽技术(SSAO)以及环境雾效果,使得几何体的全局光照遮蔽更加真实且远距离建筑产生一定的模棱层次感。

77b865b93346431aaf63d652c24bd787.png

3.7 TIDE.JS引擎渲染的数字沙盘效果

虚拟看房
VR看房近几年逐渐成为各类房源APP的标配,相较于传统的房屋照片,3D房屋户型+360高清全景模式,可以更直观的展示房屋空间信息。结合我们引擎提供的中介语音带看、3D量尺、VR虚拟带看、3D家电随心摆等功能,将会帮助购房或租房者更真实的了解房屋户型尺寸和场景细节。该功能今年上线。

3.3 空间资产管理平台

我们引擎同样支撑了空间资产管理平台项目,为建筑、园区、城市等提供3D空间资产管理与渲染服务。结合我们团队BIM翻模、三维重建等3D数据生产服务,将我们生产的BIM模型、倾斜摄影模型、三维室内扫描模型等3D空间地图做为管理平台的底座,向下打通楼宇内各类IoT设备,向上为开发者提供了能耗、安防等运维模块的SDK,以支持第三方开发者各类场景的运维、管理和渲染开发。下面介绍一下我们TIDE.JS引擎在空间资产管理平台支持的各类业务。

智慧楼宇/园区
在智慧楼宇业务中,我们的引擎支撑了整个智慧楼宇的前端渲染展示和控制。对于楼宇中的监控,除了通过拾取单个实例来获得其状态之外,我们还需要对监控录像、监控区域进行查看。监控区域的3D渲染可以让楼宇管理者清晰了解到监控是否存在死角、监控更新与选型的需求。结合RFID等定位设备,我们也可以实时展示楼宇中人员和物资的轨迹,并根据规则触发报警。
智慧楼宇的管理中,各设备之间大多数情况下是互不关联、可独立插拔的,借助我们引擎组件化的特性,我们引入插件化设计,便于开发者对于不同设备元素的呈现和插拔,以适应关注不同设备的使用者的需求。此外,我们也将在未来开发规则引擎,以交互式方式定义不同模块间的信号处理规则,从而支持不同部门间的信息联动,如资损问题涉及的安保和物业部门、访客问题涉及的安保和人力部门等。

09681bb356f04e6cbb4ea2486ac8468c.gif

3.8 智慧楼宇业务操作示例


大规模倾斜摄影

我们引擎还提供了大规模和高精度渲染的能力,以支持城市级空间数字渲染。基于我们的3D引擎,我们提供了从建筑到城市不同规模尺度的底图渲染能力,在3D渲染和交互方面,我们通过实时IBL(Image Based Lighting)、动态纹理、PBR、考虑顺序的半透明、Cascaded Shadow Mapping等技术让整个建筑物乃至建筑群更自然、真实地呈现和操作。下面展示了我们渲染杭州市区120平方公里区域的倾斜摄影模型,模型精度5cm内,总数据规模为738GB,能够在PC和部分手机端流畅运行。

3.9 智慧城市业务杭州主城区倾斜摄影模型展示

四、未来展望

未来,我们将会完善全息店铺和空间资产管理平台上的交互和开发模式。
一方面,我们引擎将推动全息店铺服务SaaS化,为我们的合作方优化3D编辑中台交互,提升用户体验。此外,我们还将制定3D数字店铺数据规范,包括场景数据规范、3D内容数据规范,方便其他室内扫描服务商、AR/VR内容服务商接入平台,搭建我们自己的3D内容素材库及生产平台,进一步丰富店铺线上3D运营工具,提升运营转化率和产品价值。
另一方面,我们会继续沉淀空间资产管理平台的技术能力,搭建空间资产管理开发平台,为开发者提供更多IoT的3D渲染及控制逻辑,和材质样式、交互逻辑、场景动画等更丰富的编辑功能。我们也将会推动人工智能实验室BIM翻模、高精路网、倾斜摄影等数据服务无缝接入我们的开发平台,如为BIM翻模提供轻量化引擎、为高精路网提供线上编辑中台,为客户提供数据服务及平台搭建的全套解决方案。

欢迎扫码加入社群

test

相关实践学习
部署Stable Diffusion玩转AI绘画(GPU云服务器)
本实验通过在ECS上从零开始部署Stable Diffusion来进行AI绘画创作,开启AIGC盲盒。
相关文章
|
2月前
|
JavaScript 前端开发 Go
CSS 与 JS 对 DOM 解析和渲染的影响
【10月更文挑战第16天】CSS 和 JS 会在一定程度上影响 DOM 解析和渲染,了解它们之间的相互作用以及采取适当的优化措施是非常重要的。通过合理的布局和加载策略,可以提高网页的性能和用户体验,确保页面能够快速、流畅地呈现给用户。在实际开发中,要根据具体情况进行权衡和调整,以达到最佳的效果。
|
1月前
|
Web App开发 JavaScript 前端开发
Node.js 是一种基于 Chrome V8 引擎的后端开发技术,以其高效、灵活著称。本文将介绍 Node.js 的基础概念
Node.js 是一种基于 Chrome V8 引擎的后端开发技术,以其高效、灵活著称。本文将介绍 Node.js 的基础概念,包括事件驱动、单线程模型和模块系统;探讨其安装配置、核心模块使用、实战应用如搭建 Web 服务器、文件操作及实时通信;分析项目结构与开发流程,讨论其优势与挑战,并通过案例展示 Node.js 在实际项目中的应用,旨在帮助开发者更好地掌握这一强大工具。
46 1
|
1月前
|
数据采集 JavaScript 搜索推荐
服务器端渲染(SSR)(Nuxt+Next.js)
服务器端渲染(SSR)技术在服务器上生成页面HTML,提升首屏加载速度和SEO效果。Nuxt.js和Next.js分别是基于Vue.js和React.js的流行SSR框架。Nuxt.js提供自动化路由管理、页面级数据获取和布局系统,支持SSR和静态站点生成。Next.js支持SSR、静态生成和文件系统路由,通过`getServerSideProps`和`getStaticProps`实现数据获取。SSR的优点包括首屏加载快、SEO友好和适合复杂页面,但也会增加服务器压力、开发限制和调试难度。选择框架时,可根据项目需求和技术栈决定使用Nuxt.js或Next.js。
|
2月前
|
Web App开发 前端开发 JavaScript
JavaScript动态渲染页面爬取——Selenium的使用(一)
JavaScript动态渲染页面爬取——Selenium的使用(一)
73 4
|
2月前
|
Web App开发 数据采集 JavaScript
JavaScript动态渲染页面爬取——Selenium的使用(二)
JavaScript动态渲染页面爬取——Selenium的使用(二)
91 2
|
2月前
|
数据采集 存储 JavaScript
Dynamic Website 爬虫:应对动态内容与 JavaScript 渲染挑战
本文深入探讨了如何设计针对动态网站的爬虫,以采集 WIPO Brand Database 中的专利和技术信息。文章详细介绍了动态网站的挑战,包括 JavaScript 渲染、反爬虫机制和异步加载,并提出了解决方案,如使用 Selenium 模拟浏览器、代理 IP 技术和 API 抓取。最后,通过具体代码示例展示了如何实现这些技术手段。
179 0
|
2月前
|
JavaScript 前端开发 Java
JS引擎V8
【10月更文挑战第9天】
35 0
|
2月前
|
前端开发 JavaScript
JavaScript动态渲染页面爬取——CSS位置偏移反爬案例分析与爬取实战
JavaScript动态渲染页面爬取——CSS位置偏移反爬案例分析与爬取实战
42 0
|
2月前
|
存储 JSON JavaScript
JavaScript动态渲染页面爬取——Pyppeteer爬取实战
JavaScript动态渲染页面爬取——Pyppeteer爬取实战
43 0
|
4月前
|
JavaScript
js渲染乘法表
js渲染乘法表
35 1