放大缩小地图时,NPC 名字标签与 NPC 人物位置偏移错位 #135

Closed
opened 2026-09-21 02:00:12 +00:00 by troytt · 1 comment
Owner

问题描述

参考截图:https://screenshot-v2.corp.google.com/12hbrk7d7cgj0
在罗格营地等城镇放大/缩小地图(或在高分屏 devicePixelRatio > 1 下)时,NPC 名字 DOM 标签(如 Deckard Cain、Kashya、Warriv、Rogue)与实际渲染的 NPC 人物精灵位置对应不上,出现明显向右下偏移。

根因分析

  1. 画布物理像素(Backing-Store Pixels)与 CSS 像素混用:
    ViewportCamera.syncCanvasSize(canvas) 将 canvas.width 和 canvas.height 乘上了缩放因子 scale = Math.min(dpr, Math.sqrt(maxPixels / (cssWidth * cssHeight)))。
    而 WebGL 顶点着色器使用 u_viewport = (canvas.width, canvas.height) 将世界坐标投影到 NDC [-1, 1],最终映射到屏幕 CSS 坐标 (cssWidth, cssHeight)。
    但 src/scene/act-scene.ts 第 2484–2500 行计算 NPC DOM 标签位置时写成了:
    const screenX = (npc.x - camX) * camera.zoom + canvas.width / 2
    const screenY = (npc.y - camY) * camera.zoom + canvas.height / 2
    
    直接将物理像素坐标赋给了 CSS left / top,未乘以 cssWidth / canvas.width 与 cssHeight / canvas.height,导致在 dpr !== 1 或缩放时整体偏离屏幕中心与人物。
  2. NPC 头顶锚点未对齐精灵包围盒(frame.offsetX / offsetY):
    对象层绘制 NPC 时使用了 obj.x + obj.frame.offsetX 与 obj.y + obj.frame.offsetY,而标签使用了固定 npc.y - 60 * camera.zoom。

解决方案

将 NPC 名字投影严格对齐 WebGL 视口矩阵(乘上 cssWidth / canvas.width 与 cssHeight / canvas.height)并使用 NPC 精灵真实包围盒顶部中心 (npc.x + frame.offsetX + frame.width / 2, npc.y + frame.offsetY - 6)(或直接在等比对齐的 Canvas 上渲染),保证任意缩放倍率与任意 DPR 下 100% 紧贴 NPC 头顶。

### 问题描述 参考截图:https://screenshot-v2.corp.google.com/12hbrk7d7cgj0 在罗格营地等城镇放大/缩小地图(或在高分屏 `devicePixelRatio > 1` 下)时,NPC 名字 DOM 标签(如 Deckard Cain、Kashya、Warriv、Rogue)与实际渲染的 NPC 人物精灵位置对应不上,出现明显向右下偏移。 ### 根因分析 1. **画布物理像素(Backing-Store Pixels)与 CSS 像素混用**: `ViewportCamera.syncCanvasSize(canvas)` 将 `canvas.width` 和 `canvas.height` 乘上了缩放因子 `scale = Math.min(dpr, Math.sqrt(maxPixels / (cssWidth * cssHeight)))`。 而 WebGL 顶点着色器使用 `u_viewport = (canvas.width, canvas.height)` 将世界坐标投影到 NDC `[-1, 1]`,最终映射到屏幕 CSS 坐标 `(cssWidth, cssHeight)`。 但 `src/scene/act-scene.ts` 第 2484–2500 行计算 NPC DOM 标签位置时写成了: ```ts const screenX = (npc.x - camX) * camera.zoom + canvas.width / 2 const screenY = (npc.y - camY) * camera.zoom + canvas.height / 2 ``` 直接将物理像素坐标赋给了 CSS `left` / `top`,未乘以 `cssWidth / canvas.width` 与 `cssHeight / canvas.height`,导致在 `dpr !== 1` 或缩放时整体偏离屏幕中心与人物。 2. **NPC 头顶锚点未对齐精灵包围盒(`frame.offsetX / offsetY`)**: 对象层绘制 NPC 时使用了 `obj.x + obj.frame.offsetX` 与 `obj.y + obj.frame.offsetY`,而标签使用了固定 `npc.y - 60 * camera.zoom`。 ### 解决方案 将 NPC 名字投影严格对齐 WebGL 视口矩阵(乘上 `cssWidth / canvas.width` 与 `cssHeight / canvas.height`)并使用 NPC 精灵真实包围盒顶部中心 `(npc.x + frame.offsetX + frame.width / 2, npc.y + frame.offsetY - 6)`(或直接在等比对齐的 Canvas 上渲染),保证任意缩放倍率与任意 DPR 下 100% 紧贴 NPC 头顶。
Author
Owner

验证通过并已随提交 14443c6 修复关单

  1. 改动方案:

    • 在 src/scene/act-scene.ts 中推导严格抵消 DPR 乘数的 WebGL NDC 投影变换,并在 computeHudLayout 中精确换算至 CSS DOM Overlay 像素坐标系。
    • NPC 铭牌锚点统一绑定至 NPC 四边形精灵顶部中央(Quad Top-Center)。
  2. 验证结果:

    • 在相机缩放倍率从 0.4x 到 8.0x 以及 DPR 1.0~2.0 的动态变化下,NPC 名称铭牌始终像素级贴合在 NPC 头部正上方,无任何漂移错位。
### 验证通过并已随提交 14443c6 修复关单 1. **改动方案**: - 在 `src/scene/act-scene.ts` 中推导严格抵消 DPR 乘数的 WebGL NDC 投影变换,并在 `computeHudLayout` 中精确换算至 CSS DOM Overlay 像素坐标系。 - NPC 铭牌锚点统一绑定至 NPC 四边形精灵顶部中央(Quad Top-Center)。 2. **验证结果**: - 在相机缩放倍率从 0.4x 到 8.0x 以及 DPR 1.0~2.0 的动态变化下,NPC 名称铭牌始终像素级贴合在 NPC 头部正上方,无任何漂移错位。
Sign in to join this conversation.
No Label
No Milestone
No project
No Assignees
1 Participants
Notifications
Due Date
The due date is invalid or out of range. Please use the format 'yyyy-mm-dd'.

No due date set.

Dependencies

No dependencies set.

Reference: troytt/diablo2-web#135
No description provided.