11.Three.js使用indexeddb前端缓存模型优化前端加载效率

11.Three.js使用indexeddb前端缓存模型优化前端加载效率

1.简述

在使用Three.js做数字孪生应用场景时,我们常常需要用到大量模型或数据。在访问我们的数字孪生应用时,每次刷新都需要从web端进行请求大量的模型数据或其他渲染数据等等,会极大的消耗时间,用户体验不好,因而我们可以通过indexeddb缓存我们的模型数据。

2.indexeddb简介

indexeddb 介绍和学习可以参考这里:https://zhuanlan.zhihu.com/p/429086021

IndexedDB主要用来客户端存储大量数据而生的,我们都知道cookie、localstorage等存储方式都有存储大小限制。如果数据量很大,且都需要客户端存储时,那么就可以使用IndexedDB数据库。它是一种前端的非关系型数据库,同样具有增删改查的功能。我下面封装了一个工具类dbUtils.js对模型数据进行缓存:

const DB_NAME = 'modeldb'; //数据库名称
const DB_VERSION = 1; //数据库版本号
const DB_STORE_NAME = 'model_glb_table'; //数据库仓库

function DBUtil() {
    this.db = null;
    // 根据模型的url地址,请求模型
    // 如果没有数据库中没有模型,则请求模型并放入缓存中,返回promise,带有模型的blob文件对象
    // 如果如果缓存中有模型了,就直接从缓存中取出
    this.get = async (url, onProgress) => {
        this.db = await this.initDataBase();
        let getRequest = this.db
            .transaction([DB_STORE_NAME], "readwrite") // 事务对象 指定表格名称和操作模式("只读"或"读写")
            .objectStore(DB_STORE_NAME) // 仓库对象
            .get(url);  // 通过主键(url)获取数据
        let that = this;
        return new Promise((resolve, reject) => {
            getRequest.onsuccess = function (event) {
                let modelFile = event.target.result;
                // 假如已经有缓存了 直接用缓存
                if (modelFile) {
                    if (onProgress) {
                        onProgress(100);
                    }
                    console.log("使用缓存");

                    resolve(modelFile.blob)
                } else {
                    // 假如没有缓存 请求新的模型存入
                    that.put(url, onProgress).then((blob) => {
                        resolve(blob)
                    }).catch(() => {
                        reject()
                    });
                }
            };
            getRequest.onerror = function (event) {
                // console.log('error', event)
                reject()
            }
        })
    }

    // 请求模型并放入缓存中
    this.put = async (url, onProgress) => {
        const response = await fetch(url);

        if (response.status !== 200) {
            throw new Error('Request failed');
        }

        const contentLength = response.headers.get('Content-Length');
        // console.log(contentLength)
        const totalBytes = parseInt(contentLength, 10);
        let downloadedBytes = 0;

        const readableStream = response.body;

        const { readable, writable } = new TransformStream();

        const writer = writable.getWriter();

        const reader = readableStream.getReader();

        const pump = async () => {
            const { done, value } = await reader.read();

            if (done) {
                writer.close();
                return;
            }

            writer.write(value);

            downloadedBytes += value.length;

            if (onProgress) {
                const progress = (downloadedBytes / totalBytes) * 100;
                console.log(progress.toFixed(2))
                onProgress(progress.toFixed(2));
            }

            return pump();
        };

        await pump();

        let blob = null;
        try {
            blob = await new Response(readable).arrayBuffer();
        } catch (e) {
            console.log('请求arrayBuffer失败,用blob方式')
            blob = await new Response(readable).blob();
        }

        let obj = {
            ssn: url
        }
        obj.blob = new Blob([blob])
        const inputRequest = this.db
            .transaction([DB_STORE_NAME], "readwrite")
            .objectStore(DB_STORE_NAME)
            .add(obj);

        return new Promise((resolve, reject) => {
            inputRequest.onsuccess = function () {
                console.log('glb数据添加成功');
                resolve(obj.blob);
            };
            inputRequest.onerror = function (evt) {
                console.log('glb数据添加失败', evt);
                reject();
            };
        });
    }

    // 打开数据库
    this.initDataBase = () => {
        if (!window.indexedDB) {
            console.log("浏览器不支持indexedDB缓存!!!")
            return;
        }
        let request = indexedDB.open(DB_NAME, DB_VERSION); //如果没有数据库,则创建,有数据库就链接
        return new Promise((resolve, reject) => {
            request.onerror = function () {
                // console.log("error: create db error");
                reject()
            };
            // 数据库创建或升级的时候会触发
            request.onupgradeneeded = function (evt) {
                evt.currentTarget.result.createObjectStore(
                    DB_STORE_NAME, { keyPath: 'ssn' });
            };
            // 数据库打开成功回调
            request.onsuccess = function (evt) {
                // console.log("onsuccess: create db success ");
                resolve(evt.target.result)
            };
        })
    }
}

3.Three.js加载模型

原本的three.js加载模型如下,这是最基础的加载模型的方法:

function initObject() {
        //再加载模型
        const objLoader = new THREE.GLTFLoader();
        objLoader.load(
          "./data/Soldier.glb",
          function (gltf) {
            let root = gltf.scene;
            root.position.set(0,0,0);
            root.scale.set(100,100,100);
            scene.add(root);

          },

          //加载回调
          function (xhr) {
            console.log((xhr.loaded / xhr.total) * 100 + "% loaded");
          },
          //加载失败回调
          function (error) {
            console.log("An error happened");
          }
        );

    }

使用缓存后加载的方法:

function initObject() {
      new DBUtil().get("./data/Soldier.glb", (progress) => {
        console.log("progress", progress);

      }).then((blob) => {

        //再加载模型
        const objLoader = new THREE.GLTFLoader();
        let url = URL.createObjectURL(new Blob([blob]));
        objLoader.load(url, function (gltf) {
          let root = gltf.scene;
          root.position.set(0, 0, 0);
          root.scale.set(100, 100, 100);
          scene.add(root);

        },

          //加载回调
          function (xhr) {
            console.log((xhr.loaded / xhr.total) * 100 + "% loaded");
          },
          //加载失败回调
          function (error) {
            console.log("An error happened");
          }
        );

      })


    }

4.验证检查

我们刷新网页后,查看应用程序中是否多了一个数据库,数据库中是否有数据

在这里插入图片描述

尝试删除该数据库后,再刷新页面。

对比数据库存在时,再次刷新页面。可以发现该数据库缓存存在时,模型渲染效率快了很多。特别时互联网访问时,还会有一个模型数据下载的过程,使用缓存可以直接省略下载的时间,效率上可以得到很大的提升。

视频地址:https://www.bilibili.com/video/BV1cQSzYLE9n/?vd_source=0f4eae2845bd3b24b877e4586ffda69a

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/907097.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

keepalive+mysql8双主

1.概述 利用keepalived实现Mysql数据库的高可用,KeepalivedMysql双主来实现MYSQL-HA,我们必须保证两台Mysql数据库的数据完全一致,实现方法是两台Mysql互为主从关系,通过keepalived配置VIP,实现当其中的一台Mysql数据库…

C++ 实现俄罗斯方块游戏

✅作者简介:2022年博客新星 第八。热爱国学的Java后端开发者,修心和技术同步精进。 🍎个人主页:Java Fans的博客 🍊个人信条:不迁怒,不贰过。小知识,大智慧。 💞当前专栏…

项目实战:基于Linux的Flappy bird游戏开发

一、项目介绍 项目总结 1.按下空格键小鸟上升,不按小鸟下落 2.搭建小鸟需要穿过的管道 3.管道自动左移和创建 4.小鸟撞到管道游戏结束 知识储备 1.C语言 2.数据结构-链表 3.Ncurses库 4.信号机制 二、Ncurses库介绍 Ncurses是最早的System V Release 4.0 (SVr4)中…

nginx上传文件超过限制大小、响应超时、反向代理请求超时等问题解决

1、文件大小超过限制 相关配置: client_max_body_size: Syntax:client_max_body_size size;Default:client_max_body_size 1m;Context:http, server, location 2、连接超时: proxy_read_timeout: Syntax:proxy_read_timeout time;Default…

C++ --- 多线程的使用

目录 一.什么是线程? 线程的特点: 线程的组成: 二.什么是进程? 进程的特点: 进程的组成: 三.线程与进程的关系: 四.C的Thread方法的使用: 1.创建线程: 2.join(…

基于Spring Boot的医疗陪护系统设计与实现(源码+定制+开发)病患陪护管理平台、医疗服务管理系统、医疗陪护信息平台

博主介绍: ✌我是阿龙,一名专注于Java技术领域的程序员,全网拥有10W粉丝。作为CSDN特邀作者、博客专家、新星计划导师,我在计算机毕业设计开发方面积累了丰富的经验。同时,我也是掘金、华为云、阿里云、InfoQ等平台…

ViT面试知识点

文章目录 VITCLIPSAMYOLO系列问题 VIT 介绍一下Visual Transformer? 介绍一下自注意力机制? 介绍一下VIT的输出方式 介绍一下VIT做分割任务 VIT是将NLP的transformer迁移到cv领域,他的整个流程大概如下:将一张图片切成很多个pat…

【Comsol教程】计算流道中的流量

在进行微流控方面的仿真的时候可能需要计算某一流道中流量的大小,下面展示如何计算。 流量分为质量流量和体积流量,我们常采用体积流量。在COMSOL中有两种方法计算, 1.使用Comsol内置的函数 这里我使用的是蠕动流模块【spf】,定义了3个开放边…

LeetCode 3226. 使两个整数相等的位更改次数

. - 力扣(LeetCode) 题目 给你两个正整数 n 和 k。你可以选择 n 的 二进制表示 中任意一个值为 1 的位,并将其改为 0。 返回使得 n 等于 k 所需要的更改次数。如果无法实现,返回 -1。 示例 1: 输入: n …

项目升级到.Net8.0 Autofac引发诡异的问题

前两天把项目升级到.Net8.0了,把.Net框架升级了,其他一些第三方库升级了一部分,升级完以后项目跑不起来了,报如下错误: An unhandled exception occurred while processing the request. DependencyResolutionExcepti…

RabbitMQ 七种工作模式介绍

目录 1.简单模式队列 2.WorkQueue(⼯作队列) 3 Publish/Subscribe(发布/订阅) 4 Routing(路由模式) 5.Topics(通配符模式) 6 RPC(RPC通信) 7 Publisher Confirms(发布确认) RabbitMQ 共提供了7种⼯作模式供我们进⾏消息传递,接下来一一介绍它的实现与目的 1.简单模式队列…

自动化测试类型与持续集成频率的关系

持续集成是敏捷开发的一个重要实践,可是究竟多频繁的集成才算“持续”集成? 一般来说,持续集成有3种常见的集成频率,分别是每分钟集成、每天集成和每迭代集成。项目组应当以怎样的频率进行集成,这取决于测试策略&…

操作系统期中复习2-4单元

Chapter-2 第一个图形界面——Xerox Alto 早期操作系统:规模小,简单,功能有限,无结构(简单结构)。(MS-DOS,早期UNIX) 层次结构:最底层为硬件,最高层为用户层,自下而上构…

2-141 怎么实现ROI-CS压缩感知核磁成像

怎么实现ROI-CS压缩感知核磁成像,这个案例告诉你。基于matlab的ROI-CS压缩感知核磁成像。ROI指在图像中预先定义的特定区域或区域集合,选择感兴趣的区域,通过减少信号重建所需的数据来缩短信号采样时间,减少计算量,并在…

Android中同步屏障(Sync Barrier)介绍

在 Android 中,“同步屏障”(Sync Barrier)是 MessageQueue 中的一种机制,允许系统临时忽略同步消息,以便优先处理异步消息。这在需要快速响应的任务(如触摸事件和动画更新)中尤为重要。 在 An…

【tomcat系列漏洞利用】

Tomcat 服务器是一个开源的轻量级Web应用服务器,在中小型系统和并发量小的场合下被普遍使用。主要组件:服务器Server,服务Service,连接器Connector、容器Container。连接器Connector和容器Container是Tomcat的核心。一个Container…

【压力测试】如何确定系统最大并发用户数?

一、明确测试目的与了解需求 明确测试目的:首先需要明确测试的目的,即为什么要确定系统的最大并发用户数。这通常与业务需求、系统预期的最大用户负载以及系统的稳定性要求相关。 了解业务需求:深入了解系统的业务特性,包括用户行…

深入理解Redis的四种模式

Redis是一个内存数据存储系统,支持多种不同的部署模式。以下是Redis的四种主要部署模式。 1、单机模式 单机模式是最简单的部署模式,Redis将数据存储在单个节点上。这个节点包括一个Redis进程和一个持久化存储。单机模式非常适合小型应用程序或者开发和…

【多态】析构函数的重写

析构函数的重写(面试常见题) 基类的析构函数为虚函数,此时派生类析构函数只要定义,⽆论是否加virtual关键字,都与基类的析构函数构成重写。 虽然基类与派⽣类析构函数名字不同看起来不符合重写的规则,实际…

合并区间 leetcode56

合并区间leetcode 目录一、题目二、踩坑过程三、上官方解答四、含泪体会彩蛋 目录 一、题目 二、踩坑过程 一开始想使用一个数组来标记区间,但是仔细想不好实现,单纯把区间里出现的设置为1,不好体现重叠的概念,如果使用三种状态…