THREE.js开发的应用运行在iphone5下发现有些时候会崩溃,跟了几天发现是因为Sprite太多频繁更新纹理占用显存导致的。通常解决纹理频繁更新问题就要用到one draw all方法,放到纹理上就是把所有纹理图片生成一张大图片的方式。

一、阻止纹理重复上传

我们需要一张大纹理,先将所有的内容绘制在大纹理上,需要显示局部纹理的时候通过纹理坐标控制去大纹理上取图像。那么这个时候问题来了,THREE.js内部实现方式是将Texture与图片、纹理坐标绑定,即使为所有的Texture对象设置同一张图片,THREE.js仍然会将每个Texture中的图片上传给GPU。每次上传一张大纹理严重阻塞UI渲染进程。

首先要解决的是让这张大纹理值上传一次。

这个问题需要我们对THREE.js源码进行深入了解,可以看到setTexture2D函数中有一个properties变量,这个变量是一个WebGLProperties类型的变量,而该类型存储各种东西:Texture、Material、RenderTarget、Object的buffers等。我们继续深入该类的源码,发现get方法会根据对象的uuid来获取相关WebGL属性,比如gl.createTexture、gl.createBuffer创建的各种缓冲区。

对应Texture得到的webgl属性如下,其中__webglTexture就是对应的纹理图片创建的缓冲区对象。

那么我们可以来一个取巧的方法,将所有纹理的的uuid都设置唯一,那么THREE.js只会对第一个Texture的纹理进行上传,后面的texture对象取到的都是第一个的properties,这样就能避免纹理重复上传。

二、建立纹理索引

我们需要自己维护一套索引关系,通过这套索引关系得到每个贴图在大纹理中纹理坐标。这里要为每一个poi记录它的起始位置和区域范围,其中要用到canvasContext.measureText来测量文本的宽度,文本高度可以直接根据fontSize取得。

同时索引建立完毕后,需要计算每个poi区域在全局纹理中的纹理坐标范围:

要注意的是,这里纹理坐标的原点在左下方,有时候原点在左上方。建立索引代码如下

三、局部更新

上述方案虽然能够避免频繁上传纹理,但是需要每次将需要绘制的内容准备好,当有内容需要更新时,还是需要重新上传整个全局纹理,反而使得性能下降巨大。经过查阅资料后发现webgl中有一种局部纹理更新技术,简单来说先在内存中开辟一块的纹理区域,将所有内容绘制在这张全局纹理中,每次有更新时,只需要更新它的一个局部区域即可。

但是这里要解决的问题是THREE.js并没有提供局部纹理更新的方式,也没有相应的自定义接口,那么这时候就需要我们自己来处理了。

这里自定义一个Texture的子类

开辟一块内存区域

在需要的时候动态更新局部纹理,其中src这里是ImageData对象

具体代码可以参考这里,我这里也是基于它来定制的。

https://github.com/spite/THREE.UpdatableTexture

原文作者通过更改THREE.js源码的方式实现,而我是直接把下面这个函数拷贝到这个子类中

四、高清屏的大坑

现在我们的方案是,先在gpu中开辟一块全局纹理区域,然后绘制时将poi绘制到一张与全局纹理同样大小的canvas上,然后从canvas中调用createImageData来获取像素,将像素局部更新到gpu中。那么在pc上我们得到的结果很完美。

然而放到移动端上后,我们得到的结果是:

TMMD中间那块哪去了!找了大半天发现问题出现在高清屏上,挡在高清屏上绘制canvas上时,我们通常会做一些高清处理,比如四像素绘制一像素。

我们做高清处理的方式是利用radio*radio设备像素绘制一css像素,看起来是css像素的大小,但实际在浏览器内部,看起来css上一像素实际在canvas里的像素是radio * radio(radio代表window.devicePixelRatio)

但实际上在浏览器内部绘制canvas图像的单位是设备像素。那么如果我们还以上面的rectW、rectH来获取像素的话,我们得到的这部分像素并不是这个poi真正占有的像素数目。

所以,问题就来了我们需要在gpu开辟的全局纹理的单位跟canvas中获取像素的单位要保持一致,我们统一使用设备像素。

我们对canvas也不用使用style来设置样式宽高了。

那么获取poi图像的真正像素范围时:

所以利用getImageData取像素时候,就要小心取到真正的像素区域,(startX * radio,startY * radio)- (poiRectW * radio, poiRectH * radio);否则某些像素就会被丢弃掉,这部分像素才是浏览器真正使用的设备像素。

现在在移动设备上能够获取正确的高清label啦!

五、局部更新引起的新问题

当全局纹理被占满时候,在继续绘制poi,这时候新的poi区域需要更新到gpu中,那么也就带来了新的问题,在gpu中的纹理还保持着之前的像素,而新的poi会覆盖这部分区域,但有时候往往会与之前的文字叠加起来,效果如下:

可以看到新更新的poi,在计算纹理坐标时候,有一部分像素包含了其他poi的像素。这个问题是因为新poi的区域刚好叠在了先前poi的边界上,那么我们只要给新的poi加一点buffer,这个buffer是白素透明区域,buffer会把之前的poi像素覆盖掉,而我们计算纹理坐标时,只取poi的边界,那么就可以解决这个问题。

那么首先绘制的时候就要保留buffer

上传的时候使用buffer

计算纹理坐标时,排除buffer

六、局部更新带来的性能为

根据目前的结果,局部更新能后解决crash的问题,但是带来了严重的性能开销,与同事应用局部更新提升性能的结果相反。这个问题还要继续跟踪。

Three.js使用局部纹理更新的更多相关文章

  1. 【转】【Asp.Net】了解使用 ASP.NET AJAX 进行局部页面更新

    简介Microsoft的 ASP.NET 技术提供了一个面向对象.事件驱动的编程模型,并将其与已编译代码的优势结合起来.但其服务器端的处理模型仍存在技术本身所固有的几点不足: 进行页面更新需要往返服务 ...

  2. nw.js桌面程序自动更新(node.js表白记)

    Hello Google Node.js 一个基于Google V8 的JavaScript引擎. 一个伟大的端至端语言,或许我对你的热爱源自于web这门极富情感的技术吧! 注: 光阴似水,人生若梦, ...

  3. WebGL three.js学习笔记 纹理贴图模拟太阳系运转

    纹理贴图的应用以及实现一个太阳系的自转公转 点击查看demo演示 demo地址:https://nsytsqdtn.github.io/demo/solar/solar three.js中的纹理 纹理 ...

  4. React.js 小书 Lesson20 - 更新阶段的组件生命周期

    作者:胡子大哈 原文链接:http://huziketang.com/books/react/lesson20 转载请注明出处,保留原文链接和作者信息. 从之前的章节我们了解到,组件的挂载指的是将组件 ...

  5. three.js学习:纹理Texture之平面纹理

    index.html <!DOCTYPE html> <html lang="en"> <head> <meta charset=&quo ...

  6. cocos2dx js 3.2 热更新

    COCOS IDE用手机调试更新是正常的,是预想的结果,但用COCOS IDE打包发布APK,安装到手机上,热更新下载图片.JSON UI什么的都能正常更新替换,但JS脚本没有替换,这是为毛.更新文件 ...

  7. JS小福利 —— 实时更新的页面小时钟

    今天小女刚学会了一个好玩的小玩意儿~~特来跟大家分享一下~~~ 这是一个有趣的时钟显示程序,可以进行实时的年月日.星期.时分秒更新,有了这组小代码,以后可以作为日期插件应用在大型的JS代码中哦~~ 积 ...

  8. node webkit(nw.js) 设置自动更新

    原理:把更新的文件放在服务器上,设置一个客户端版本号,每次打开客户端的时候,通过接口获取服务器上的版本,如果高于本地的版本就下载服务器上的代码,低于或等于就不更新 <script> var ...

  9. FastAdmin 浏览器 JS CSS 缓存如何更新?

    由于代码修改,但文件名没有修改,因为浏览器对 JS 和 CSS 是缓存的,而且由于服务器无法控制客户端的缓存. 但是可以对 JS 和 CSS 的请求加上版本号,达到更新缓存的效果.

随机推荐

  1. string to byte[]

    在平常的开发过程中,经常碰到需要将string转化成byte[]字节数组的情形,有两种方法,在这里总结一下. 使用MemoryStream 代码如下. using (MemoryStream sb = ...

  2. [刘阳Java]_MyBatis_映射文件的select标签入门_第3讲

    1.Mybatis映射文件的<select>标签主要帮助我们完成SQL语句查询功能,<select>标签它包含了很多属性,下面简单对<select>标签的属性做一个 ...

  3. C#方法的参数

  4. POJ3680_Intervals

    给你若干个区间,每个区间有一个权值,你可以选出某些区间,使得在保证没有任何一段的覆盖次数超过k的前提下,总的权值最大. 这个建模真的十分神奇,赞一个. 对于给出的每一个区间,离散化,最终我们可以知道所 ...

  5. stm32——Flash读写

    stm32——Flash读写 一.Flash简介 通过对stm32内部的flash的读写可以实现对stm32的编程操作. stm32的内置可编程Flash在许多场合具有十分重要的意义.如其支持ICP( ...

  6. ScriptManager局部刷新

    ScriptManager和UpdatePanel控件联合使用可以实现页面异步局部更新的效果.其中的UpdatePanel就是设置页面中异步局部更新区域,它必须依赖于ScriptManager存在,因 ...

  7. plist文件读写

    - (void)viewDidLoad { [super viewDidLoad]; NSDictionary *dictionary1 = [NSDictionary dictionaryWithO ...

  8. Android RecyclerView体验(一)- 简介

    在网上关于RecyclerView的基本使用方式已经有了比较详细介绍,而且其设计结构也类似于ListView,所以本文将不重点介绍如何使用,在文末的引用中都可以相关内容.这里主要是介绍Recycler ...

  9. (入门篇 NettyNIO开发指南)第四章-TIP黏包/拆包问题解决之道

    熟悉TCP编程的读者可能都知道,无论是服务端还是客户端,当我们读取或者发送消息的时候,都需要考虑TCP底层的粘包/拆包机制.木章开始我们先简单介绍TCP粘包/拆包的基础知识,然后模拟一个没有考虑TCP ...

  10. 树状数组-HDU1541-Stars一维树状数组 POJ1195-Mobile phones-二维树状数组

    树状数组,学长很早之前讲过,最近才重视起来,enmmmm... 树状数组(Binary Indexed Tree(B.I.T), Fenwick Tree)是一个查询和修改复杂度都为log(n)的数据 ...