恒美微站 Logo 恒美微站
  • 首页
  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心
  • 联系我们

图形学渲染管线全解:从顶点到像素

  • 首页
  • 资讯中心
  • /
  • 图形学渲染管线全解:从顶点到像素

相关资讯

【办公类-39-04】批量下载微信收到图片(四)-Python模拟点击下载微信收到的图片 2026/8/13 17:58:35
BilibiliDown 保姆级下载教程:3 个真实场景带你从零搬空 B 站视频库 2026/8/13 17:58:35
揭秘张云网站建设背后的硬核逻辑与用户体验重塑之道:为何专业才是最好的流量密码 2026/8/13 17:58:35

最新资讯

ESP芯片烧录工具esptool:3种高效固件管理方法与实战技巧
内核级Root方案深度解析:KernelSU Next技术架构与实战指南
贵州省建设厅网站首页全解析:从政策解读到办事指南,一文读懂贵州住建新动态
Golongpoll客户端使用教程:Go与JavaScript双版本全解析
揭秘中国建设银行招聘网站入口及全流程解析:普通人如何靠诚意逆袭拿到Offer
明日方舟终极工具箱:一键解决干员培养所有难题

今日推荐

VSCode插件精选:从AI补全到代码规范,打造高效开发环境
如何快速完成文件批量重命名:FreeReNamer终极指南
2026年横评:宁波3大学科小升初机构全面对比

本周热门

5分钟告别提取码焦虑:baidupankey如何智能破解百度网盘资源锁
如何快速生成中国车牌图片:Python开源工具完整指南
当 LLM 遇见大文档:主流开源项目如何处理上下文超限

本月精选

如何用DamaiHelper实现演唱会门票的智能自动化抢购:完整技术解决方案指南
第4篇:59 倍性能差距的索引瓶颈定位——一次教科书级的全表扫描调优
终极歌词批量下载神器:5分钟解决离线音乐库歌词同步难题

图形学渲染管线全解:从顶点到像素

发布时间:2026/8/13 17:58:35
图形学渲染管线全解:从顶点到像素 第一遍:骨架(30 秒版本)渲染管线 把3D 顶点数据变成屏幕上像素颜色的一条流水线。[顶点数据 Shader 纹理] ↓ ┌──────────────────┐ │ 1. Vertex Shader │ 处理每个顶点(坐标变换) └──────────────────┘ ↓ ┌──────────────────┐ │ 2. 图元装配 │ 把顶点组装成三角形 └──────────────────┘ ↓ ┌──────────────────┐ │ 3. 裁剪 剔除 │ 丢掉屏幕外/背面的三角形 └──────────────────┘ ↓ ┌──────────────────┐ │ 4. 光栅化 │ 三角形 → 一堆像素(fragment) └──────────────────┘ ↓ ┌──────────────────┐ │ 5. Fragment Shader│ 每个像素算颜色 └──────────────────┘ ↓ ┌──────────────────┐ │ 6. 逐片元操作 │ 深度测试、混合、写入 FB └──────────────────┘ ↓ [屏幕像素]核心洞察:管线是漏斗形的——数据量越往后越大(1 个三角形能生成几万个 fragment),所以越靠后的阶段越贵,优化空间越大。第二遍:每个阶段的详细扒开阶段 0:输入 —— 我们有什么原料?在管线开始前,GPU 已经通过 binding point 拿到了这些原料: VBO(顶点数据): [顶点1: pos, uv, normal, color] [顶点2: pos, uv, normal, color] [顶点3: pos, uv, normal, color] ... EBO(索引数据): [0, 1, 2, 2, 3, 0, ...] ← 每 3 个索引组成一个三角形 ️ 纹理(在纹理工位上) Shader Program(在 shader 工位上) Uniform 数据(matrices、光源、参数...)阶段 1:Vertex Shader(顶点着色器)输入:一个顶点(位置 各种属性)输出:变换后的顶点(裁剪空间位置 传给下一阶段的属性)主要做什么?核心工作:把顶点从模型空间变换到裁剪空间。一个顶点的坐标变换旅程,是图形学最经典的三次变换:[模型空间] → M(Model 矩阵) → [世界空间] [世界空间] → V(View 矩阵) → [观察空间] [观察空间] → P(Projection 矩阵) → [裁剪空间] 一步到位: gl_Position P * V * M * vec4(vertexPos, 1.0);用现实类比把这个过程想象成给一个模型拍照:模型空间:模型自己内部的坐标系(比如一个人的模型,原点在脚下)世界空间:把模型摆到场景里的某个位置(比如人站在房子门口)观察空间:相机的视角(以相机为原点)裁剪空间:把 3D 投影到 2D 的准备阶段(近大远小的透视)关键点:并行执行每个顶点独立跑一遍 Vertex Shader,可以完全并行。GPU 上几千个核心同时算不同的顶点。Vertex Shader 里能做什么?坐标变换(主要工作)顶点动画(骨骼蒙皮 skinning、顶点位移)计算传给 fragment shader 的插值属性(法线、切线、世界坐标等)顶点光照(便宜但粗糙的光照方案)阶段 2:图元装配(Primitive Assembly)输入:一堆变换后的顶点输出:一堆三角形(或点、线,取决于 draw 模式)做什么?按索引把顶点组装成三角形。举例:顶点:[v0, v1, v2, v3] 索引:[0, 1, 2, 0, 2, 3] 组装出两个三角形: 三角形A:(v0, v1, v2) 三角形B:(v0, v2, v3)这个阶段还有个功能:处理图元的连接方式(TRIANGLES / TRIANGLE_STRIP / TRIANGLE_FAN)。这个阶段基本上是硬件自动完成的,你没什么控制权(几何着色器/曲面细分是例外)。阶段 3:裁剪 剔除(Clipping Culling)输入:一堆三角形输出:留下值得画的三角形3.1 视锥裁剪(Frustum Clipping)问题:相机看不到的三角形,我们别浪费性能去光栅化了。裁剪空间里,可见区域是一个[-1, 1]³的立方体(NDC 之前)。三角形分三种情况:情况 A:完全在视锥内 → 保留 情况 B:完全在视锥外 → 丢弃 情况 C:部分在视锥内 → 切开!生成新的顶点情况 C 是最麻烦的——三角形被视锥边界切开时,GPU 会生成新的顶点,可能把 1 个三角形变成 2 个甚至 3 个。3.2 背面剔除(Back-face Culling)问题:一个封闭模型(比如一个球),背面(远离相机的一半)看不见,不用画。判断方法:根据三角形顶点的顺时针/逆时针方向判断朝向。if(三角形是背对相机的)→ 丢弃Unity 里Cull Back / Cull Front / Cull Off就是控制这个。3.3 透视除法(Perspective Divide)裁剪空间 → NDC(Normalized Device Coordinates):NDC ClipSpace.xyz / ClipSpace.w这一步把近大远小的效果落实到坐标上。3.4 视口变换(Viewport Transform)NDC(范围 [-1, 1])映射到屏幕坐标(比如 [0, 1920] × [0, 1080])。至此,每个三角形的顶点已经是屏幕上的位置了。阶段 4:光栅化(Rasterization)—— 管线的炸弹阶段输入:屏幕空间的三角形输出:一大堆 fragment(片元 潜在的像素)什么是 fragment?Fragment ≠ Pixel。Pixel:屏幕上的物理像素(最终显示的东西)Fragment:三角形覆盖某个像素后产生的候选颜色,一个 pixel 可能被多个 fragment 竞争光栅化做什么?判断三角形覆盖了屏幕上的哪些像素,并为每个覆盖的像素生成一个 fragment。三角形: v0 ▲ / \ / \ / \ /_______\ v1 v2 光栅化后生成: [fragment (100, 200)] [fragment (101, 200)] [fragment (100, 201)] ... 可能几千个 fragment关键步骤4.1 覆盖测试(Coverage Test)对屏幕上每个像素,判断我在不在这个三角形内?开了 MSAA 4x:判断像素里的 4 个采样点分别在不在 → 得到 4-bit 的coverage mask(还记得吗?)4.2 属性插值(Attribute Interpolation)这一步至关重要。Vertex Shader 输出的属性(uv、法线、颜色等),只有 3 个顶点上有值。但三角形内部的 fragment 需要各自的属性。做法:根据 fragment 在三角形里的位置,用重心坐标插值:v0 (uv 0,0) ▲ /|\ / | \ / × \ ← × 处的 uv 是 v0/v1/v2 的加权平均 /___|___\ v1 (uv1,0) v2 (uv0.5,1)所有 vertex shader 输出的varying/TEXCOORD,都在这一步被插值。4.3 透视校正插值一个常被忽略但很重要的细节:插值不能是简单的线性插值,必须考虑透视效果(近大远小对纹理坐标的影响)。GPU 硬件自动处理,但底层实现涉及除以 w。阶段 5:Fragment Shader(片元着色器)输入:一个 fragment(带着插值好的属性)输出:一个颜色(可能还有深度)主要做什么?为每个 fragment 计算最终颜色。这是整个管线里最贵的阶段,因为:Fragment 数量巨大(一个屏幕百万级)光照、纹理采样、复杂计算都在这里常见工作half4 frag(v2f i) : SV_Target { // 1. 采样纹理 half4 albedo tex2D(_MainTex, i.uv); // 2. 采样法线贴图 half3 normal UnpackNormal(tex2D(_BumpMap, i.uv)); // 3. 光照计算(diffuse specular) half3 lighting ComputeLighting(normal, i.worldPos); // 4. 混合、后处理 half4 finalColor albedo * lighting; return finalColor; }为什么 Fragment Shader 这么贵?规模的问题:一个 1080p 屏幕 200 万像素一个物体覆盖屏幕一半 100 万个 fragment 要跑 shader场景有几十个物体 重叠 几百万到上亿次 shader 执行 / 帧这就是移动端最怕 Overdraw、Fragment Shader 优化收益最大的原因。Early-Z(早期深度测试)优化:如果 GPU 能提前知道这个 fragment 反正会被深度测试丢弃,就不用跑 fragment shader 了。触发条件:Shader 里不discardShader 里不修改深度(SV_Depth)不开启 alpha blend这也是为什么discard(Alpha Test)、写深度都会破坏 Early-Z 优化。阶段 6:逐片元操作(Per-Fragment Operations)输入:Fragment Shader 输出的颜色输出:最终写入 framebuffer 的像素这是一系列测试和混合操作,按顺序执行:6.1 剪裁测试(Scissor Test)只在指定的矩形区域内渲染(比如小地图、UI 面板范围)。6.2 模板测试(Stencil Test)有一个模板缓冲区(stencil buffer),可以基于它做各种遮罩效果:镜面反射描边阴影体积UI 遮罩if(stencil_value_at_pixel 满足某条件)通过;else丢弃 fragment;6.3 深度测试(Depth Test)核心的可见性判断。if(fragment.depthdepthBuffer[pixel])// 更近通过,可能更新深度缓冲else丢弃 fragmentUnity 里ZTest Less / Greater / LEqual / Always就是控制比较方式,ZWrite On/Off控制是否更新深度缓冲。这是 Alpha Blend 物体必须关 ZWrite 的原因(否则透明物体会遮挡后面的透明物体)。6.4 混合(Blending)如果 fragment 没被丢弃,和 framebuffer 里已有的颜色混合:finalColorsrcFactor*shaderColordstFactor*frameBufferColor;这就是 Alpha Blend 的实现:Blend SrcAlpha OneMinusSrcAlpha 半透明混合Blend One One 加法混合(粒子、发光)Blend DstColor Zero 乘法混合(阴影)6.5 写入 Framebuffer最终颜色写入 framebuffer 对应的像素。这才是像素被画出来的时刻。阶段 7:(可选)MSAA Resolve如果开了 MSAA,framebuffer 存的是每个像素的多个采样点颜色。最后需要resolve成最终显示的图:每个像素的 4 个 sample 颜色 → 平均 → 最终像素颜色在 TBDR 手机 GPU 上,这一步发生在片上缓存内,只把 resolve 后的结果写到系统内存。这就是手机 MSAA 免费的核心。一张完整流程图[顶点数据] [索引] [纹理] [Uniforms] │ │ │ │ ▼ │ │ │ ┌────────────────────────────────┐ │ 1. Vertex Shader(每顶点1次) │ ← 可编程 │ 坐标变换 属性计算 │ └────────────────────────────────┘ │ ▼ ┌────────────────────────────────┐ │ 2. 图元装配 │ ← 硬件固定 │ 按索引组装三角形 │ └────────────────────────────────┘ │ ▼ ┌────────────────────────────────┐ │ 3. 裁剪 剔除 │ ← 硬件固定 │ 视锥裁剪、背面剔除 │ │ 透视除法、视口变换 │ └────────────────────────────────┘ │ ▼ ┌────────────────────────────────┐ │ 4. 光栅化(生成大量 fragment) │ ← 硬件固定 │ 覆盖测试(coverage mask) │ │ 属性插值 │ └────────────────────────────────┘ │ ┌─────┴─────┐ ▼ │ ┌────────────────┐ │ │ Early-Z 测试 │────┘(可能提前丢弃) │(优化路径) │ └────────────────┘ │ ▼ ┌────────────────────────────────┐ │ 5. Fragment Shader(每像素1次) │ ← 可编程,最贵 │ 采样纹理、光照、颜色计算 │ └────────────────────────────────┘ │ ▼ ┌────────────────────────────────┐ │ 6. 逐片元操作 │ ← 硬件固定 │ Scissor → Stencil → Depth │ │ → Blend → Framebuffer │ └────────────────────────────────┘ │ ▼ ┌────────────────────────────────┐ │ 7. MSAA Resolve(可选) │ │ 多采样平均 → 最终像素 │ └────────────────────────────────┘ │ ▼ [屏幕像素]关键元规律—— 从管线推导性能现在你有了完整管线图,可以推导出图形学所有的性能规律:规律 1:“越靠后越贵”Vertex Shader:每顶点 1 次,通常几万次/帧Fragment Shader:每像素 1 次,可能几千万次/帧结论:优化的 80% 收益来自减少 Fragment Shader 的执行次数。规律 2:减少 Fragment Shader 执行的方法视锥剔除(阶段 3):越早剔除越好遮挡剔除(游戏引擎实现):不画被遮挡的物体Early-Z(阶段 5 前):不该跑的 fragment 提前丢LOD:远处用低模,顶点少 → 光栅化产生的 fragment 也少不 discard、不写深度:保证 Early-Z 生效合理排序:不透明物体从前到后画,增加 Early-Z 剔除率规律 3:“减少状态切换”阶段 0 的 binding point 切换 SetPass Call合批 减少 binding 切换 减少 draw 之间的开销规律 4:“移动端的额外规律”(TBDR)阶段 6 写入 framebuffer 时,数据先写到片上 Tile 缓存,再一次性写到系统内存MSAA 在片上缓存内做,几乎免费切换 RenderTarget 极贵(触发 Tile Store/Load)discard 破坏 HSR(手机版 Early-Z)规律 5:Alpha 相关的所有雷区都来自管线Alpha Test:阶段 5 里 discard → 破坏 Early-Z / HSRAlpha Blend:阶段 6 里需要读 framebuffer 混合 → 不能 Early-Z、需要排序A2C:利用阶段 4 的 coverage mask → 绕过上面两个问题一个完整的例子:画一个带纹理的立方体来看看一个立方体的一个顶点、一个像素具体经历了什么:顶点的旅程顶点数据: position (1.0, 1.0, 1.0) (立方体的一个角,模型空间) uv (1.0, 1.0) normal (0.577, 0.577, 0.577) ↓ 阶段 1:Vertex Shader pos_clip P * V * M * (1, 1, 1, 1) (0.8, 0.6, 0.5, 2.0) (裁剪空间) ↓ 阶段 3.3:透视除法 pos_ndc pos_clip.xyz / 2.0 (0.4, 0.3, 0.25) ↓ 阶段 3.4:视口变换 pos_screen (0.4, 0.3) → (1152, 216) (1080p 屏幕坐标) 这个顶点在屏幕上的位置是 (1152, 216)一个 fragment 的旅程光栅化(阶段 4): 三角形覆盖了像素 (500, 400) 在这个像素处,重心坐标插值得到: uv (0.6, 0.4) normal (0.7, 0.5, 0.5) worldPos (2.3, 1.5, 4.0) ↓ 阶段 5:Fragment Shader albedo tex2D(_MainTex, (0.6, 0.4)) (0.8, 0.3, 0.3) (红色) lighting dot(normal, lightDir) 0.75 finalColor albedo * lighting (0.6, 0.22, 0.22) ↓ 阶段 6:深度测试 fragment.depth 0.6 depthBuffer[(500, 400)] 0.7 0.6 0.7 → 通过,更新 depthBuffer[(500, 400)] 0.6 ↓ 阶段 6:写入 framebuffer framebuffer[(500, 400)] (0.6, 0.22, 0.22) 至此,像素 (500, 400) 变成暗红色一个思考题(如果你能答上,说明彻底理解了)问题:一个场景有 1000 个不透明立方体,每个 12 个三角形,平均每个立方体在屏幕上占 100×100 像素。Vertex Shader 大约执行多少次?光栅化生成多少 fragment?Fragment Shader 大约执行多少次?如果你想优化性能,应该优先减少哪个数字?怎么减?参考答案VS:1000 × 8 顶点 8,000 次(立方体共享顶点,实际约 8 个/立方体)光栅化产生:1000 × 100×100 10,000,000 个 fragmentFS 理论上执行 10M 次,但实际因为Overdraw(立方体前后重叠)和 Early-Z(前面挡住后面),真实执行次数可能在 2M ~ 30M 之间优先优化 FS 执行次数。方法:从前到后排序渲染(增加 Early-Z 命中)遮挡剔除(不画完全被挡的立方体)LOD(远处用小 quad 代替立方体)简化 Fragment Shader总结:这张管线图是你理解一切的地图从今天起,当你看任何图形学/渲染相关的文章、教程、优化技巧、Unity 特性,都可以在这张管线图上定位:“Batching” 减少管线开始前的状态切换“MSAA” 阶段 4 的 coverage 多采样“Early-Z” 阶段 5 前的智能剔除“Alpha Blend” 阶段 6 的 blending“Post-Processing” 阶段 6 之后,把 framebuffer 当纹理再走一遍完整管线“Compute Shader” 完全绕开管线,直接编程 GPU“Deferred Rendering” 分两次管线执行,第一次只算 G-Buffer

关于恒美微站

恒美微站专注于为个体商户、工作室提供极简自助建站服务,让每个人都能轻松拥有专业网站。

快速链接

  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心

服务项目

  • 可视化建站
  • 拖拽编辑
  • 主题定制
  • SEO 优化
  • 网站托管

联系方式

  • 📍 地址:北京市朝阳区建国路 88 号
  • 📞 电话:400-888-8888
  • ✉️ 邮箱:info@hmyw.cn
  • 🕐 时间:周一至周日 9:00-18:00

© 2024 恒美微站 hmyw.cn 版权所有 | 京 ICP 备 12345678 号