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

WebGL2中如何获取单位球面上点击点与片元着色器内的3D坐标

单位球面星图的反投影问题

我制作了一个由恒星、精灵等天体组成的单位球面,所有天体的3D坐标都位于该球面表面。相机处于球心位置,采用透视投影渲染最终星图。

视角效果

  • 球心视角(用户看到的应用界面):
    球心视角的星图界面
  • 侧面视角(便于理解天体球面结构):
    侧面视角的天体球面结构

我查阅了大量关于反投影(unprojection)和射线投射(ray casting)的资料,但这些方法均不符合我的需求:

  • 依赖Three.js这类封装库(我仅使用gl-matrix,基于原生WebGL2开发);
  • 采用基于绘制ID的WebGL拾取技术(星图空白区域过多,该方法无法生效);
  • 认为此类转换必须依赖深度信息(但我的场景是单位球面,向量长度恒为1,深度固定);
  • 仅针对OpenGL及C类语言展开讨论,未覆盖WebGL2与JavaScript场景。

由于场景无模型、相机仅支持旋转操作,矩阵代码非常简洁:

const projectionMatrix = mat4.create()

mat4.perspective(projectionMatrix,
  degreesToRad(fov),
  viewport.x / viewport.y,
  0,
  100)

const panningMatrix = mat4.create()
// 此处为用户拖动星图时应用旋转的代码
mat4.invert(panningMatrix, panningMatrix)

const groundViewMatrix = mat4.create()
mat4.multiply(groundViewMatrix, panningMatrix, groundViewMatrix)

// some-shader.vert; 此处u_modelViewMatrix即为groundViewMatrix
gl_Position = u_projectionMatrix * u_modelViewMatrix * a_position;

现需解决两个核心问题:

  1. 在JavaScript端获取点击点对应的单位球面3D坐标(用于转换为赤经/赤纬展示给用户);
  2. 在片元着色器中获取当前绘制像素对应的单位球面3D坐标(用于绘制真实感天空,计算该像素与太阳的角距离、地平高度等)。

解决方案

问题1:JavaScript端点击点转单位球面坐标

核心逻辑是将屏幕点击坐标转换为标准化设备坐标(NDC),通过逆投影视图矩阵计算射线方向,最后归一化得到单位球面坐标(因球面半径为1,射线与球面交点即为归一化后的方向向量)。

步骤如下:

  1. 屏幕坐标转NDC:
    const ndcX = (clientX / canvas.width) * 2 - 1;
    const ndcY = 1 - (clientY / canvas.height) * 2; // WebGL Y轴与屏幕Y轴方向相反
    
  2. 计算逆投影视图矩阵:
    const viewProjectionMatrix = mat4.create();
    mat4.multiply(viewProjectionMatrix, projectionMatrix, groundViewMatrix);
    const invViewProjectionMatrix = mat4.create();
    mat4.invert(invViewProjectionMatrix, viewProjectionMatrix);
    
  3. NDC点转世界空间并归一化:
    const worldPos = vec4.create();
    vec4.transformMat4(worldPos, [ndcX, ndcY, 1.0, 1.0], invViewProjectionMatrix);
    vec4.scale(worldPos, worldPos, 1.0 / worldPos[3]); // 透视除法
    
    const sphereCoord = vec3.create();
    vec3.normalize(sphereCoord, [worldPos[0], worldPos[1], worldPos[2]]);
    
    sphereCoord即为点击点对应的单位球面3D坐标,可直接用于转换为赤经/赤纬。

问题2:片元着色器中获取像素对应球面坐标

推荐两种实现方式:

方式一:顶点着色器传递方向向量(高效)

利用视图矩阵仅做旋转、不改变向量长度的特性,在顶点着色器中直接传递旋转后的单位方向向量,片元着色器插值后即可使用:

// 顶点着色器
attribute vec3 a_position; // 原始单位球面坐标
uniform mat4 u_viewMatrix; // 即groundViewMatrix
varying vec3 v_worldDir;

void main() {
  v_worldDir = (u_viewMatrix * vec4(a_position, 1.0)).xyz;
  gl_Position = u_projectionMatrix * u_viewMatrix * vec4(a_position, 1.0);
}
// 片元着色器
varying vec3 v_worldDir;
uniform vec3 u_sunPos; // 太阳的单位球面坐标

void main() {
  // 计算与太阳的角距离
  float angle = acos(dot(v_worldDir, u_sunPos));
  // 计算地平高度等其他参数...
  gl_FragColor = ...;
}
方式二:全屏四边形计算(无顶点数据时使用)

若为全屏天空绘制,可通过全屏四边形的UV坐标计算NDC,再经逆矩阵转换为单位球面坐标:

// 顶点着色器(全屏四边形)
attribute vec2 a_uv;
varying vec2 v_uv;

void main() {
  v_uv = a_uv;
  gl_Position = vec4(a_uv * 2.0 - 1.0, 0.0, 1.0);
}
// 片元着色器
varying vec2 v_uv;
uniform mat4 u_invViewProjectionMatrix;

void main() {
  vec3 ndc = vec3(v_uv * 2.0 - 1.0, 1.0);
  vec4 worldPos = u_invViewProjectionMatrix * vec4(ndc, 1.0);
  worldPos /= worldPos.w;
  
  vec3 sphereDir = normalize(worldPos.xyz);
  // 后续天空渲染计算...
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.23 10:10:57