正如在https://github.com/mrdoob/three.js/issues/11301 中讨论的,我们在 WebVR 中遇到的主要问题之一,虽然在非 VR 体验中也很烦人,但在加载资产时阻塞了主线程。
随着最近在浏览器中实现链接遍历,非阻塞加载是确保令人满意的用户体验的必要条件。 如果您从一个页面跳转到另一个页面并且目标页面开始加载阻塞主线程的资产,它将阻塞渲染功能,因此不会向头显提交任何帧,并且经过一小段宽限期后,浏览器会将我们从 VR 中踢出它将要求用户取出耳机,再次单击进入 VR(需要用户手势)并返回体验。
目前我们可以看到两种非阻塞加载OBJ文件的实现:
两者都有其优点和缺点,老实说,我不是评估该实现的网络工作者专家,但这是一个有趣的讨论,理想情况下会导致可用于将加载器移植到非阻塞版本的通用模块。
有什么建议?
/cc @mikearmstrong001 @kaisalmen @delapuente @spite
你可以有一个基于 Promise+worker+incremental 的加载器(有点像这两点的混合)
将源 URL 传递给工作脚本,获取资源,返回具有所需缓冲区、结构甚至 ImageBitmaps 的可传输对象结构; 它应该足够简单,不需要大量的three.js 处理开销。
无论如何,上传到 GPU 的数据都会被阻塞,但您可以通过 display.rAF 构建一个队列以在不同的帧之间分发命令。 这些命令可以每帧一次执行一个,或者计算操作的平均时间并运行尽可能多的“安全”以在当前帧预算中运行(类似于 requestIdleCallback 的东西会很好,但它没有得到广泛支持,并且在 WebVR 会话中存在问题)。 也可以通过使用 bufferSubData、texSubImage2D 等来改进。
现在对工作人员和可转移对象的支持非常可靠,特别是在支持 WebVR 的浏览器中。
大家好,我有一个可用的原型,在这种情况下您可能会感兴趣。 请参阅以下分支:
https://github.com/kaisalmen/WWOBJLoader/tree/Commons
这里的网格配置部分已与WWOBJLoader2
完全分离:
https://github.com/kaisalmen/WWOBJLoader/blob/Commons/src/loaders/WWLoaderCommons.js
WWLoaderCommons
可以轻松实现其他网格提供程序(文件格式加载程序)。 基本上,它定义了 Web Worker 实现必须如何将网格数据提供回主线程并将其处理/集成到场景中。 参见作为技术演示者的随机三角形垃圾提供者 😉:
https://github.com/kaisalmen/WWOBJLoader/tree/Commons/test/meshspray
https://kaisalmen.de/proto/test/meshspray/main.src.html
即使在当前的实现中, WWOBJLoader2
依赖于可传输对象(ArrayBuffers/ByteBuffers) Mesh
从工作线程到主线程的BufferedGeometry
数据。 从时间上看,从提供的 ByteBuffers 创建Mesh
是可以忽略不计的。 每当将更大的网格集成到场景中时,渲染就会停止(数据复制、场景图调整......!?)。 这总是独立于来源的情况(如果我错了,请纠正我)。
WWOBJLoader2 的“流”模式可以平滑这些停顿,但如果来自 OBJ 模型的单个网格块的重量为 50 万个顶点,那么渲染将暂停更长的时间。
我开了一个新问题来详细说明我在定制分支上做了什么以及为什么:
https://github.com/kaisalmen/WWOBJLoader/issues/11
该问题仍然是一个存根,细节将很快跟进。
为了提供一些数字,这里是https://threejs.org/examples/webgl_loader_gltf2.html的性能配置文件,加载具有 2048x2048 纹理的 13MB 模型。
在这种情况下,阻塞主线程的主要事情是将纹理上传到 GPU,据我所知,这不能从 WW 中完成。加载器应该逐渐添加纹理,或者three.js 应该在内部处理它。
对于好奇,阻塞主线程的最后一个块是添加环境立方体贴图。
react-vr 的主要目标不一定是在挂钟时间方面拥有最佳的加载器,但不会在加载新内容时导致突然和意外的帧输出。 我们可以做的任何事情来尽量减少这种情况对所有人都有好处,尤其是 VR。
纹理绝对是一个问题,明显的第一步是选择性地增量加载它们 - 一次为大纹理设置一组线。 由于客户端程序的上传是隐藏的,因此他们将很难管理,但我会更公开地暴露给 webgl 渲染器以减轻three.js的压力
对于 gltf 解析,我通常在我的测试中看到 500 毫秒的阻塞,这很重要,我更喜欢所有加载器的增量方法(也应该是可克隆的)
React VR 的前提是鼓励由 Web 风格驱动的简单动态内容,从而鼓励更多的开发人员,这将更加强调改进动态处理。 大多数情况下,我们不知道在用户创建的应用程序开始时需要哪些资产。
@kaisalmen感谢您的链接
在 Elation Engine / JanusWeb 中,我们实际上使用一个工作线程池来完成我们所有的模型解析,效果很好。 一旦工人装完每个模型,我们把它序列化使用object.toJSON()
,它发送到主线程postMessage()
,然后使用加载ObjectLoader.parse()
。 这消除了加载器代码的大部分阻塞部分 - 在ObjectLoader.parse()
仍然花费了一些时间,这些时间可能可以优化,但整体交互性和加载速度得到了显着改善。 由于我们使用了一个工作池,我们还可以并行解析多个模型,这在复杂场景中是一个巨大的胜利。
在纹理方面,是的,我认为需要对three.js 的纹理上传功能进行一些更改。 使用texSubImage2D
的分块上传器将是理想的,然后我们可以在多个帧上对大纹理进行部分更新,如上所述。
我非常乐意在此更改上进行协作,因为这将使许多使用 Three.js 作为基础的项目受益
我认为使用texSubImage2D
是个好主意。
还有为什么 WebGL 不异步上传纹理。
OpenGL 和其他库有相同的限制吗?
我在想的另一件事是 GLSL 编译。
会掉帧吗? 或者足够快而我们不需要关心?
是的,这在原生 OpenGL 中也是一个问题——编译着色器和上传图像数据是同步/阻塞操作。 这就是为什么大多数游戏引擎建议甚至强制您在开始关卡之前预加载所有内容的原因 - 通常认为即使从硬盘驱动器加载新资源也会对性能造成太大影响,在这里我们尝试异步执行通过互联网……实际上,我们遇到了比大多数游戏开发者更困难的问题,如果我们希望能够即时流式传输新内容,我们将不得不使用更先进的技术。
如果我们将来使用新的ImageBitmap
API,上传纹理的问题就会减少。 参见https://youtu.be/wkDd-x0EkFU?t=82 。
顺便说一句:感谢@spite ,我们在项目中已经有了一个实验性的ImageBitmapLoader 。
@Mugen87实际上我已经在 Elation Engine / JanusWeb 中使用 ImageBitmap 完成了所有纹理加载 - 它绝对有帮助并且值得集成到 Three.js 核心中,但是在 WebGL 中使用纹理涉及两个主要费用 - 图像解码时间, 和图像上传时间 - ImageBitmap 只对第一个有帮助。
在我的测试中,这确实减少了大约 50% 的 CPU 阻塞时间,但是将大纹理上传到 GPU,尤其是 2048x2048 及更高,很容易花费一秒钟或更长时间。
试试@jbaicoianu的建议会很方便。 无论如何,如果选择主线程替代方案,这似乎是requestIdleCallback而不是 setTimeout 的完美匹配。
我同意你们所有人的看法,我相信在工作线程上加载和解析所有内容的方法,在主线程上创建所需的对象(如果它非常昂贵,可以分几个步骤完成),然后在渲染器上包含增量加载。
对于 MVP,我们可以定义一个 maxTexturesUploadPerFrame(默认为无穷大),渲染将根据该数字处理从池中加载。
在接下来的迭代中,我们可以添加一个逻辑,正如@spite评论的那样,来测量平均值并根据阻塞前的安全范围时间自动上传它们。 最初可以将每个纹理作为一个单元来完成,但随后可以改进以增量上传块以获得更大的纹理。
requestIdleCallback 会很好,但它没有得到广泛支持,并且在 WebVR 会话中存在问题
@spite我对你的句子很好奇,你说的有问题是什么意思?
我有一个 THREE.UpdatableTexture 来使用 texSubImage2D 增量更新纹理,但需要对three.js 进行一些调整。 这个想法是准备一个 PR 来增加支持。
关于 requestIdleCallback (rIC):
首先,它在 Chrome 和 Firefox 上得到支持,虽然它可以很容易地被 polyfill,但 polyfill 版本可能会稍微违背目的。
第二:与在呈现时需要调用 vrDisplay.requestAnimationFrame (rAF) 而不是 window.rAF 的方式相同,这同样适用于 rIC,如本 crbug 中所述。 这意味着加载器需要始终知道当前的活动显示,否则它将根据呈现的内容停止触发。 它并不是非常复杂,它只是增加了加载器接线的复杂性(理想情况下应该只完成它们的工作,独立于呈现状态)。 另一种选择是让threejs中在主线程中运行增量作业的部分共享当前显示; 我认为现在使用 Threejs 对 VR 的最新更改要容易得多。
另一个考虑因素:为了能够使用 texSubImage2D(256x256 或 512x512)分几步上传一个大纹理,我们需要一个 WebGL2 上下文来具有偏移和剪切功能。 否则图像必须通过画布预先剪裁,在上传之前基本上平铺客户端。
@spite好点,我没有想过在呈现时不调用 rIC,起初我认为我们应该需要一个 display.rIC 但我相信 .rIC 应该附加到窗口并在窗口或显示时被调用都闲着。
我相信我在 webvr 规范讨论中没有听到任何与此相关的信息@kearwood可能有更多信息,但绝对是我们应该解决的问题。
期待看到您的 UpdatableTexture PR! :) 即使它只是一个 WIP,我们也可以将一些讨论移到那里。
也许装载机可以变成这样......
THREE.MyLoader = ( function () {
// parse file and output js object
function parser( text ) {
return { 'vertices': new Float32Array() }
}
// convert js object to THREE objects.
function builder( data ) {
var geometry = new THREE.BufferGeometry();
geometry.addAttribute( new THREE.BufferAttribute( data.vertices, 3 );
return geometry;
}
function MyLoader( manager ) {}
MyLoader.prototype = {
constructor: MyLoader,
load: function ( url, onLoad, onProgress, onError ) {},
parse: function ( text ) {
return builder( parser( text ) );
},
parseAsync: function ( text, onParse ) {
var code = parser.toString() + '\nonmessage = function ( e ) { postMessage( parser( e.data ) ); }';
var blob = new Blob( [ code ], { type: 'text/plain' } );
var worker = new Worker( window.URL.createObjectURL( blob ) );
worker.addEventListener( 'message', function ( e ) {
onParse( builder( e.data ) );
} );
worker.postMessage( text );
}
}
} )();
THREE.UpdatableTexture 的第一个提案发布
理想情况下,它应该是任何 THREE.Texture 的一部分,但我会首先探索这种方法。
@mrdoob我看到了将完全相同的代码通过管道传输给工作人员的优点,感觉太错误了😄。 我想知道序列化、blob 和重新评估脚本会产生什么影响; 没什么太可怕的,但我认为浏览器没有针对这个怪癖进行优化🙃
此外,理想情况下,资源本身的获取将发生在工作人员中。 而且我认为浏览器中的 parser() 方法需要一个three.js本身的importScripts。
但是定义同步/异步加载器的一点就太糟糕了!
@mrdoob builder
函数可以是所有加载器完全通用和通用的(WIP:https://github.com/kaisalmen/WWOBJLoader/blob/Commons/src/loaders/support/WWMeshProvider.js#LL215- LL367;更新:尚未在函数中隔离)。 如果输入数据被限制为纯 js 对象而不引用任何THREE
对象(这就是你的想法,对吧?)我们可以构建可序列化的工作代码而无需在工作中导入(什么WWOBJLoader
确实如此)。 这对于 Geometry 来说很容易,但是材料/着色器(如果在文件中定义)只能在构建器中创建,并且之前只能由parser
描述为 JSON。
我认为,工作人员应该向每个新的 Mesh 及其完成发出信号。 它可以像这样改变:
// parse file and output js object
function parser( text, onMeshLoaded, onComplete ) {
....
}
parse: function ( text ) {
var node = new THREE.Object3d();
var onMeshLoaded = function ( data ) {
node.add( builder( data ) );
};
// onComplete as second callbackonly provided in async case
parser( text, onMeshLoaded ) );
return node;
},
worker builder util 很有帮助+一些通用通信协议,这与您原样使用解析器的想法并不矛盾,但我认为它需要一些包装。 WWOBJLoader 演变的当前状态: https :
更新2:
builder
,但能够设置一些参数来调整parser
的行为是有意义的。 这也意味着配置参数应该可以独立于解析传递给工作线程WWOBJLoader
的定制 Commons 分支中工作,顺便说一句)WWOBJLoader2
现在扩展OBJLoader
并覆盖解析。 所以,我们有两个解析上限,但在不同的类中。 它接近提案,但尚不符合要求。 一些解析器代码需要统一,最终两个类都需要融合暂时就这样了。 欢迎反馈😄
@mrdoob我喜欢从加载器的代码中动态组合工人的想法。 我目前的方法只是加载整个组合的应用程序 js 并且只使用与主线程不同的入口点,绝对不如让工作人员只用他们需要的代码组合效率高。
我喜欢使用精简传输格式在 worker 之间传递的方法,因为在传递回主线程时很容易将这些 TypedArray 标记为可传输。 在我目前的方法中,我在工作器中使用.toJSON()
方法,但随后我将顶点、UV 等的 JS 数组替换为适当的 TypedArray 类型,并在调用时将它们标记为可转移留言。 这使得主线程中的解析/内存使用量更轻一些,代价是工作线程中的处理/内存使用量更多 - 这是一个很好的权衡,但可以通过引入按照您的建议使用新的传输格式,或者通过修改.toJSON()
来选择给我们 TypedArrays 而不是 JS 数组。
我认为这种简化方法的两个缺点是:
@spite关于“此外,理想情况下,资源本身的获取将发生在工作人员身上。” - 这是我第一次为 Elation Engine 实现基于工作器的资产加载器时的想法 - 我有 4 或 8 个工作器,我会在它们可用时将作业传递给他们,然后工作器将获取文件,解析它们,并将它们返回到主线程。 然而,实际上这意味着下载会阻止解析,并且如果您一次请求它们,您将失去从流水线等中获得的好处。
意识到这一点后,我们添加了另一层来管理我们所有的资产下载,然后资产下载器会触发事件以让我们知道资产何时可用。 然后我们将这些传递给工作池,使用二进制文件数据上的可传输对象有效地将其输入到工作线程中。 有了这个变化,即使下载在主线程上,下载也会更快,并且解析器可以全速运行处理,而不是摆弄他们的拇指等待数据。 总的来说,这是我们在资产加载速度方面所做的最佳优化之一。
关于纹理加载的主题,我已经构建了一个新的FramebufferTexture
类的概念证明,该类带有一个配套的FramebufferTextureLoader
。 这种纹理类型扩展了WebGLRenderTarget
,它的加载器可以配置为以给定大小的分块块加载纹理,并使用requestIdleCallback()
它们组合到帧缓冲区中。
https://baicoianu.com/~bai/three.js/examples/webgl_texture_framebuffer.html
在这个例子中,只需选择一个图像大小和一个平铺大小,它就会开始加载过程。 首先我们将纹理初始化为纯红色。 我们开始下载图像(它们大约 10 mb,所以请稍等),当它们完成时,我们将背景更改为蓝色。 在这一点上,我们开始用createImageBitmap()
解析图像来解析文件,完成后我们设置了许多空闲回调,其中包含对createImageBitmap()
进一步调用,从而有效地将图像拆分为瓦片. 这些图块在多个帧上渲染到帧缓冲区中,并且对帧时间的影响比一次性完成要小得多。
注意 - FireFox 目前似乎没有实现createImageBitmap
所有版本,并且当它尝试拆分成图块时,它目前正在为我抛出一个错误。 因此,此演示目前仅适用于 Chrome。 有没有人参考 FireFox 中的createImageBitmap
支持路线图?
我需要做一些清理工作,这个原型有点乱,但我对结果很满意,一旦我能找到解决跨浏览器问题(画布回退等)的方法,我就考虑使用它作为 JanusWeb 中所有纹理的默认值。 淡入效果也很简洁,我们甚至可以先想象一下缩小的版本,然后逐步加载细节更高的图块。
是否有任何与性能或功能相关的原因,任何人都可以想到为什么为场景中的每个纹理都设置一个帧缓冲区而不是标准纹理参考可能是个坏主意? 我找不到任何关于最大值的信息。 每个场景的帧缓冲区,据我所知,一旦设置了帧缓冲区,如果你不渲染它,那么它与任何其他纹理参考相同,但我有这种感觉,就像我错过了一些明显的东西为什么这将是一个非常糟糕的主意:)
@jbaicoianu re: firefox 的createImageBitmap,原因是不支持dictionary 参数,所以不支持图片方向或颜色空间转换。 它使 API 的大多数应用程序变得毫无用处。 我提交了两个与该问题相关的错误: https : https://bugzilla.mozilla.org/show_bug.cgi?id=1335594
@spite这也是我的想法,我看到过这个关于不支持选项字典的错误 - 但在这种情况下,我什至没有使用它,我只是想使用 x、y、w、h 选项。 我得到的具体错误是:
Argument 4 of Window.createImageBitmap '1024' is not a valid value for enumeration ImageBitmapFormat.
这令人困惑,因为我在规范中没有看到任何版本的createImageBitmap
以ImageBitmapFormat
作为参数。
是否有任何与性能或功能相关的原因,任何人都可以想到为什么为场景中的每个纹理都设置一个帧缓冲区而不是标准纹理参考可能是个坏主意? 我找不到任何关于最大值的信息。 每个场景的帧缓冲区,据我所知,一旦设置了帧缓冲区,如果你不渲染它,那么它与任何其他纹理参考相同,但我有这种感觉,就像我错过了一些明显的东西为什么这将是一个非常糟糕的主意:)
@jbaicoianu THREE.WebGLRenderTarget
保留了一个帧缓冲区、一个纹理和一个渲染缓冲区。 组装好纹理后,可以删除帧缓冲区和渲染缓冲区,只保留纹理。 这样的事情应该这样做(未测试):
texture = target.texture;
target.texture = null; // so the webgl texture is not deleted by dispose()
target.dispose();
@wrr很高兴知道,谢谢。 我肯定也必须在内存效率上做一个传递 - 如果你改变参数足够多,它不可避免地会在某些时候崩溃,所以我知道我还没有做一些清理工作。 任何其他类似的提示将不胜感激。
@mrdoob和@jbaicoianu我忘了提到我也喜欢这个主意。 😄
我已经整理了OBJLoader
和WWOBJLoader
和所有示例(代码)的代码(重新设计的 init、工作指令对象、替换垃圾多回调处理、公共资源描述等)。 两个装载机现在已准备好进行组合。 根据我的业余时间,他们有望在下周某个时候根据您的蓝图进行:
定向 WWOBJLoader2 测试:
https://kaisalmen.de/proto/test/wwparallels/main.src.html
通用WorkerSupport
定向用户:
https://kaisalmen.de/proto/test/meshspray/main.src.html
大压缩 OBJ 文件测试:
https://kaisalmen.de/proto/test/wwobjloader2stage/main.src.html
我将在可用时使用较新的代码更新上述示例并通知您。
2017-07-30 更新: OBJLoader2
和WWOBJLoader2
现在使用相同的解析器。 他们直接或从工作人员将数据传递给通用构建器函数。
2017-07-31 更新: WWOBJLoader2
不见了。 OBJLoader2
提供parse
和parseAsync
、 load
和run
(由LoaderDirector
或手动输入)
2017-08-09 更新:
将更新移至新帖子。
OBJLoader2
的签名和行为再次与OBJLoader
兼容(我在进化过程中打破了这一点), OBJLoader2
提供parseAsync
和load
与useAsync
标志。 我想,现在可以称为 V2.0.0-Beta 了。 在这里您可以找到当前的开发状态:
https://github.com/kaisalmen/WWOBJLoader/tree/V2.0.0-Beta/src/loaders
我已经提取了用作实用程序和所需支持工具的 LoaderSupport 类(独立于 OBJ)。 它们可以重新用于潜在的其他基于工人的装载机。 下面的所有代码,我都放在命名空间THREE.LoaderSupport
以突出其对OBJLoader2
依赖:
Builder
:用于一般网格构建WorkerDirector
:通过反射创建加载器,在队列中处理PrepData
并配置好数量的工人。 用于完全自动化加载器(MeshSpray 和 Parallels 演示)WorkerSupport
:从现有代码创建工人并建立简单通信协议的实用程序类PrepData
+ ResourceDescriptor
:用于自动化的描述或简单地用于示例之间的统一描述Commons
: 加载器可能的基类(捆绑通用参数)Callbacks
: (onProgress, onMeshAlter, onLoad) 用于自动化和方向, LoadedMeshUserOverride
用于提供从onMeshAlter
(下面 objloader2 测试中的法线添加)Validator
:空/未定义变量检查@mrdoob @jbaicoianu OBJLoader2
现在按照建议包装解析器(它配置了全局设置的参数或由PrepData
以运行)。 Builder
接收每个原始网格,解析器返回基本节点,但除此之外,它与蓝图匹配。
在OBJLoader2
仍有一些可能不需要的用于序列化解析器的辅助代码。
Builder 需要清理,因为buildMeshes
函数的合约/参数对象仍然受到 OBJ 加载的严重影响,因此仍被认为是在构建中。
代码需要一些润色,但它已经准备好反馈、讨论、批评等......😄
使用运行和加载的 OBJ 加载器:
https://kaisalmen.de/proto/test/objloader2/main.src.html
OBJ Loader 使用 run async 和 parseAsync:
https://kaisalmen.de/proto/test/wwobjloader2/main.src.html
直接使用 run async OBJLoader2:
https://kaisalmen.de/proto/test/wwparallels/main.src.html
通用 WorkerSupport 的定向使用:
https://kaisalmen.de/proto/test/meshspray/main.src.html
大压缩 OBJ 文件测试:
https://kaisalmen.de/proto/test/wwobjloader2stage/main.src.html
看起来不错! 你知道OBJLoader
的这些变化吗? #11871 565c6fd0f3d9b146b9434e5fccfa2345a90a3842
是的,我需要移植这个。 我提出了一些可重复的性能测量。 这两个周末都将开始工作。 你打算什么时候发布 r87? N-gon 支持可能取决于日期。
@mrdoob和瞧: https :
状态更新(代码):
创建的工作人员现在可以通过消息接收的参数配置工作人员内部的任何解析器。 WorkerSupport
提供了一个参考 worker runner 实现(代码),如果需要或需要,可以完全替换为自己的代码。
工人将创建并运行在解析器run
的方法WorkerRunnerRefImpl
( Parser
是可用的工人范围内; this.applyProperties
呼叫制定者或性能解析器):
WorkerRunnerRefImpl.prototype.run = function ( payload ) {
if ( payload.cmd === 'run' ) {
console.log( 'WorkerRunner: Starting Run...' );
var callbacks = {
callbackBuilder: function ( payload ) {
self.postMessage( payload );
},
callbackProgress: function ( message ) {
console.log( 'WorkerRunner: progress: ' + message );
}
};
// Parser is expected to be named as such
var parser = new Parser();
this.applyProperties( parser, payload.params );
this.applyProperties( parser, payload.materials );
this.applyProperties( parser, callbacks );
parser.parse( payload.buffers.input );
console.log( 'WorkerRunner: Run complete!' );
callbacks.callbackBuilder( {
cmd: 'complete',
msg: 'WorkerRunner completed run.'
} );
} else {
console.error( 'WorkerRunner: Received unknown command: ' + payload.cmd );
}
};
来自OBJLoader2.parseAsync
消息如下所示:
this.workerSupport.run(
{
cmd: 'run',
params: {
debug: this.debug,
materialPerSmoothingGroup: this.materialPerSmoothingGroup
},
materials: {
materialNames: this.materialNames
},
buffers: {
input: content
}
},
[ content.buffer ]
);
消息对象依赖于加载器,但工作器中解析器的配置是通用的。
上一篇文章中链接示例使用的代码已更新为最新代码。
我认为 OBJLoader2 的演变和支持功能的提取现在已经到了需要您的反馈的地步。 当所有示例都从它的 repo 移植到上面的分支时,我将打开一个带有完整摘要的 PR,然后请求反馈
仅供参考,这里有一项正在进行的工作,让 ImageBitmapLoader 使用上述工作人员。 也许更有趣的是,结果中有一些硬性数字: https :
firefox的createImageBitmap,原因是不支持dictionary参数,所以不支持图片方向或色彩空间转换。 它使 API 的大多数应用程序变得毫无用处。
这是不幸的。 ☹️
@mrdoob您是否有计划将ImageLoader
中的TextureLoader
切换ImageLoader
ImageBitmapLoader
,因为 ImageBitmap 上传到纹理的阻塞应该更少? 如果我们只传递第一个参数, createImageBitmap()
到目前为止似乎在 FireFox 上工作。 (也许我们不需要通过TextureLoader
传递第二个和更多的参数?)
return createImageBitmap( blob );
createImageBitmap ()
支持选项字典实际上很重要。 否则,您无法更改图像方向(翻转 Y)或指示预乘 alpha 之类的内容。 问题是您不能将WebGLRenderingContext.pixelStorei
用于ImageBitmap
。 从规范:
_如果TexImageSource是ImageBitmap,那么这三个参数(UNPACK_FLIP_Y_WEBGL、UNPACK_PREMULTIPLY_ALPHA_WEBGL、UNPACK_COLORSPACE_CONVERSION_WEBGL)将被忽略。 相反,应使用等效的 ImageBitmapOptions 来创建具有所需格式的 ImageBitmap。_
所以我认为如果 FF 支持选项字典,我们只能切换到ImageBitmapLoader
。 此外,像Texture.premultiplyAlpha
和Texture.flipY
这样的属性现在不适用于ImageBitmap
。 我的意思是如果用户设置它们,它们不会影响基于ImageBitmap
的纹理,这有点不幸。
喔好吧。 我错过了那个规格。
此处还讨论了选项字典的重要性:
bugzilla (https://bugzilla.mozilla.org/show_bug.cgi?id=1367251, https://bugzilla.mozilla.org/show_bug.cgi?id=1335594) 上的错误已经存在......两个几年了? 我不认为他们会花这么长时间来修复它。
所以问题是FF支持“技术上”该功能,但实际上是无用的。 为了使用它,我们可以为使用它的 Chrome 设置一个路径,并为其他不使用它的浏览器设置另一个路径。 问题是,由于 Firefox 确实具有该功能,我们必须进行 UA 嗅探,这很糟糕。
实用的解决方案是执行特征检测:使用带有翻转标志的 cIB 构建一个 2x2 图像,然后回读并确保值正确。
关于 FireFox 的错误,我也会在内部联系他们。 在听到他们的计划后,让我们看看是否需要解决方法。
bugzilla (https://bugzilla.mozilla.org/show_bug.cgi?id=1367251, https://bugzilla.mozilla.org/show_bug.cgi?id=1335594) 上的错误已经存在......两个几年了? 我不认为他们会花这么长时间来修复它。
是的,对不起,我真的有一段时间没有跟进它-_-
所以问题是FF支持“技术上”该功能,但实际上是无用的。 为了使用它,我们可以为使用它的 Chrome 设置一个路径,并为其他不使用它的浏览器设置另一个路径。 问题是,由于 Firefox 确实具有该功能,我们必须进行 UA 嗅探,这很糟糕。
实用的解决方案是执行特征检测:使用带有翻转标志的 cIB 构建一个 2x2 图像,然后回读并确保值正确。
是的,我同意这两种解决方案都非常糟糕,我们应该尽量避免使用它们,因此在深入研究其中任何一个之前,让我们看看我们是否可以在我们这边解除对它的阻止
我做了ImageBitmap
上传性能测试。 每 5 秒上传一次纹理。
您可以比较常规图像与 ImageBitmap。
https://rawgit.com/takahirox/three.js/ImageBitmapTest/examples/webgl_texture_upload.html (普通图片)
https://rawgit.com/takahirox/three.js/ImageBitmapTest/examples/webgl_texture_upload.html?imagebitmap (ImageBitmap)
在我的窗户上我看到
| 浏览器 | 8192x4096 JPG 4.4MB | 2048x2048 PNG 4.5MB |
| ---- | ---- | ---- |
| 铬图像| 500 毫秒 | 140ms |
| Chrome ImageBitmap | 165ms | 35ms |
| 火狐图像| 500 毫秒 | 40ms |
| FireFox ImageBitmap | 500 毫秒 | 60ms |
( texture.generateMipmaps
是true
)
我的想法
即使使用 ImageBitmap,上传纹理似乎仍然会阻止大纹理。 也许我们需要部分上传技术或非阻塞的东西。
我想这个问题的一个解决方案可能是使用纹理压缩格式并避免使用 JPG 或 PNG(因此ImageBitmap
)。 在这种情况下看到一些性能数据会很有趣。
是的,同意了。 但我想我们可能仍然会看到大纹理的阻塞,尤其是在像移动设备这样的低功耗设备上。 不管怎样,先评估性能。
或者使用 schedule/requestIdleCallback texSubImage2D
rIC = requestIdleCallback?
是的,我进行了忍者编辑
好的。 是的同意了。
顺便说一句,我还不熟悉压缩纹理。 让我确认一下我的理解。 我们不能将压缩纹理与ImageBitmap
因为compressedTexImage2D
不接受ImageBitmap
,对吗?
https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/compressedTexImage2D
我回去重新审视我的旧 TiledTextureLoader 实验 - 似乎它们现在导致我的视频驱动程序崩溃并重新启动:(
(编辑:实际上,看起来甚至直接在 chrome 中加载最大的纹理(16k x 16k - https://baicoianu.com/~bai/three.js/examples/textures/dotamap1_25.jpg)是导致崩溃的原因。这曾经工作得很好,所以似乎在 chrome 的图像处理中有些回归)
我使用 requestIdleCallback、ImageBitmap 和 ES6 生成器做了一些实验,将大纹理分成多个块以上传到 GPU。 我使用了帧缓冲区而不是常规纹理,因为即使您使用 texSubimage2D 来填充图像数据,您仍然需要预先分配内存,这需要将一堆空数据上传到 GPU,而可以创建帧缓冲区并使用单个 GL 调用进行初始化。
这些更改的存储库在这里仍然可用https://github.com/jbaicoianu/THREE.TiledTexture/
我记得实验的一些笔记:
我的结果是相似的:上传速度和卡顿之间存在权衡。 (顺便说一句,我创建了这个 https://github.com/spite/THREE.UpdatableTexture)。
我认为对于在 WebGL 1 中工作的第二个选项,您实际上需要两个纹理,或者至少需要修改 UV 坐标。 在 WebGL 2 中,我认为复制与目标纹理大小不同的源更容易。
是的,使用 texSubImage2D 我认为这种调整大小是不可能的,但是当使用帧缓冲区时,我使用 OrthographicCamera 来渲染带有纹理片段的平面,所以这只是改变平面比例的问题那个绘制调用。
关于ImageBItmap在FireFox上的性能问题,我在bugzilla上开了一个bug
我一直在尝试更好地了解与纹理相关的数据何时实际加载到 GPU 并遇到此线程。 在我的特定用例中,我不关心将本地 jpeg/gif 文件加载和解码为纹理,我只关心尝试将纹理数据预加载到 GPU 上。 阅读完这个帖子后,我必须承认我不完全确定它是解决这两个问题还是只解决前者? 鉴于我只关心后者,我是否需要寻找不同的解决方案,或者这里有什么东西可以帮助强制将纹理数据加载到 GPU 中?
最有用的评论
THREE.UpdatableTexture 的第一个提案发布
理想情况下,它应该是任何 THREE.Texture 的一部分,但我会首先探索这种方法。