You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Three.js片元着色器中NDC坐标转世界坐标及鼠标平面画圆实现

如何将NDC坐标转换为世界坐标并绘制光标圆形

首先,咱们得明确:NDC(标准化设备坐标)是渲染管线最后一步的坐标,要转成世界坐标,本质上是逆着渲染管线的变换流程走——也就是把NDC坐标通过逆视图投影矩阵(或分开的逆投影+逆视图矩阵)转换回去。下面分两种高效的方案来实现:

方案1:CPU端直接转换(推荐,更高效)

既然你已经拿到了鼠标的NDC坐标,完全可以在CPU端直接把它转换成世界坐标,再传到片元着色器里,这样不用在每个片元里做矩阵运算,性能更好。

代码示例(以Three.js为例,其他引擎逻辑类似)

// 假设你有camera实例(PerspectiveCamera/OrthographicCamera)
const ndcPoint = new THREE.Vector3(
  this._currentPoint.x, 
  this._currentPoint.y, 
  0.5 // 这里的z值对应屏幕深度:0.0是近平面,1.0是远平面,0.5是中间
);
// 用相机的unproject方法直接转世界坐标
const worldCursorPos = ndcPoint.unproject(camera);

// 把世界坐标传到片元着色器的uniform里
this._cursorMaterial.uniforms.uBrushWorldPos.value = worldCursorPos;

然后在着色器里直接用这个世界坐标计算距离:

顶点着色器(需要传递片元的世界坐标)

uniform mat4 modelMatrix;
varying vec3 vWorldPos;

void main() {
  vec4 worldPos = modelMatrix * vec4(position, 1.0);
  vWorldPos = worldPos.xyz;
  gl_Position = projectionMatrix * viewMatrix * worldPos;
}

片元着色器

uniform vec3 uBrushWorldPos; // CPU传过来的光标世界坐标
varying vec3 vWorldPos; // 片元自身的世界坐标

void main() {
  // 计算片元到光标世界位置的距离
  float distanceToCursor = length(vWorldPos - uBrushWorldPos);
  // 世界空间下的圆形半径(根据你的需求调整)
  float circleRadius = 0.2;

  if (distanceToCursor < circleRadius) {
    gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); // 红色光标圆
  } else {
    // 原渲染逻辑
    gl_FragColor = ...;
  }
}

方案2:着色器内通过逆矩阵转换

如果需要在着色器里动态处理坐标转换(比如实时调整相机参数),可以把逆视图投影矩阵传到着色器,在片元里完成转换:

CPU端准备矩阵

// 计算视图投影矩阵的逆矩阵
const invViewProj = new THREE.Matrix4()
  .multiplyMatrices(camera.projectionMatrixInverse, camera.matrixWorld);

// 传递uniform:NDC光标坐标 + 逆视图投影矩阵
this._cursorMaterial.uniforms.uBrushNdcPos.value = this._currentPoint;
this._cursorMaterial.uniforms.uInvViewProj.value = invViewProj;

片元着色器内转换

uniform vec2 uBrushNdcPos;
uniform mat4 uInvViewProj;
varying vec3 vWorldPos;

void main() {
  // 把2D NDC转成齐次裁剪坐标(z设为0.5对应屏幕中间深度,w=1.0)
  vec4 clipPos = vec4(uBrushNdcPos.x, uBrushNdcPos.y, 0.5, 1.0);
  // 乘以逆矩阵得到世界坐标,再做齐次除法
  vec4 worldCursorPos = uInvViewProj * clipPos;
  worldCursorPos.xyz /= worldCursorPos.w;

  // 后续绘制圆形的逻辑和方案1一致
  float distanceToCursor = length(vWorldPos - worldCursorPos.xyz);
  float circleRadius = 0.2;

  if (distanceToCursor < circleRadius) {
    gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
  } else {
    gl_FragColor = ...;
  }
}

关键注意点

  • 深度值的选择:如果是透视相机,NDC的z值决定了光标在世界空间的深度,你可以根据需求调整(比如设为0.0对应近平面,1.0对应远平面)。
  • 矩阵顺序:逆视图投影矩阵的计算是投影矩阵逆 × 视图矩阵逆(因为渲染时是投影 × 视图 × 模型,逆变换要反过来)。
  • 模型矩阵:如果你的物体有模型变换(缩放、平移、旋转),顶点着色器里一定要用modelMatrix计算世界坐标,不然距离计算会出错。

内容的提问来源于stack exchange,提问作者Nicolas

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.25 08:06:55