渲染管线-计算得到gl_Position(顶点着色器)之后续GPU流程

核心流程

顶点着色器写出 gl_Position 之后,这个值还不是最终屏幕坐标,它此时处在裁剪空间-Clip Space

  • GPU 后续会继续走完整的图形管线,大致顺序是:
  • 图元装配
  • 裁剪
  • 透视除法
  • 视口变换
  • 光栅化
  • 片元着色
  • 各种测试与混合
  • 写入帧缓冲

如果把它串成一句话,就是:

text 复制代码
gl_Position(裁剪空间)
-> 裁剪
-> NDC
-> 屏幕坐标
-> 生成片元
-> 计算颜色
-> 深度/模板/混合
-> 显示到屏幕

1. 图元装配

  • 顶点着色器是"逐顶点"执行的,GPU 会把多个顶点按照你的绘制方式组装成图元。
  • 例如:
    • gl.drawArrays(gl.TRIANGLES, ...) 会每 3 个顶点组装成一个三角形
    • gl.LINES 会组装成线段
    • gl.POINTS 会组装成点
  • 这一步里,gl_Position 只是每个顶点的位置结果,GPU 还要先知道这些顶点组成了什么图形。

2. 裁剪,Clipping

  • GPU 会拿组装好的图元,和裁剪空间可见范围做比较。
  • 在标准裁剪空间里,顶点需要满足类似这样的范围关系:
text 复制代码
-w <= x <= w
-w <= y <= w
-w <= z <= w
  • 如果一个三角形完全在可见范围外,就直接丢掉。
  • 如果只有一部分超出范围,GPU 会把它裁掉,只保留可见部分。
  • 所以 gl_Positionw 很重要,因为裁剪不是简单拿 x/y/z 和固定值比,而是和 w 相关。

3. 透视除法,Perspective Divide

  • 裁剪之后,GPU 会把裁剪空间坐标变成 NDC,标准化设备坐标。
  • 做法就是:
text 复制代码
x_ndc = x / w
y_ndc = y / w
z_ndc = z / w
  • 这一步非常关键,透视投影里的"近大远小"就是在这里真正体现出来的。
  • 做完之后,坐标会落到标准范围内:
text 复制代码
x: [-1, 1]
y: [-1, 1]
z: [-1, 1]
  • 这里得到的已经不是 gl_Position 原始值,而是透视除法后的标准坐标。

4. 视口变换,Viewport Transform

  • NDC 仍然不是屏幕像素坐标,GPU 接下来会把它映射到视口区域。
  • 比如:
    • x = -1 映射到屏幕左边
    • x = 1 映射到屏幕右边
    • y = -1 映射到屏幕下边
    • y = 1 映射到屏幕上边
  • 这个过程受 gl.viewport(x, y, width, height) 影响。
  • 也就是说,NDC 会被换算成真正的窗口像素区域里的位置。

5. 面剔除,可选

  • 在光栅化前,GPU 还可能做背面剔除
  • 如果启用了:
js 复制代码
gl.enable(gl.CULL_FACE);
  • 那么 GPU 会根据三角形顶点顺序判断它是正面还是背面。
  • 背向摄像机的面可以直接丢弃,不再继续处理。
  • 这样能减少后续片元计算量。

6. 光栅化,Rasterization

  • 到这一步,GPU 已经有了屏幕上的三角形轮廓。
  • 光栅化会把几何图元转换成大量片元,Fragment
  • 你可以把它理解成:
    • 三角形覆盖了哪些像素区域
    • 就为这些像素位置生成对应的片元
  • 注意:
    • 片元不完全等于最终像素
    • 它只是"候选像素数据",后面还要经过测试才可能真正写进屏幕

7. 插值,Interpolation

  • 光栅化生成片元时,GPU 会把顶点着色器输出的其他变量自动插值到每个片元上。
  • 比如你在顶点着色器里输出:
glsl 复制代码
varying vec2 vUv;
varying vec3 vColor;
varying vec3 vNormal;
  • 那么 GPU 会根据三角形内部位置,为每个片元自动算出它自己的:
    • vUv
    • vColor
    • vNormal
  • 这一步非常重要,因为片元着色器通常就是靠这些插值后的数据来计算纹理、光照和颜色的。

8. 片元着色器执行

  • 每个片元都会执行一次片元着色器。
  • 在这里你通常会做:
    • 采样纹理
    • 计算光照
    • 组合颜色
    • 输出最终颜色

例如:

glsl 复制代码
gl_FragColor = vec4(color, 1.0);

或者在 WebGL2 中:

glsl 复制代码
out vec4 outColor;
  • 这一步决定"这个片元想要显示成什么颜色"。

9. 每片元测试,Per-Fragment Tests

  • 片元着色器算出颜色后,还不一定能真正画到屏幕上。

  • GPU 还会做一系列测试,常见包括:

  • 深度测试

    • 判断这个片元是不是被前面的物体挡住了
    • 只有更靠近摄像机的片元通常才会保留
  • 模板测试

    • 根据模板缓冲决定哪些区域允许绘制
  • 裁剪/裁切规则

    • 某些片元可能仍会因为状态设置被丢弃
  • Alpha discard

    • 如果你在片元着色器里主动 discard,这个片元也会被直接丢掉

如果深度测试没过,这个片元即使颜色算出来了,也不会写入最终图像。

10. 混合,Blending

  • 如果片元通过测试,且启用了混合:
js 复制代码
gl.enable(gl.BLEND);
  • GPU 会把"新片元颜色"和"帧缓冲里已有颜色"混合。
  • 这就是透明效果的基础。
  • 常见混合公式例如:
js 复制代码
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
  • 这时最终显示的颜色,可能不是片元着色器直接输出的颜色,而是混合后的结果。

11. 写入帧缓冲

  • 最终通过所有测试的片元,会把结果写入帧缓冲Framebuffer
  • 帧缓冲里通常包含:
    • 颜色缓冲
    • 深度缓冲
    • 模板缓冲
  • 当一帧全部完成后,浏览器再把颜色缓冲内容显示到屏幕上。

重点理解 gl_Position 的地位

  • gl_Position 只负责告诉 GPU:这个顶点在裁剪空间里在哪里
  • 它并不直接等于屏幕坐标。
  • 它后面还要经历:
    • 裁剪
    • 除以 w
    • 映射到视口
    • 光栅化成片元
    • 片元着色
    • 深度/混合处理
  • 所以 gl_Position 更像是"几何阶段的最终输出",不是整个渲染流程的最终输出。

最容易混淆的几点

  • gl_Position 不是屏幕像素坐标,它是裁剪空间坐标。
  • w 不是可有可无,它直接影响裁剪和透视除法。
  • 顶点着色器结束后,不是直接上屏,而是先从"顶点"变成"图元",再变成"片元"。
  • 片元着色器输出颜色后,也不一定会显示,还要过深度测试和混合。

一句话总结

  • 计算出 gl_Position 之后,GPU 会先把顶点组装成图元,再做裁剪和透视除法,把它们变成屏幕范围内的片元,然后执行片元着色器,最后经过深度测试、混合等步骤,才真正写到屏幕上。

最短记忆版

  • gl_Position 之后的主线就是:
text 复制代码
图元装配 -> 裁剪 -> 透视除法 -> 视口变换 -> 光栅化 -> 片元着色 -> 深度/混合 -> 帧缓冲
相关推荐
竹林8181 小时前
用 The Graph 查询链上数据实战:从手搓 RPC 到 Subgraph,我的 NFT 项目数据加载快了 10 倍
前端·javascript
kyriewen4 小时前
别再每次都 Google 了:我整理了前端日常最常踩的 10 个 Git 坑,附速查表
前端·javascript·git
SmartBoyW5 小时前
深入ECMAScript规范:彻底搞懂JS隐式类型转换与底层ToPrimitive机制
前端·javascript
用户852495071846 小时前
解密 JavaScript 中的 this:谁才是真正的调用者?
javascript·面试
Heo6 小时前
Vite进阶用法详解
前端·javascript·面试
铁皮饭盒7 小时前
Next.js 风格路由内置?Bun FileSystemRouter 凭啥这么香
javascript
花褪残红青杏小8 小时前
Rust图像处理第11节-故障风 RGB 通道偏移:错位错色制造电子故障
rust·webassembly·图形学
小林ixn8 小时前
别再背八股了!从 5 个真实场景彻底搞懂 JavaScript 的 this
javascript
SmalBox8 小时前
【节点】[EyeSurfaceTypeDebug节点]原理解析与实际应用
unity3d·游戏开发·图形学