文章目录
本文将详细介绍我的一个个人项目:如何仅使用浏览器、一个普通的笔记本摄像头和 three.js
库,来实现一个“虚拟窗口”特效。这种技术也被称为头部耦合透视(Head-Coupled Perspective)或动态离轴3D错觉,其效果是让电脑屏幕看起来像一扇通往三维世界的窗户,随着你头部的移动,你所看到的景物透视也会实时发生变化。
一、思路总览:化繁为简
这个项目的灵感来源于一些已有的实现,但那些项目通常需要专门的硬件或复杂的配置流程。我的目标是用更易于获取的工具来达到类似的效果。下面是传统方法与我所采用的、基于Web的简化方法的对比。
步骤 | 类似项目做法 | 我的Web端做法 | 关键差异 |
---|---|---|---|
1. 摄像头标定 | 拍摄数十张棋盘格照片,并用 OpenCV 精确计算摄像头内参。 | 初始化一个估算参数,然后通过对比计算深度与物理测量深度的偏差,来手动、快速地调整参数。 | 简洁高效。我的方法避免了繁琐的标定流程,对于此应用场景来说“足够好”。 |
2. 眼动追踪 | 使用 Face Mesh 实时定位虹膜,通过针孔相机模型计算出眼睛的三维位置。 | 与原项目基本一致。MediaPipe 的 Face Mesh 方案成熟且能在浏览器中高效运行。 | 无 |
3. 虚拟世界构建 | 使用原生图形引擎构建3D场景,并将虚拟摄像机放置在眼睛的位置。 | 使用 three.js 框架来构建场景和管理摄像机。 | 高可用性。three.js 使项目能够跨平台运行,并且非常容易在网页上分享。 |
4. 离轴透视投影 | 使用一个离轴(off-axis)投影矩阵来“扭曲”视锥体,确保3D场景始终能完美填充屏幕“窗口”。 | 与原项目基本一致。这是实现整个错觉背后的核心数学原理。 | 无 |
5. 项目演示 | 展示靶心、僵尸或虚拟橱窗等不同场景,让物体有“跳出”屏幕的感觉。 | 复刻经典的“靶心(Targets)”场景来验证和展示效果。 | 简化范围 |
最终目标是统一的:使用浏览器和普通摄像头,生成一个动态的、基于离轴投影的三维窗口错觉。
二、第一步:“足够好”的摄像头内参计算
目标: 获取摄像头的内参,主要是焦距(focal length),这是后续计算眼睛空间深度的关键。
传统方法需要使用棋盘格进行繁琐的标定,虽然精确但非常耗时。我问自己:有没有更简单有效的方法?
我的做法: 我利用了视场角(FOV)和焦距之间的数学关系。
焦距 = (图像宽度像素值 / 2) / tan(FOV / 2)
我没有直接去计算焦距,而是决定反过来调整FOV,直到整个系统输出正确的结果。具体流程如下:
- 初始化: 为摄像头的FOV设置一个初始猜测值(比如 50 度)。
- 计算与对比: 程序会使用这个FOV值实时计算出用户眼睛的深度(Z坐标)并显示在屏幕上。
- 物理测量: 我用一把尺子实际测量自己与屏幕之间的距离。
- 手动调节: 我通过界面上的控制滑块,不断调整FOV的值,直到屏幕上显示的计算深度与我物理测量的深度基本吻合。
这种交互式的反馈循环使得标定过程非常迅速,并且精度足以满足本项目的需求。
三、第二步:计算眼睛的三维空间坐标
当摄像头“标定”完成后,我们就可以计算用户眼睛相对于摄像头的三维坐标(X, Y, Z)了。
-
2D虹膜检测: 我使用可在浏览器中运行的 MediaPipe Face Mesh 来检测虹膜边缘的关键点。通过这些点,我可以计算出虹膜在视频画面中的像素尺寸。
-
计算深度(Z坐标): 利用针孔相机模型(其原理是相似三角形),我们可以计算出眼睛到摄像头的距离。公式如下:
深度 = (焦距 × 真实虹膜尺寸) / 虹膜的像素尺寸
人眼虹膜的真实直径相当恒定,平均约为 11.7毫米。这为我们的公式提供了一个可靠的“真实尺寸”参考。
-
计算X和Y坐标: 在深度已知后,我们同样利用相似三角形原理,可以计算出水平(X)和垂直(Y)方向的坐标:
X坐标 = (虹膜中心点的像素X偏移 × 深度) / 焦距
Y坐标 = (虹膜中心点的像素Y偏移 × 深度) / 焦距
这里的“像素X/Y偏移”指的是虹膜中心相对于摄像头画面中心的2D像素坐标。
四、第三步:使用 three.js
构建虚拟场景
目标: 创建一个3D场景,其中虚拟摄像机的位置与用户真实眼睛的位置完美同步。
-
场景原点与屏幕: 我将摄像头的位置定义为3D世界的原点(0, 0, 0)。然后,我将物理屏幕建模为这个空间中的一个矩形平面。我测量了我的笔记本屏幕尺寸(长34.5cm,宽19.4cm),并在场景中按这个尺寸和实际距离创建了一个虚拟屏幕。
-
虚拟摄像机与眼睛位置同步: 我将上一步计算出的眼睛三维坐标,直接赋值给
three.js
场景中的虚拟摄像机的位置。这样,当我真实世界的眼睛移动时,虚拟世界中的摄像机也随之移动。 -
复刻演示场景: 为了展示效果,我复刻了经典的“Targets”场景。这包括:
- 定义空间感的四周网格。
- “Targets”文字标签。
- 多个漂浮在空中的、色彩鲜艳的同心圆环体。
- 一个简单的“击中”目标的动画效果。
五、第四步:魔法核心 - 离轴透视投影
目标: 确保无论眼睛(虚拟摄像机)移动到哪里,3D场景的视野都能精确地、不偏不倚地充满整个物理屏幕。
标准的透视投影假设观察者正对着视锥体的中心。如果我们的虚拟摄像机偏离了中心,部分场景就会被裁切掉,错觉就会被打破。
解决方案是 离轴透视投影(Off-Axis Perspective Projection)。它能创建一个不对称(或称“斜视”)的视锥体,这个视锥体被精确地扭曲,使其边界从摄像机当前的位置看去,正好与屏幕的四个边缘对齐。
实现过程如下:
- 计算非对称视锥体边界: 根据眼睛的三维位置和虚拟屏幕四个角点的相对位置,利用相似三角形原理,计算出近裁剪平面(near clipping plane)上所需的左、右、上、下边界偏移量。
- 构建并应用矩阵: 使用这些计算出的边界值来构建一个新的4x4离轴投影矩阵。在
three.js
中,可以通过Camera.projectionMatrix.makePerspective(left, right, top, bottom, near, far)
方法来实现。最后,将这个新矩阵应用到摄像机的projectionMatrix
属性上。
六、项目演示与完整代码
Github完整代码(含完整代码、PPT讲解):https://github.com/aug618/VR-on-2D(觉得有意思或者有帮助的话,麻烦请你点个star哦~)
B站视频演示:https://www.bilibili.com/video/BV1m1TMzuEjL/
七、补充与思考
这个项目是一次在计算机视觉、3D图形学和创造性解决问题方面的绝佳实践。通过用更简单、Web友好的方案替代复杂、专业的步骤,我们可以让强大的视觉错觉技术变得更加触手可及。