Threejs 技巧和窍门大清单


说明:该系列博文翻译自Lewy Blue的discoverthreejs.com教程 。文章不得转载,按照原作者的意思是:不希望Discover three.js的内容在其它地方进行发布,如果是翻译内容,需要链接到他博文新的页面,谢谢。


大家好! 在写这本书时候,我一直在收集大量的技巧、窍门、注意事项和陷阱。 此页内容涵盖了我目前找到的所有内容。

并不是所有这里说的技巧都经过了验证,尤其是性能方面的技巧。 盲目地遵循这一列表会存在太多不确定性,因此请务必彻底测试你的应用程序,看看哪些对你有用。 这些是建议,而不是规则(对于绝大部分而言)。 也就是说,此页内容对任何体量的应用程序都提供了许多有用的技巧。

如果你有任何想要添加的,又或是你发现了任何错误,告诉我,我会更新此页内容。

这里的大部分信息都不是只针对three.js,甚至也不只针对WebGL,而是适用于任何实时图形应用程序或框架。

编程愉快!


入门小白友情提示,或者说是帮助!为什么我什么都看不到?

你已经跟着学习了几个基本教程内容,一切都是那么美好。 现在你正创建一个自己的应用,并且你已经_完全_按照教程中的说明设置了所有内容。 但是你就是什么都看不到! 什么鬼??

下面的一些思路可以助你找出问题。

1.检查浏览器控制台的错误信息

检查浏览器控制台的错误信息

但你已经这样做了,对吧?

2. 将背景颜色设置为黑色以外的颜色

看着一块黑色画布(canvas),干瞪眼? 如果你只能看得到黑色,是很难判断是否发生了些什么。 试着将背景颜色设置为红色:

import { Color } from './vendor/three/build/three.module.js';
scene.background = new Color('red');

如果你得到一块红色画布,那么至少你的 renderer.render 调用是起作用的,你可以继续查找看还有其他什么问题。

3. 确保场景中有添加了光照(light)并且它照亮了你的物体

如同现实世界中一样,three.js 中的大多数材质都需要有了光照才可见。

4. 用MeshBasicMaterial覆盖场景中的所有材质

MeshBasicMaterial是一种不需要光照参数就可见的材质。 如果你正好在显示物体上遇到问题,可以使用MeshBasicMaterial临时覆盖场景中所有的材质。 如果这么做,物体神奇地出现了,那么你的问题是缺少光照。

import {
  MeshBasicMaterial
} from './vendor/three/build/three.module.js';

scene.overrideMaterial = new MeshBasicMaterial({color: 'green'});

5. 你的物体是否在相机的视锥体(viewing frustum)内?

如果您的物体不在视锥体( viewing frustum )内,它将被剪裁。 试着调大你的远剪裁平面参数值:

camera.far = 100000;
camera.updateProjectionMatrix();

记住,这只是为了测试用! 相机的视锥体以米为单位,为了获得最佳性能,你应该将其设置得尽可能小。 一旦场景设置好并正常运行后,应尽可能减小视锥体的大小。

6. 你的相机在物体里面吗?

默认情况下,所有物体都在(0,0,0)点创建,也就是 origin。 确保你已将相机向后移动,以便你可以看到场景!

camera.position.z = 10;

7. 仔细考虑场景内的缩放比例 (scale)

试着可视化你的场景,并记住three.js 中的一个单位对应是一米。 所有物体都是以合理的规则组合在一起的吗? 或者你可能看不到任何物体,是因为你刚刚加载的物体只有 0.00001 米宽。 等等,屏幕中间的那个小黑点是什么?

通用技巧

  1. JavaScript 中的对象的创建开销很大,所以不要在循环中创建对象。取而代之地,应创建一个单一的对象,例如 Vector3 并使用vector.set() 或类似的方法来重用循环内的对象。

  2. 渲染循环也是如此。为了确保您的应用以每秒 60 帧的丝滑般渲染,尽可能减少渲染循环中的消耗。不要每帧都创建新的对象。

  3. 总是使用 BufferGeometry ,而不是 Geometry,它更快(性能更好)。

  4. 预先封装好的对象也是如此,始终使用缓冲几何版本(BoxBufferGeometry而不是 BoxGeometry)。

  5. 始终尝试重用对象,例如物体、材质、纹理等(尽管更新某些内容可能比创建新内容慢,请参阅下面说的纹理技巧)。

使用国际单位制(SI Units)

three.js 处处都使用的国际单位制。如果你也使用该单位制,你会发现事情变得更顺利。如果你出于某种原因确实使用了不同类型的单位,例如英寸(颤抖),请确保你有充分的理由去这样做。

国际单位制

SI单位

  • 距离以为单位(three.js 的1 单位 = 1 米)。
  • 时间以秒为单位
  • 光照以国际光照强度单位测量, 坎德拉(cd)、流明 (lm) 和勒克斯 (lx)(至少你需开启renderer.physicallyCorrectLights)。

如果你要创建史诗级规模场景(空间模拟或类似的东西),请使用缩放因子或切换到使用 对数深度缓冲区-logarithmic depth buffer

准确的颜色

为了达到(近似乎)准确的颜色,这么设置渲染器:

renderer.gammaFactor = 2.2;
renderer.outputEncoding = THREE.sRGBEncoding;

对于颜色类,这么设置:

const color = new Color(0x800080);
color.convertSRGBToLinear();

又或者,更常见的情况是,在材质中设置颜色:

const material = new MeshBasicMaterial({ color:0x800080});
material.color.convertSRGBToLinear();

最后,为了得到(近似乎)正确的纹理颜色,你只需要针对颜色,环境、自发光贴图设置纹理编码方式

import {
  sRGBEncoding
} from './vendor/three/build/three.module.js';

const colorMap = new TextureLoader().load('colorMap.jpg');
colorMap.encoding = sRGBEncoding;

所有其他纹理类型应保持为颜色的线性空间(linear space)中。这是默认设置,因此你无需更改除颜色、环境和自发光贴图以外的任何纹理的编码。

需要注意的是,我在这里说是几乎是正确的,因为目前 Three.js 颜色管理还不太正确。希望它会很快得到修复,但与此同时,任何颜色的不准确都非常小,除非你是进行科学或医学方面的渲染,否则任何人都不太可能注意到这些。

JavaScript

不要你以为你知道什么会更快

Web 浏览器使用的 JavaScript 引擎经常变动,并在后台对你的代码进行了大量优化。不要相信你对什么会更快的直觉,总是测试看看。不要听几年前的文章告诉你要避免某些方法,例如array.maparray.forEach。自己测试这些,或者查找过去几个月的文章,并辅以适当的测试。

使用样式指南和代码检测工具(linter)

就个人而言,我使用的组合 EslintPrettier,以及 Airbnb样式指南本教程第 2 部分)花了我大约 30 分钟 在 VSCode 中进行设置,现在我再也不用浪费时间进行格式化、整理或去怀疑某一条特定的语法是否是妥当了。

许多使用 Three.js 的人更喜欢 **Mr.doob’s Code Style™ **而不是 Airbnb,所以如果你更喜欢使用它,只需将 eslint-config-airbnb插件替换为 eslint-config-mdcs

模型、网格和其他可见的事物

  1. 避免使用常见的基于文本的 3D 数据格式(例如 Wavefront OBJ 或 COLLADA)进行数据交付。取而代之,请使用针对 Web端 优化的格式,例如 glTF。

  2. 使用 Draco 对 glTF 进行网格压缩。有时候这可以将 glTF 文件减小到其原始大小的 10% 以下!

  3. 或者,该部分有一个新工具叫 gltfpack ,在某些情况下,它可能会有比 Draco 更好的效果。

  4. 如果您需要操作大量物体可见和不可见(或从场景中添加/删除它们),请考虑使用图层-Layers 以获得最佳性能。

  5. 处于完全相同位置的物体会导致闪烁(Z-fighting)。试着移动 0.001 之类的微小量,使物体看起来处于同一位置,同时 GPU 也满足渲染条件。

  6. 保持场景以原点为中心,以减少大坐标处的浮点误差。

  7. 永远不要移动你的场景Scene。 在(0,0,0)位置创建,这是其中所有物体的默认参考系。

相机

  1. 使您的视锥体尽可能的小,以获得更好的性能。在开发过程中使用大的视锥体是可以的,但是一旦你微调应用以进行部署时,请让你的视锥体尽可能小,FPS提升一小步,性能提升一大步。

  2. 不要把物体放在远剪裁平面上(特别是如果你的远剪裁平面真的很大),因为这会引起闪烁。

渲染器

  1. 不要启用preserveDrawingBuffer,除非你确实需要

  2. 除非需要,否则禁用透明缓冲区(alpha buffer )。

  3. 除非需要,否则不要启用模板缓冲区(stencil buffer)。

  4. 除非需要,否则禁用深度缓冲区(depth buffer)(但您可能确实需要它)。

  5. 创建渲染器时使用powerPreference: "high-performance"。这可能会促使用户的系统在多 GPU 系统中选择高性能 GPU。

  6. 仅当相机位置的发生变化或动画发生时才考虑渲染。

  7. 如果你的场景是静态的并使用OrbitControls相机,你可以监听控件的change 事件。这样你就可以只在相机移动时渲染场景:

OrbitControls.addEventListener( 'change', () => renderer.render( scene, camera ) );

遵循了最后两条,你也不会获得更高的帧率,但是你的风扇会更少开启,移动设备电池消耗更少。

注意:我在网络上看到一些地方建议你禁用抗锯齿并改用后处理抗锯齿(AA) 通道。在我的测试中,并非如此。在现代硬件上,内置的多重采样抗锯齿(MSAA)似乎也消耗很小,即使是在低功耗移动设备上也是如此。然而后处理的快速近似抗锯齿(FXAA) 或 增强型子像素形态学抗锯齿(SMAA) 通道,在我测试过的每个场景中都会导致相当大的帧率下降,而且质量也比MSAA更低 。

光照

  1. 直射光(SpotLightPointLightRectAreaLightDirectionalLight)很影响性能。在您的场景中尽可能少使用直射光。

  2. 避免在场景中添加和删除光,因为这需要WebGLRenderer重新编译所有着色器程序(它确实缓存了程序,因此后续执行此操作的时间会比第一次更快)。取而代之的使用light.visible = falselight.intensiy = 0

  3. 打开renderer.physicallyCorrectLights,使用 国际单位制的正确光照。

阴影

  1. 如果您的场景是静态的,则仅在事物发生变化时更新阴影贴图,而不是每一帧。

  2. 使用 CameraHelper可视化阴影相机的视锥体。

  3. 使阴影视锥体尽可能小。

  4. 使阴影纹理的分辨率尽可能低。

  5. 请记住,点光源阴影比其他阴影类型更消耗性能,因为它们必须渲染六次(每个方向一次),而DirectionalLightSpotLight阴影则需渲染一次。

  6. 当我们谈论PointLight阴影时,请注意,当用于可视化点光源阴影时,CameraHelper只可视化了六个阴影方向中的一个。它仍然很有用,但你需要在其他五个方向上发挥你的想象力。

材质

  1. MeshLambertMaterial不适用于闪亮拉丝的材质,但对于像布料这样的哑光材质,它会产生非常相似MeshPhongMaterial的结果,但性能更高。

  2. 如果你使用的是顶点变形目标(morph targets),请确保您在材质中设置了morphTargets = true,否则它们将不起作用!

  3. 这同样适用于 变形法线morph normals

  4. 如果你正在使用 SkinnedMesh来制作骨骼动画,确保 material.skinning = true.

  5. 与变形目标、变形法线或蒙皮一起使用的材质无法共享。你需要为每个蒙皮或变形的网格创建单独的材质( material.clone()在这里非常适用)。

自定义材质

  1. 仅在你的uniform变量发生变化时更新它,而不是每一帧。

几何

  1. 避免使用LineLoop,因为它必须由直线拟合而成。

纹理

  1. 你所有的纹理都需要是 2 的幂次方 (POT) 大小

    1 、2 、4 、8 、16 ,…,512 ,2048 ,….

  2. 不要更改纹理的尺寸。而是创建新的, 性能更高

  3. 尽可能使用最小尺寸的纹理(您可以使用 256x256 的平铺纹理吗?您可能会感到惊讶!)。

  4. 非 2 的幂次方 (NPOT) 纹理需要linear或nearest filtering滤镜参数设置,以及clamp-to-border或clamp-to-edge包裹方式(详见textures常量)。不支持 Mipmap 过滤和重复包裹。但说真的,就不要使用 NPOT 纹理。

  5. 具有相同尺寸的纹理在内存中的大小相同,因此 JPG 的文件可能比 PNG 小,但它会在您的 GPU 上占用相同数量的内存。

抗锯齿

  1. 抗锯齿的最坏情况是几何体由许多相互平行排列的细直片组成。想想金属百叶窗或格子栅栏。如果可能的话,**不要在场景中包含这样的几何体。**如果别无选择,请考虑用纹理替换晶格,因为这可能会产生更好的结果。

后处理

  1. 内置抗锯齿功能不适用于后期处理(至少在 WebGL 1 中)。您需要手动使用FXAASMAA(可能更快、更好)。

  2. 当你没有使用内置抗锯齿( AA),请务必禁用它!

  3. three.js 有大量的后处理着色器,这太棒了!但请记住,每个通道都需要渲染整个场景。完成测试后,请考虑是否可以将你的通道合并为一个自定义通道。这样会需要多做一些工作,但可以显着提高性能。

物体的释放

从你的场景中删除一些东西?

首先,考虑不这样做,特别是如果你稍后还需要添加回来。你可以暂时隐藏对象,设置object.visible = false(也适用于光照)或material.opacity = 0。你可以设置light.intensity = 0禁用光照而不会导致着色器重新编译。

如果您确实需要从场景中永久移除物品,请先阅读这篇文章: 如何释放物体-How to dispose of objects

更新场景中的对象?

阅读这篇文章: 如何更新-How to update things

性能

  1. 为静态或很少移动的物体设置object.matrixAutoUpdate = false ,并手动调用object.updateMatrix() 在其位置/旋转/四元数/缩放更新时。
  2. **透明物体很消耗性能。**在场景中尽可能少使用透明物体。
  3. 尽可能使用alphatest , 而不是标准透明度,它会更快。
  4. 在测试应用程序的性能时,你需要做的第一件事就是检查它是受 CPU 限制还是受 GPU 限制。使用基础材质(basic material )替换所有材质scene.overrideMaterial(请参阅入门技巧和页面的开头)。如果性能提高,那么你的应用程序受 GPU 限制。如果性能没有提高,则你的应用程序受 CPU 限制。
  5. 在性能好的机器上进行性能测试时,你可能最多获得 60FPS 的帧率。运行 chrome 以open -a "Google Chrome" --args --disable-gpu-vsync获得无限制的帧率。
  6. 现代移动设备的像素比高达 5 - 考虑将这些设备上的最大像素比限制为 2 或 3。以场景的一些非常轻微的模糊为代价,你将获得可观的性能提升。
  7. 烘焙光照和阴影贴图以减少场景中的光照数量。
  8. 密切关注场景中的绘制方法的(drawcalls)调用次数。一个好的经验法则是更少的绘制调用 = 更好的性能。
  9. 远处的物体不需要同靠近相机的物体具有相同的模型精度。有许多可以降低远处物体的精度的技巧来提高性能。考虑使用 LOD(细节层次)物体。你也可以只为远处的物体每 2 或 3 帧更新位置/动画,或者用billboard替换它们 - 即物体的图片。

高阶技巧

  1. 不要用TriangleFanDrawMode,很耗性能。

  2. 当您有成百或上千个相似的几何图形时,请使用几何实例化。

  3. 在 GPU 而非 CPU 上进行动画处理,尤其是在为顶点或粒子设置动画时(请参阅 THREE.Bas以了解执行此操作的一种方法)。

也阅读下这些文章!

Unity 和 Unreal 引擎的文档也有包含大量性能建议的文章,其中大部分也适用于three.js 。也请阅读以下内容:

WebGL Insights 整本书中收集了许多技巧。它更具技术性,但也值得一读,尤其是在你编写自己的着色器时。

参考文献


BIM树洞

做一个静谧的树洞君

用建筑的语言描述IT事物;

用IT的思维解决建筑问题;

共建BIM桥梁,聚合团队。

本学习分享资料不得用于商业用途,仅做学习交流!!如有侵权立即删除!!

微信公众号: BIM树洞
在这里插入图片描述

气氛组博客:http://www.31bim.com

BIM应用及咨询,CAD,Revit, OpenGL,图形开发交流,加Q群1083064224

  • 0
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值