简述
物体的坐标转换过程大致为:局部坐标 -> 世界坐标 -> 观察空间坐标 -> 裁剪空间坐标 -> 屏幕空间坐标
我们将 观察空间坐标系 和 裁剪空间坐标系 之间的转换统一处理,最终得到 标准设备坐标系
因此坐标转换过程就变成了:局部坐标 -> 世界坐标 -> 标准设备坐标 -> 屏幕空间坐标
屏幕坐标转世界坐标
ThreeJS 是使用了 canvas 画布绘制图形的,因此屏幕坐标系就是 canvas 中的坐标系,也就是左上角是坐标原点:
在 ThreeJS 中,一个物体可看作一个 Mesh,Mesh 的坐标是用一个 Vector3 来表示的,Vector3 中包含了 x、y、z 坐标。
空间坐标系是三维的,其原点默认在屏幕中心,且 x y z 的范围是 [-1,1],因此其 x、y 轴在屏幕坐标系中的表示就是
通过Vector3对象的方法project,方法的参数是相机对象,语句worldVector.project(camera);返回的结果是世界坐标worldVector在camera相机对象矩阵变化下对应的标准设备坐标, 标准设备坐标xyz的范围是[-1,1]。
ThreeJS 中,画布一般是全屏的,因此画布的宽高 w,h 就是:window.innerWidth 和 window.innerHeight,所以 Three 的空间坐标系中点 (cx, cy)在屏幕坐标系中就是:(w / 2,h / 2)。
假设 canvas 中有一点 (x,y),这个点在空间坐标系中为 (x1,y1),那么这个转换公式是:
x1=(x/w)∗2−1
y1=−(y/h)∗2+1
公式推导过程如下:
世界坐标转屏幕坐标
屏幕坐标转空间坐标需要经过两个步骤:屏幕坐标 -> 标准设备坐标 -> 世界坐标。
通过 Vector3对象的方法 project(camera),返回的结果是世界坐标 worldVector在 camera相机对象矩阵变化下对应的标准设备坐标, 标准设备坐标 xyz 的范围是[-1,1]。
同样的,假设画布宽为 w ,高为 h,屏幕坐标系中的一点为 (x, y),标准设备坐标系中对应的点为 (x1, y1)
从标准设备坐标系转换到屏幕坐标系与我们前面计算出的公式相反:
首先计算出屏幕坐标系中心:
const centerX = window.innerWidth / 2;
const centerY = window.innerHeight / 2;
计算出的 centerX 和 centerY 同时也表示了坐标轴的一半大小。
然后,将设备坐标系使用 project 方法转换到标准设备坐标系,再转换到屏幕坐标系中:
const standardVec = worldVector.project(camera);
const screenX = Math.round(centerX * standardVec.x + centerX);
const screenY = Math.round(-centerY * standardVec.y + centerY);
第三方 CSS2DRenderer
/**
* 创建div元素(作为立方体标签)
*/
var div = document.createElement('div');
div.innerHTML = '立方体';
div.style.padding = '5px 10px';
div.style.color = '#fff';
div.style.fontSize = '16px';
div.style.position = 'absolute';
div.style.backgroundColor = 'rgba(25,25,25,0.5)';
div.style.borderRadius = '5px'
// document.body.appendChild(div);
// 获得HTML元素创建的UI界面
var tag = document.getElementById('tag');
//div元素包装为CSS2模型对象CSS2DObject,并插入场景中
var label = new CSS2DObject(div);
label.position.copy(boxMesh.position);
// label.position.y += 30
scene.add(label); //CSS2模型标签插入到场景中
// 创建一个CSS2渲染器CSS2DRenderer
var labelRenderer = new CSS2DRenderer();
labelRenderer.setSize(window.innerWidth, window.innerHeight);
labelRenderer.domElement.style.position = 'absolute';
// 避免renderer.domElement影响HTMl标签定位,设置top为0px
labelRenderer.domElement.style.top = '0px';
labelRenderer.domElement.style.left = '0px';
//设置.pointerEvents=none,以免模型标签HTML元素遮挡鼠标选择场景模型
labelRenderer.domElement.style.pointerEvents = 'none';
document.body.appendChild(labelRenderer.domElement);
第三方 CSS3DRenderer
/**
* 创建div元素(作为立方体标签)
*/
var div = document.createElement('div');
div.innerHTML = '立方体';
div.style.padding = '5px 10px';
div.style.color = '#fff';
div.style.fontSize = '16px';
div.style.position = 'absolute';
div.style.backgroundColor = 'rgba(25,25,25,0.5)';
div.style.borderRadius = '5px'
// document.body.appendChild(div);
// 获得HTML元素创建的UI界面
// var tag = document.getElementById('tag');
//div元素包装为CSS3模型对象CSS3DObject,并插入场景中
var label = new CSS3DObject(div);
div.style.pointerEvents = 'none';//避免HTML标签遮挡三维场景的鼠标事件
label.position.copy(boxMesh.position);
//缩放CSS3DObject模型对象
label.scale.set(0.5,0.5,0.5)
label.position.y += 20
scene.add(label); //CSS3模型标签插入到场景中
// 创建一个CSS3渲染器CSS3DRenderer
var labelRenderer = new CSS3DRenderer();
labelRenderer.setSize(window.innerWidth, window.innerHeight);
labelRenderer.domElement.style.position = 'absolute';
// 避免renderer.domElement影响HTMl标签定位,设置top为0px
labelRenderer.domElement.style.top = '0px';
labelRenderer.domElement.style.left = '0px';
//设置.pointerEvents=none,以免模型标签HTML元素遮挡鼠标选择场景模型
labelRenderer.domElement.style.pointerEvents = 'none';
document.body.appendChild(labelRenderer.domElement);