@realsee/five
Version:
Realsee Renderer SDK
167 lines (125 loc) • 6.72 kB
Markdown
# Screen & Space Projection (屏幕与空间坐标转换)
- **Summary**: 详解屏幕二维坐标与三维空间坐标/射线之间的相互转换关系,以及如何在 DOM 层与 3D 场景进行联动(如显示标签)。
- **Schema**: `five.project2d` 接口定义。
- **Concepts**: World Space, Screen Space, NDC, Raycasting.
- **Examples**: 空间点位转屏幕坐标 (标签功能)、屏幕坐标转空间射线。
## Schema
> **Definition**: [Five](../../five/application/five.d.ts)
```typescript
// defined in Five class
interface Five {
/**
* 计算三维坐标对应到屏幕的二维坐标
* @param vector - 三维空间坐标 (World Space)
* @param testModel - 是否进行模型遮挡检测 (默认 false)。如果为 true,且该点被模型遮挡,则返回 null。
* @returns 二维屏幕坐标 (相对于 Canvas 左上角) 或 null (如果点在视野外或被遮挡)
*/
project2d(vector: THREE.Vector3, testModel?: boolean): THREE.Vector2 | null;
}
```
## Concepts
### 1. 坐标系转换流程
在 Five (Three.js) 中,坐标转换通常遵循以下流程:
* **World Space (世界坐标)**: 三维场景中的绝对坐标 (x, y, z),单位通常为米。
* **Camera Space (相机坐标)**: 相对于相机的坐标。
* **NDC (Normalized Device Coordinates)**: 归一化设备坐标,x, y, z 范围均为 [-1, 1]。
* **Screen Space (屏幕坐标)**: 对应 Canvas 画布的像素坐标。原点 (0, 0) 在左上角,x 向右增加,y 向下增加。
### 2. 转换关系
| 方向 | 方法 | 核心逻辑 | 用途 |
| :--- | :--- | :--- | :--- |
| **3D ➔ 2D** | `five.project2d(vector)` | World ➔ NDC ➔ Screen | 标签 (Tag)、HUD、跟随 3D 物体的 UI |
| **2D ➔ 3D** | `Raycaster` | Screen ➔ NDC ➔ Ray | 点击拾取 (Picking)、地面检测、放置物体 |
### 3. five.project2d
`five.project2d` 是 Five 提供的便捷方法,它封装了以下逻辑:
1. 将世界坐标投影 (Project) 到相机的 NDC 空间。
2. 判断点是否在视锥体 (Frustum) 内(即是否在视野范围内)。
3. (可选) 发射射线检测该点与相机之间是否有模型遮挡。
4. 将 NDC 坐标转换为相对于 Canvas 左上角的像素坐标 (px)。
## Examples
### 1. 空间标签 (3D Point to DOM Label)
最典型的应用场景是在 3D 场景中的某个物体上方显示一个 HTML 标签。
```typescript
import { Five, Mode } from "@realsee/five";
import * as THREE from "three";
const five = new Five();
// ... 初始化 five ...
// 1. 创建标签 DOM 元素
const label = document.createElement("div");
label.textContent = "Living Room";
label.style.position = "absolute";
label.style.padding = "4px 8px";
label.style.background = "rgba(0, 0, 0, 0.6)";
label.style.color = "white";
label.style.borderRadius = "4px";
label.style.pointerEvents = "none"; // 避免阻挡鼠标事件
label.style.transform = "translate(-50%, -100%)"; // 居中并显示在点上方
label.style.display = "none"; // 默认隐藏
document.body.appendChild(label); // 添加到 container (注意 container 的定位 context)
// 2. 定义目标空间坐标 (例如客厅中心)
const targetPosition = new THREE.Vector3(1.5, 2.0, -3.0);
// 3. 在每一帧渲染时更新标签位置
five.on("render.prepare", () => {
// 计算屏幕坐标
// 参数2: false 表示不检测遮挡 (性能更好);true 表示如果被墙挡住则隐藏
const screenPos = five.project2d(targetPosition, true);
if (screenPos === null) {
// 如果返回 null,说明点在相机背面,或者被遮挡(如果开启了 testModel)
label.style.display = "none";
} else {
label.style.display = "block";
// 更新 DOM 位置
// 注意:screenPos 是相对于 Canvas 的坐标。
// 如果 Canvas 占满全屏,直接使用即可。
// 如果 Canvas 有偏移,需加上 Canvas 的 offset。
label.style.left = `${screenPos.x}px`;
label.style.top = `${screenPos.y}px`;
}
});
```
### 2. 屏幕点击转射线 (Screen to Ray)
将鼠标点击的屏幕坐标转换为 3D 射线,用于检测物体。
> **注意**: 更多射线检测细节请参考 [Raycast](./raycast.md)。
```typescript
import * as THREE from "three";
// 假设 container 是 five 的容器元素
const container = five.getElement()!.parentElement!;
container.addEventListener("click", (event) => {
const raycaster = new THREE.Raycaster();
const mouse = new THREE.Vector2();
// 1. 计算 NDC 坐标 (x, y 范围 [-1, 1])
// event.clientX/Y 是相对于视口的坐标
// getBoundingClientRect 用于获取容器的位置
const rect = container.getBoundingClientRect();
mouse.x = ((event.clientX - rect.left) / rect.width) * 2 - 1;
mouse.y = -((event.clientY - rect.top) / rect.height) * 2 + 1; // 注意 Y 轴翻转
// 2. 从相机发射射线
raycaster.setFromCamera(mouse, five.camera);
// 3. 进行相交检测
const intersects = five.model.intersectRaycaster(raycaster);
if (intersects.length > 0) {
console.log("Hit point:", intersects[0].point);
}
});
```
## Common Pitfalls
1. **坐标系原点**:
* DOM/CSS 坐标系原点在左上角,Y 轴向下。
* NDC/WebGL 坐标系原点在中心,Y 轴向上。
* `project2d` 返回的是 **DOM 风格** 的坐标 (左上角原点),可以直接赋给 CSS `left`/`top`。
* 但在进行 Raycaster 转换时,需要手动将 DOM 坐标转换为 NDC (Y 轴翻转)。
2. **性能开销**:
* `project2d(pos, true)` 开启 `testModel` 会进行射线检测,性能开销较大。如果在 `render.prepare` 中对大量点进行检测,可能会导致掉帧。
* 建议:对于大量标签,默认使用 `false`;或者仅对可视范围内的关键标签进行遮挡检测;或者限制检测频率(如每 10 帧检测一次)。
3. **Canvas 尺寸**:
* `project2d` 内部使用 `five.renderer.getSize()` 和 `five.viewport` 计算。确保 Canvas 尺寸与 DOM 显示尺寸一致 (通常 Five 会自动处理,但如果使用了 CSS 缩放需注意)。
4. **Null 返回值**:
* 务必检查 `project2d` 的返回值是否为 `null`。点在相机背面、超出视锥体裁剪范围、或被遮挡(开启 testModel 时)都会返回 `null`。
## Related
* [Raycast](./raycast.md): 详细的射线检测文档。
* [Five](./five.md): Five 核心类说明。
* [State](./state.md): 了解相机位姿 (Pose)。
* [Coordinate System](./coordinate-system.md): 坐标系定义,理解世界坐标与屏幕坐标的关系。
---
```yaml
tags: [标签, 坐标转换, 信息面板, coordinate, screen, project, label, ui, tag, HUD, 3D转2D, overlay, tooltip]
```