跳到主要内容

NDC裁剪与Y轴

Vertex shader 必须写出 clip space。GPU 做透视除法后落到 NDC:x,y,z ∈ [-1, 1](WebGL)。在这个盒子外面的图元被裁掉。CSS 像素、Canvas 2D 用户空间、WebGL NDC 不是同一套,命中和 overlay 全靠你换算。

1. 三套空间

空间原点用途
CSS 盒左上,Y 向下getBoundingClientRect、指针
WebGL NDC / clip中心,Y 向上VS 输出
drawing buffer左下,Y 向上viewportreadPixels
export function cssToNdc(
cssX: number,
cssY: number,
cssWidth: number,
cssHeight: number,
): { x: number; y: number } {
return {
x: (cssX / cssWidth) * 2 - 1,
y: 1 - (cssY / cssHeight) * 2,
};
}

readPixels(0, 0, 1, 1) 读的是 左下角 像素,不是 CSS 的左上。把读回的 Uint8ArrayImageData 用,必须翻转行,或渲染时就按 2D 约定把投影 Y 乘 -1。

纹理从 <img> / 2D canvas 上传:浏览器图像原点在左上。生产几乎总会开:

gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, 1);

和「自己生成的、已经是 GL 原点」的 Uint8Array 不要混用同一套 unpack 状态。

2. WebGPU 的 Y

WebGPU 默认 NDC Y 向上,但 framebuffer / 纹理的 texel (0,0) 在左上,更接近 CPU 图像。从 WebGL 迁过去时,投影矩阵和 blit pass 的翻转经常漏一次。写两套相机矩阵,标清楚目标 API,不要共用一个 mat4 常量当「世界到 clip」。

3. viewport 必须跟 drawingBuffer

export function syncViewport(gl: WebGL2RenderingContext): void {
gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight);
}

CSS 把 canvas 拉到 800×400、backing store 仍是 300×150 时,只用 clientWidth 设 viewport,三角形会按错误纵横比裁。resize 流程见 DPR 与 drawingBuffer

4. 失败形态

症状原因
指针点在上方,高亮在下方NDC Y 没翻,直接用了 CSS Y
截图上下颠倒readPixels 当 PNG 存却没翻行
贴图上下颠倒忘了 UNPACK_FLIP_Y_WEBGL,或翻了两次
画面被裁一半viewport 用了 CSS 像素而不是 drawingBufferWidth

权威资料

核对日期:2026-08-26