Three.js - Sprite / 文本标签性能
Three.js - Sprite / Text Label Performance
有一个 three.js 场景,其中包含一些 3D 对象和 200 - 300 个小文本标签(< 10% 在一个视角下对相机可见)。添加文本精灵将 FPS 从 60 降低到 30 - 40,而且它也非常消耗内存。
有没有办法让精灵更快?
我阅读了缓存 material,但标签都是唯一的 - 所以这是不可能的。
测试:https://jsfiddle.net/h9sub275/4/
(您可以更改 SPRITE_COUNT 以查看计算机上的 FPS 下降)
编辑 1:将 canvas 大小设置为文本边界将减少内存消耗,但不会提高 FPS。
var Test = {
SPRITE_COUNT : 700,
init : function() {
this.renderer = new THREE.WebGLRenderer({antialias : true}); // false, a bit faster without antialias
this.renderer.setPixelRatio(window.devicePixelRatio);
this.renderer.setSize(window.innerWidth, window.innerHeight);
this.container = document.getElementById('display');
this.container.appendChild(this.renderer.domElement);
this.camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 1000);
this.scene = new THREE.Scene();
this.group = new THREE.Object3D();
this.scene.add(this.group);
for (var i = 0; i < this.SPRITE_COUNT; i++) {
var sprite = this.makeTextSprite('label ' + i, 24);
sprite.position.set(Math.random() * 20 - 10, Math.random() * 20 - 10, Math.random() * 20 - 10);
this.group.add(sprite);
}
this.stats = new Stats();
this.stats.domElement.style.position = 'absolute';
this.stats.domElement.style.left = '0px';
this.stats.domElement.style.top = '0px';
document.body.appendChild(this.stats.domElement);
this.render();
},
render : function() {
var self = this;
this.camera.rotation.x += 0.002;
this.renderer.render(this.scene, this.camera);
this.stats.update();
requestAnimationFrame(function() {self.render();});
},
makeTextSprite : function(message, fontsize) {
var ctx, texture, sprite, spriteMaterial,
canvas = document.createElement('canvas');
ctx = canvas.getContext('2d');
ctx.font = fontsize + "px Arial";
// setting canvas width/height before ctx draw, else canvas is empty
canvas.width = ctx.measureText(message).width;
canvas.height = fontsize * 2; // fontsize * 1.5
// after setting the canvas width/height we have to re-set font to apply!?! looks like ctx reset
ctx.font = fontsize + "px Arial";
ctx.fillStyle = "rgba(255,0,0,1)";
ctx.fillText(message, 0, fontsize);
texture = new THREE.Texture(canvas);
texture.minFilter = THREE.LinearFilter; // NearestFilter;
texture.needsUpdate = true;
spriteMaterial = new THREE.SpriteMaterial({map : texture});
sprite = new THREE.Sprite(spriteMaterial);
return sprite;
}
};
window.onload = function() {Test.init();};
你是对的,three.js确实导致GPU以这种方式使用大量纹理内存。请记住,发送到 GPU 的每个纹理都必须与其宽度一样高,因此为每个精灵制作一个 canvas 会浪费大量内存。
这里的一个挑战是 three.js 设计选择,即在 Texture
上有一组 UV 坐标;即使您将标签精灵合并到一个纹理贴图中,并且您 .clone()
每个 material 的纹理没有一些额外的努力,它仍然会在不共享的情况下将每个 Texture
发送到 GPU记忆。简而言之,它目前没有记录的方式告诉它这些纹理是相同的,并且你不能将每个 Material
指向相同的 Texture
因为它不是 Material
保持紫外线。 https://github.com/mrdoob/three.js/issues/5821 讨论了这个问题。
我通过将精灵组合到一张或多张纹理贴图中解决了这些问题。为此,我创建了一个 "sprite texture atlas manager" 来管理我需要的精灵纹理的分配,并且我将背包算法移植到 JS,这有助于我(大部分)用我的标签填充这些纹理贴图,所以不是很多内存被浪费了。
我已经在一个单独的库中提取了我的代码,它可以在这里找到:https://github.com/Leeft/three-sprite-texture-atlas-manager with a live example (which does not yet use sprites, but that should be easy to add) at http://jsfiddle.net/Shiari/sbda72k9/。
幸运的是,虽然这还没有记录,但我还发现现在在最近的版本中(至少是 r73,也许还有 r72)很容易强制纹理共享 GPU 内存,方法是确保它们都有相同的 .uuid
值。我的库确保使用它,并且在我目前的测试中(使用 2048x2048 精灵贴图;我只需要其中两个具有我正在渲染的大小)这使得 GPU 内存在不共享时从 ~2.6GB 下降共享时约 300-600MB。 (当你只在那里放置一个标签时,2048px 太大了,当不共享贴图时,减小纹理大小有很大帮助)。
最后,根据您自己的回答,绘制调用和剔除也是 r73 中的性能问题。不过我从来没有遇到过这个问题,因为我已经通过对所有内容进行分组来批处理绘制调用。
这是 three.js WebGLRenderer 中的一个错误(缺少对 <= r73 中的精灵的视锥体检查)。它已经固定在 dev 分支中。所以你可以期待它在 r74 中可用。
问题和详细信息 https://github.com/mrdoob/three.js/issues/7371
具有最新开发版本的固定版本:https://jsfiddle.net/h9sub275/9/
使用 r73 的性能测试:https://jsfiddle.net/h9sub275/7/
(点击查看手动移除不可见精灵和不移除它们的性能差异)
最新开发版本:
<script src="https://rawgit.com/mrdoob/three.js/dev/build/three.js"> </script>
有一个 three.js 场景,其中包含一些 3D 对象和 200 - 300 个小文本标签(< 10% 在一个视角下对相机可见)。添加文本精灵将 FPS 从 60 降低到 30 - 40,而且它也非常消耗内存。
有没有办法让精灵更快? 我阅读了缓存 material,但标签都是唯一的 - 所以这是不可能的。
测试:https://jsfiddle.net/h9sub275/4/ (您可以更改 SPRITE_COUNT 以查看计算机上的 FPS 下降)
编辑 1:将 canvas 大小设置为文本边界将减少内存消耗,但不会提高 FPS。
var Test = {
SPRITE_COUNT : 700,
init : function() {
this.renderer = new THREE.WebGLRenderer({antialias : true}); // false, a bit faster without antialias
this.renderer.setPixelRatio(window.devicePixelRatio);
this.renderer.setSize(window.innerWidth, window.innerHeight);
this.container = document.getElementById('display');
this.container.appendChild(this.renderer.domElement);
this.camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 1000);
this.scene = new THREE.Scene();
this.group = new THREE.Object3D();
this.scene.add(this.group);
for (var i = 0; i < this.SPRITE_COUNT; i++) {
var sprite = this.makeTextSprite('label ' + i, 24);
sprite.position.set(Math.random() * 20 - 10, Math.random() * 20 - 10, Math.random() * 20 - 10);
this.group.add(sprite);
}
this.stats = new Stats();
this.stats.domElement.style.position = 'absolute';
this.stats.domElement.style.left = '0px';
this.stats.domElement.style.top = '0px';
document.body.appendChild(this.stats.domElement);
this.render();
},
render : function() {
var self = this;
this.camera.rotation.x += 0.002;
this.renderer.render(this.scene, this.camera);
this.stats.update();
requestAnimationFrame(function() {self.render();});
},
makeTextSprite : function(message, fontsize) {
var ctx, texture, sprite, spriteMaterial,
canvas = document.createElement('canvas');
ctx = canvas.getContext('2d');
ctx.font = fontsize + "px Arial";
// setting canvas width/height before ctx draw, else canvas is empty
canvas.width = ctx.measureText(message).width;
canvas.height = fontsize * 2; // fontsize * 1.5
// after setting the canvas width/height we have to re-set font to apply!?! looks like ctx reset
ctx.font = fontsize + "px Arial";
ctx.fillStyle = "rgba(255,0,0,1)";
ctx.fillText(message, 0, fontsize);
texture = new THREE.Texture(canvas);
texture.minFilter = THREE.LinearFilter; // NearestFilter;
texture.needsUpdate = true;
spriteMaterial = new THREE.SpriteMaterial({map : texture});
sprite = new THREE.Sprite(spriteMaterial);
return sprite;
}
};
window.onload = function() {Test.init();};
你是对的,three.js确实导致GPU以这种方式使用大量纹理内存。请记住,发送到 GPU 的每个纹理都必须与其宽度一样高,因此为每个精灵制作一个 canvas 会浪费大量内存。
这里的一个挑战是 three.js 设计选择,即在 Texture
上有一组 UV 坐标;即使您将标签精灵合并到一个纹理贴图中,并且您 .clone()
每个 material 的纹理没有一些额外的努力,它仍然会在不共享的情况下将每个 Texture
发送到 GPU记忆。简而言之,它目前没有记录的方式告诉它这些纹理是相同的,并且你不能将每个 Material
指向相同的 Texture
因为它不是 Material
保持紫外线。 https://github.com/mrdoob/three.js/issues/5821 讨论了这个问题。
我通过将精灵组合到一张或多张纹理贴图中解决了这些问题。为此,我创建了一个 "sprite texture atlas manager" 来管理我需要的精灵纹理的分配,并且我将背包算法移植到 JS,这有助于我(大部分)用我的标签填充这些纹理贴图,所以不是很多内存被浪费了。
我已经在一个单独的库中提取了我的代码,它可以在这里找到:https://github.com/Leeft/three-sprite-texture-atlas-manager with a live example (which does not yet use sprites, but that should be easy to add) at http://jsfiddle.net/Shiari/sbda72k9/。
幸运的是,虽然这还没有记录,但我还发现现在在最近的版本中(至少是 r73,也许还有 r72)很容易强制纹理共享 GPU 内存,方法是确保它们都有相同的 .uuid
值。我的库确保使用它,并且在我目前的测试中(使用 2048x2048 精灵贴图;我只需要其中两个具有我正在渲染的大小)这使得 GPU 内存在不共享时从 ~2.6GB 下降共享时约 300-600MB。 (当你只在那里放置一个标签时,2048px 太大了,当不共享贴图时,减小纹理大小有很大帮助)。
最后,根据您自己的回答,绘制调用和剔除也是 r73 中的性能问题。不过我从来没有遇到过这个问题,因为我已经通过对所有内容进行分组来批处理绘制调用。
这是 three.js WebGLRenderer 中的一个错误(缺少对 <= r73 中的精灵的视锥体检查)。它已经固定在 dev 分支中。所以你可以期待它在 r74 中可用。
问题和详细信息 https://github.com/mrdoob/three.js/issues/7371
具有最新开发版本的固定版本:https://jsfiddle.net/h9sub275/9/
使用 r73 的性能测试:https://jsfiddle.net/h9sub275/7/
(点击查看手动移除不可见精灵和不移除它们的性能差异)
最新开发版本:
<script src="https://rawgit.com/mrdoob/three.js/dev/build/three.js"> </script>