how-to-update-things.html 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281
  1. <!DOCTYPE html><html lang="zh"><head>
  2. <meta charset="utf-8">
  3. <title>如何更新对象</title>
  4. <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
  5. <meta name="twitter:card" content="summary_large_image">
  6. <meta name="twitter:site" content="@threejs">
  7. <meta name="twitter:title" content="Three.js - 如何更新对象">
  8. <meta property="og:image" content="https://threejs.org/files/share.png">
  9. <link rel="shortcut icon" href="../../files/favicon_white.ico" media="(prefers-color-scheme: dark)">
  10. <link rel="shortcut icon" href="../../files/favicon.ico" media="(prefers-color-scheme: light)">
  11. <link rel="stylesheet" href="../resources/lesson.css">
  12. <link rel="stylesheet" href="../resources/lang.css">
  13. <link rel="stylesheet" href="/manual/zh/lang.css">
  14. <script type="importmap">
  15. {
  16. "imports": {
  17. "three": "../../build/three.module.js"
  18. }
  19. }
  20. </script>
  21. </head>
  22. <body>
  23. <div class="container">
  24. <div class="lesson-title">
  25. <h1>如何更新对象</h1>
  26. </div>
  27. <div class="lesson">
  28. <div class="lesson-main">
  29. <div>
  30. <p>默认情况下,只要对象被添加到场景中,就会自动更新其矩阵:</p>
  31. <pre class="prettyprint notranslate lang-js" translate="no">
  32. const object = new THREE.Object3D();
  33. scene.add( object );
  34. </pre>
  35. 或者,它是某个已加入场景对象的子对象:
  36. <pre class="prettyprint notranslate lang-js" translate="no">
  37. const object1 = new THREE.Object3D();
  38. const object2 = new THREE.Object3D();
  39. object1.add( object2 );
  40. scene.add( object1 ); // object1 和 object2 都会自动更新它们的矩阵
  41. </pre>
  42. </div>
  43. <p>但如果你确定对象是静态的,可以关闭自动更新,仅在需要时手动更新变换矩阵。</p>
  44. <pre class="prettyprint notranslate lang-js" translate="no">
  45. object.matrixAutoUpdate = false;
  46. object.updateMatrix();
  47. </pre>
  48. <h2>BufferGeometry</h2>
  49. <div>
  50. <p>
  51. BufferGeometry 把顶点位置、面索引、法线、颜色、UV 以及自定义属性等信息
  52. 存在属性缓冲中,也就是
  53. [link:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Typed_arrays 类型化数组]。
  54. 这种结构通常比旧版 Geometry 更快,但使用起来通常更复杂。
  55. </p>
  56. <p>
  57. 更新 BufferGeometry 时最重要的一点是:不能调整缓冲区大小
  58. (开销很大,基本等同于新建几何体),但可以更新缓冲区已有内容。
  59. </p>
  60. <p>
  61. 这意味着如果你知道某个属性会增长(如顶点数增加),
  62. 必须预先分配足够大的缓冲区来容纳新增数据。
  63. 同时也意味着 BufferGeometry 必然存在最大容量,
  64. 无法做到无限高效扩展。
  65. </p>
  66. <p>
  67. 下面以“运行时不断延长的线段”为例:
  68. 先为 500 个顶点分配空间,但起初只绘制 2 个点,
  69. 通过 `BufferGeometry.drawRange` 控制绘制范围。
  70. </p>
  71. <pre class="prettyprint notranslate lang-js" translate="no">
  72. const MAX_POINTS = 500;
  73. // 几何体
  74. const geometry = new THREE.BufferGeometry();
  75. // 属性
  76. const positions = new Float32Array( MAX_POINTS * 3 ); // 每个点使用 3 个浮点数(x、y、z)
  77. geometry.setAttribute( 'position', new THREE.BufferAttribute( positions, 3 ) );
  78. // 绘制范围
  79. const drawCount = 2; // 仅绘制前 2 个点
  80. geometry.setDrawRange( 0, drawCount );
  81. // 材质
  82. const material = new THREE.LineBasicMaterial( { color: 0xff0000 } );
  83. // 线段
  84. const line = new THREE.Line( geometry, material );
  85. scene.add( line );
  86. </pre>
  87. <p>
  88. 接着按如下方式随机写入线段点位:
  89. </p>
  90. <pre class="prettyprint notranslate lang-js" translate="no">
  91. const positionAttribute = line.geometry.getAttribute( 'position' );
  92. let x = 0, y = 0, z = 0;
  93. for ( let i = 0; i < positionAttribute.count; i ++ ) {
  94. positionAttribute.setXYZ( i, x, y, z );
  95. x += ( Math.random() - 0.5 ) * 30;
  96. y += ( Math.random() - 0.5 ) * 30;
  97. z += ( Math.random() - 0.5 ) * 30;
  98. }
  99. </pre>
  100. <p>
  101. 如果要在首次渲染后改变<em>绘制点数量</em>,这样做:
  102. </p>
  103. <pre class="prettyprint notranslate lang-js" translate="no">
  104. line.geometry.setDrawRange( 0, newValue );
  105. </pre>
  106. <p>
  107. 如果要在首次渲染后修改位置数据,需要设置 `needsUpdate`:
  108. </p>
  109. <pre class="prettyprint notranslate lang-js" translate="no">
  110. positionAttribute.needsUpdate = true; // 首次渲染后修改数据时必须设置
  111. </pre>
  112. <p>
  113. 首次渲染后修改位置数据时,通常还需要重新计算包围体,
  114. 以保证视锥体裁剪、辅助器等功能正常工作。
  115. </p>
  116. <pre class="prettyprint notranslate lang-js" translate="no">
  117. line.geometry.computeBoundingBox();
  118. line.geometry.computeBoundingSphere();
  119. </pre>
  120. <p>
  121. [link:https://jsfiddle.net/t4m85pLr/1/ 这个 fiddle 示例]
  122. 展示了一个动画线段,你可以按需改造。
  123. </p>
  124. <h3>示例</h3>
  125. <p>
  126. [example:webgl_custom_attributes WebGL / custom / attributes]<br />
  127. [example:webgl_buffergeometry_custom_attributes_particles WebGL / buffergeometry / custom / attributes / particles]
  128. </p>
  129. </div>
  130. <h2>材质</h2>
  131. <div>
  132. <p>所有 uniforms 都可以自由修改(如颜色、纹理、不透明度等),并会在每帧传入着色器。</p>
  133. <p>GL 状态相关参数也可随时修改(如 depthTest、blending、polygonOffset 等)。</p>
  134. <p>以下属性在运行时不易修改(尤其材质至少渲染过一次后):</p>
  135. <ul>
  136. <li>uniform 的数量与类型</li>
  137. <li>是否启用以下特性
  138. <ul>
  139. <li>texture(纹理)</li>
  140. <li>fog(雾)</li>
  141. <li>vertex colors(顶点色)</li>
  142. <li>morphing(变形)</li>
  143. <li>shadow map(阴影贴图)</li>
  144. <li>alpha test(Alpha 测试)</li>
  145. <li>transparent(透明)</li>
  146. </ul>
  147. </li>
  148. </ul>
  149. <p>这些变化会触发重建着色器程序,你需要设置:</p>
  150. <code>material.needsUpdate = true</code>
  151. <p>注意这一步可能较慢并导致帧率抖动或卡顿(尤其在 Windows 上,DirectX 下编译 shader 往往比 OpenGL 更慢)。</p>
  152. <p>为获得更平滑体验,可以通过“占位值”模拟部分开关效果,比如强度为 0 的灯光、纯白纹理或密度为 0 的雾。</p>
  153. <p>你可以替换几何体分块所用材质,但无法在运行时改变对象按面材质划分分块的方式。</p>
  154. <h3>如果你需要在运行时切换多套材质配置:</h3>
  155. <p>若材质/分块数量较少,可提前分块(例如人物:头发/脸/身体/上衣/裤子;汽车:前/侧/顶/玻璃/轮胎/内饰)。</p>
  156. <p>若数量很大(例如每个面都可能不同),建议改用属性或纹理驱动每面外观,而非大量分块材质。</p>
  157. <h3>示例</h3>
  158. <p>
  159. [example:webgl_materials_car WebGL / materials / car]<br />
  160. [example:webgl_postprocessing_dof WebGL / webgl_postprocessing / dof]
  161. </p>
  162. </div>
  163. <h2>纹理</h2>
  164. <div>
  165. <p>图像、Canvas、视频和数据纹理若内容有变更,需要设置:</p>
  166. <code>
  167. texture.needsUpdate = true;
  168. </code>
  169. <p>渲染目标会自动更新。</p>
  170. <h3>示例</h3>
  171. <p>
  172. [example:webgl_materials_video WebGL / materials / video]<br />
  173. [example:webgl_rtt WebGL / rtt]
  174. </p>
  175. </div>
  176. <h2>相机</h2>
  177. <div>
  178. <p>相机位置和目标会自动更新。如果你要修改以下参数:</p>
  179. <ul>
  180. <li>
  181. fov(视野范围)
  182. </li>
  183. <li>
  184. aspect(宽高比)
  185. </li>
  186. <li>
  187. near(近裁剪面)
  188. </li>
  189. <li>
  190. far(远裁剪面)
  191. </li>
  192. </ul>
  193. <p>
  194. 则需要重新计算投影矩阵:
  195. </p>
  196. <pre class="prettyprint notranslate lang-js" translate="no">
  197. camera.aspect = window.innerWidth / window.innerHeight;
  198. camera.updateProjectionMatrix();
  199. </pre>
  200. </div>
  201. <h2>InstancedMesh</h2>
  202. <div>
  203. <p>
  204. `InstancedMesh` 用于在 `three.js` 中便捷地进行实例化渲染。
  205. 视锥体裁剪、射线检测等功能依赖最新包围体(包围盒和包围球)。
  206. 由于 `InstancedMesh` 的工作方式,它具有自己的 `boundingBox` 与 `boundingSphere`,
  207. 会覆盖几何体级别的包围体。
  208. </p>
  209. <p>
  210. 与几何体类似,只要底层数据变化就应重算包围体。
  211. 对 `InstancedMesh` 来说,常见场景是通过 `setMatrixAt()` 修改实例变换矩阵后,
  212. 再重算包围体。处理方式与几何体相同。
  213. </p>
  214. <pre class="prettyprint notranslate lang-js" translate="no">
  215. instancedMesh.computeBoundingBox();
  216. instancedMesh.computeBoundingSphere();
  217. </pre>
  218. </div>
  219. <h2>SkinnedMesh</h2>
  220. <div>
  221. <p>
  222. 在包围体机制上,`SkinnedMesh` 与 `InstancedMesh` 原则相同:
  223. 它拥有自己的 `boundingBox` 与 `boundingSphere`,用于正确包裹动画中的网格。
  224. 当调用 `computeBoundingBox()` 与 `computeBoundingSphere()` 时,
  225. 会基于当前骨骼变换(即当前动画状态)计算对应包围体。
  226. </p>
  227. </div>
  228. </div>
  229. </div>
  230. </div>
  231. <script src="../resources/prettify.js"></script>
  232. <script src="../resources/lesson.js"></script>
  233. </body></html>
粤ICP备19079148号