نمایش برچسب، عدد، خط و نشانگر در صحنه‌های Three.js — بررسی کاربردی و پیشنهادها

وقتی باید اندازه‌ها، برچسب‌ها، حاشیه‌نویسی‌ها، آیکون‌ها یا طراحی‌های موقت را روی یک مدل Three.js نشان دهید — به‌خصوص در معماری که جایگذاری و دقت عددی مهم است — چند گزینه کاربردی دارید. در ادامه روش‌های رایج را با کدهای کوتاه و مقایسه آورده‌ام تا برای هر کار ابزار درست را انتخاب کنید.

۱) لایه Canvas 2D (تصویر کردن سه‌بعدی به دوبعدی و رسم روی canvas)

یک canvas معمولی WebGL برای Three.js دارید و یک <canvas> شفاف HTML را رویش قرار می‌دهید (position: absolute). در هر فریم انیمیشن، نقاط سه‌بعدی را با vector.project(camera) تصویر می‌کنید و شکل‌ها و متن‌ها را با Canvas 2D API در موقعیت‌های دوبعدی رسم می‌کنید.

<canvas id="webglCanvas"></canvas>
<canvas id="overlayCanvas" style="position:absolute; top:0; left:0; pointer-events:none;"></canvas>
<script type="module">
  import * as THREE from "three";

  const overlayCanvas = document.getElementById("overlayCanvas");
  const overlayCtx = overlayCanvas.getContext("2d");

  // ... scene, camera, renderer and controls ...

  overlayCanvas.width = window.innerWidth;
  overlayCanvas.height = window.innerHeight;

  const points = [new THREE.Vector3(0, 0, 0), new THREE.Vector3(1, 1, 0)];
  const tempPoint = new THREE.Vector3(1, 0.5, 0);

  function projectTo2D(vec3) {
    const v = vec3.clone().project(camera);
    return {
      x: (v.x * 0.5 + 0.5) * overlayCanvas.width,
      y: (-v.y * 0.5 + 0.5) * overlayCanvas.height,
    };
  }

  function animate() {
    requestAnimationFrame(animate);
    controls.update();
    renderer.render(scene, camera);

    // Clear and redraw the overlay every frame
    overlayCtx.clearRect(0, 0, overlayCanvas.width, overlayCanvas.height);
    overlayCtx.fillStyle = "red";
    points.forEach(p => {
      const { x, y } = projectTo2D(p);
      overlayCtx.beginPath();
      overlayCtx.arc(x, y, 5, 0, Math.PI * 2);
      overlayCtx.fill();
    });

    const start = projectTo2D(points[0]);
    const end = projectTo2D(tempPoint);
    overlayCtx.strokeStyle = "yellow";
    overlayCtx.lineWidth = 2;
    overlayCtx.beginPath();
    overlayCtx.moveTo(start.x, start.y);
    overlayCtx.lineTo(end.x, end.y);
    overlayCtx.stroke();
  }
  animate();
</script>

کی استفاده کنیم: پیش‌نمایش‌های سریع و بصری‌های موقت (کشیدن یک خط جدید در حالی که کاربر ماوس را حرکت می‌دهد)، تعداد زیادی آیتم که به استایل HTML/CSS نیاز ندارند و مش‌های GPU سنگین می‌شوند، و وقتی به حداکثر انعطاف رسم دوبعدی نیاز دارید.

مزایا: پیاده‌سازی خیلی سریع؛ Canvas 2D API ساده و انعطاف‌پذیر است؛ مناسب بصری‌های موقت و لایه‌های پویای زیاد.

معایب: آبجکت سه‌بعدی واقعی نیست — باید در هر فریم دوباره تصویر و رسم کنید تا با دوربین هماهنگ بماند، وگرنه لایه جابه‌جا می‌شود. استایل متن نسبت به HTML/CSS محدود است و برای خطوط واضح باید مختصات را با devicePixelRatio مقیاس کنید.

۲) هندسه سه‌بعدی Three.js (Line، LineSegments، Points، مش‌های کوچک)

هندسه سه‌بعدی واقعی بسازید — THREE.Line با BufferGeometry برای یک بخش اندازه‌گیری، THREE.SphereGeometry یا THREE.Points برای نشانگرها. این آبجکت‌ها در Scene Graph هستند و با WebGL رندر می‌شوند، پس به‌طور طبیعی از تبدیل‌های دوربین، پوشانده شدن و روابط فضایی پیروی می‌کنند.

// A persistent 3D line between two Vector3s (a, b)
const geometry = new THREE.BufferGeometry().setFromPoints([a, b]);
const material = new THREE.LineBasicMaterial({ color: 0xffaa00, linewidth: 2 });
const line = new THREE.Line(geometry, material);
scene.add(line);

کی استفاده کنیم: اندازه‌گیری‌های ماندگار که باید به هندسه بچسبند و با حرکت دوربین دقیق بمانند؛ وقتی پوشانده شدن یا روابط عمقی با مش‌های دیگر مهم است؛ وقتی به ویرایش، انتخاب یا Snapping روی خود آبجکت نیاز دارید.

مزایا: دقیق‌ترین و مطمئن‌ترین گزینه — خط‌ها و پین‌ها آبجکت‌های سه‌بعدی واقعی‌اند و طبیعتاً با دوربین هماهنگ‌اند. می‌توانید از پرفورمنس GPU (BufferGeometry) و شیدرها برای استایل دادن به تعداد زیادی خط استفاده کنید.

معایب: کمی پیچیده‌تر است (مدیریت هندسه‌ها، به‌روزرسانی‌ها و حافظه با شماست). برای هزاران مش کوچک، آن‌ها را Batch کنید یا از Instancing استفاده کنید.

۳) Sprite (THREE.Sprite) و چهارضلعی‌های تکسچردار

THREE.Sprite صفحه‌ای است که همیشه رو به دوربین است و با یک تصویر بیت‌مپ تکسچر می‌شود — مناسب آیکون‌ها یا تصاویر نشانگر کوچک. Spriteها در فضای سه‌بعدی هستند و از تبدیل‌ها و عمق پیروی می‌کنند، اما رستری‌اند.

const labelCanvas = document.createElement("canvas");
// draw text into the canvas context...
const texture = new THREE.CanvasTexture(labelCanvas);
const spriteMaterial = new THREE.SpriteMaterial({ map: texture, depthTest: false });
const sprite = new THREE.Sprite(spriteMaterial);
sprite.position.copy(point3D);
scene.add(sprite);

کی استفاده کنیم: آیکون‌ها یا برچسب‌های کوچکی که باید در فضای سه‌بعدی باشند و همیشه رو به دوربین، یا متنی که به صورت تکسچر رندر می‌شود.

مزایا: ساده و داخل صحنه (از دوربین و پوشانده شدن پیروی می‌کند)؛ می‌تواند پویا از یک canvas ساخته شود.

معایب: هر Sprite یک تکسچر GPU مصرف می‌کند، پس تعداد زیاد به Batch یا Instancing نیاز دارد. کیفیت متن به رزولوشن وابسته است و با مقیاس ممکن است تار شود.

۴) CSS2DRenderer (برچسب‌های HTML متصل به موقعیت‌های سه‌بعدی)

CSS2DRenderer در Three.js عناصر DOM را به CSS2DObjectها در صحنه وصل می‌کند. عناصر در هر فریم به موقعیت درست صفحه منتقل می‌شوند، پس به نظر می‌رسد به نقاط سه‌بعدی چسبیده‌اند. چون DOM باقی می‌مانند، می‌توانید با CSS استایلشان دهید و رویداد و تولتیپ اضافه کنید.

import { CSS2DRenderer, CSS2DObject } from "three/examples/jsm/renderers/CSS2DRenderer.js";

const label = document.createElement("div");
label.className = "label";
label.innerText = "3.24 m";

const labelObject = new CSS2DObject(label);
labelObject.position.copy(point3D);
scene.add(labelObject);

// In the render loop, alongside the WebGL renderer
labelRenderer.render(scene, camera);
renderer.render(scene, camera);

کی استفاده کنیم: برچسب‌های متنی استایل‌دار، تولتیپ‌های تعاملی HTML و رابط‌های حاشیه‌نویسی قابل کلیک — هر وقت به استایل کامل CSS نیاز دارید.

مزایا: متن واضح و استایل‌دهی آسان با HTML/CSS؛ عالی برای برچسب‌ها و رابط کاربری تعاملی.

معایب: عناصر DOM، WebGL نیستند، پس با صدها برچسب پرفورمنس افت می‌کند. برای کاربرد معمول معماری (ده‌ها تا چند صد) خیلی کاربردی است.

۵) CSS3DRenderer (DOM واقعی در تبدیل‌های سه‌بعدی)

CSS3DRenderer عناصر DOM را در تبدیل‌های سه‌بعدی قرار می‌دهد، پس می‌توانید HTML را با چرخش و مقیاس در فضای سه‌بعدی واقعی بگذارید. برای پنل‌های تعاملی، صفحات وب جاسازی‌شده و عناصر رابط کاربری که باید در تبدیل‌های سه‌بعدی شرکت کنند — مثل بیلبوردی چرخان با محتوای HTML — مفید است. برای تجربه‌های خاص قدرتمند است اما سنگین‌تر و پیچیده‌تر؛ تعداد DOM و تبدیل‌های CSS محدودیت‌های اصلی‌اند.

۶) رویکرد ترکیبی (پیشنهادی برای ابزارهای اندازه‌گیری معماری)

یک الگوی کاربردی و مطمئن:

  • از خط‌ها و نقاط سه‌بعدی (THREE.Line و کره‌های THREE.Mesh واقعی) برای اندازه‌گیری‌های ماندگار استفاده کنید — این دقت، انتخاب، Snapping و عمق درست را تضمین می‌کند.
  • از CSS2DRenderer برای برچسب‌ها (اعداد، واحدها، HTML تعاملی) استفاده کنید تا فونت واضح و استایل CSS داشته باشید.
  • از لایه Canvas 2D فقط برای پیش‌نمایش‌های موقت استفاده کنید، مثل کشیدن یک اندازه‌گیری جدید — ارزان و فوری.
  • در صورت نیاز از Sprite برای تعداد زیادی آیکون کوچک استفاده کنید، به شرط اینکه با دقت Batch یا Instance کنید.

این ترکیب بهترین دقت، انعطاف رابط کاربری و پرفورمنس را با هم می‌دهد.