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;
现需解决两个核心问题:
- 在JavaScript端获取点击点对应的单位球面3D坐标(用于转换为赤经/赤纬展示给用户);
- 在片元着色器中获取当前绘制像素对应的单位球面3D坐标(用于绘制真实感天空,计算该像素与太阳的角距离、地平高度等)。
解决方案
问题1:JavaScript端点击点转单位球面坐标
核心逻辑是将屏幕点击坐标转换为标准化设备坐标(NDC),通过逆投影视图矩阵计算射线方向,最后归一化得到单位球面坐标(因球面半径为1,射线与球面交点即为归一化后的方向向量)。
步骤如下:
- 屏幕坐标转NDC:
const ndcX = (clientX / canvas.width) * 2 - 1; const ndcY = 1 - (clientY / canvas.height) * 2; // WebGL Y轴与屏幕Y轴方向相反 - 计算逆投影视图矩阵:
const viewProjectionMatrix = mat4.create(); mat4.multiply(viewProjectionMatrix, projectionMatrix, groundViewMatrix); const invViewProjectionMatrix = mat4.create(); mat4.invert(invViewProjectionMatrix, viewProjectionMatrix); - 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
相关产品推荐
相关产品推荐

