渲染流水线与Shader概念
我的模型到底是怎么渲染到屏幕上的???
以《原神》雷电将军模型为例,从双击游戏图标到屏幕显示
一、前期准备:CPU / 内存 / GPU 的分工(游戏启动阶段)
- CPU 干活:加载雷电将军的 3D 模型文件(顶点坐标、法线、UV)、贴图(皮肤 / 衣服纹理)、Shader 程序,同时设置摄像机视角(比如你操控的视角)、灯光(场景里的阳光 / 月光)。
- 内存干活:把模型、贴图、Shader 这些数据临时存起来,方便 CPU/GPU 快速读取(比硬盘快 100 倍)。
- CPU 给 GPU 下指令:“把雷电将军的模型数据传给你,按这个 Shader 的规则渲染到屏幕上!”
二、核心步骤 1:空间变换(把 3D 模型 “拍” 成 2D 雏形)
雷电将军的模型顶点一开始在模型空间(以模型自身为中心),要先经过 4 次坐标变换,才能到屏幕上的像素位置:
- 模型变换:模型空间 → 世界空间(把雷电将军放到璃月港的场景里,确定她在世界中的位置)。
- 视图变换:世界空间 → 摄像机空间(以你的游戏视角为中心,相当于 “用相机对准她”,视野是金字塔形状的视锥体)。
- 投影变换:摄像机空间 → 裁剪空间(把不规则的视锥体 “拉成” 规则的长方体,GPU 能快速判断:哪些顶点在视野内、哪些要裁掉)。
- 视口变换:裁剪空间 → NDC 空间(-1~1 的标准立方体)→ 屏幕空间(映射成显示器的像素坐标,比如她的头顶对应屏幕 (500,200) 像素)。
关键:这 4 次变换主要在顶点着色器里完成,Shader 代码控制变换规则。
三、核心步骤 2:GPU 渲染流水线(把 “雏形” 变成屏幕上的像素)
GPU 拿到变换后的顶点数据,按固定流水线处理:
1. 顶点着色器
- 输入:雷电将军的顶点数据(坐标、法线、UV)。
- 处理:完成上面的 4 次空间变换,还能加动画(比如她挥刀的动作)。
- 输出:变换后的顶点坐标(屏幕空间)+ 顶点属性(法线、UV 等)。
2. 图元装配
把顶点拼成三角形(3D 模型的基本单位),比如她的衣服由几千个三角形组成。
3. 前期剔除(减少无效计算)
- 背面剔除:把背对摄像机的三角形(比如她后背看不到的衣服三角)直接丢掉。
- 视锥体裁剪:把视野外的三角形(比如她脚边超出屏幕的部分)裁掉。
4. 光栅化(找像素 + 填属性)
- 第一步:判断每个三角形盖住了屏幕上哪些像素(比如她的脸盖住了 (490,180)~(510,220) 的像素)。
- 第二步:对每个像素插值计算属性(比如从顶点的颜色 / UV / 深度,算出每个像素的颜色倾向、贴图位置、离镜头的距离)。
- 输出:一堆 “片段”(像素候选,带颜色、深度、UV 等属性)。
5. 片段着色器
- 输入:光栅化生成的片段。
- 处理:按 Shader 规则计算最终颜色(比如采样皮肤贴图、计算阳光照在脸上的高光、加雷元素特效)。
- 输出:每个片段的最终颜色(比如她的瞳孔是紫色 #660099)。
6. 测试与混合(筛选有效像素)
- 深度测试(Early-Z 提前做):如果某个片段被前面的物体挡住(比如她的手挡住了身体),直接丢掉,不画。
- Alpha 测试:如果片段是透明的(比如她的头发丝),提前丢掉,减少计算。
- 混合:透明部分(比如雷元素特效)和背景混合,算出最终颜色。
7. 帧缓存
把通过测试的像素颜色存到帧缓存里(相当于 “画到临时画布上”)。
8. 输出到屏幕
显卡把帧缓存里的像素数据传给显示器,你就看到了屏幕上的雷电将军!
四、性能优化(让渲染更快)
GPU 会在上述步骤中做优化,避免卡顿:
- 减少图元:远处的雷电将军用 LOD 模型(三角形数量减半)。
- 减少片段:Early-Z 提前丢被挡住的像素,不进片段着色器。
- 并行计算:GPU 按 2×2 像素块批量算,不是一个像素一个算。
- 简化 Shader:减少片段着色器的计算(比如关闭复杂的光影特效)。
五、Shader 的核心作用
整个流程中,Shader 是 “规则制定者”:
- 顶点着色器:决定模型的位置 / 形状(比如让雷电将军变大 / 变小)。
- 片段着色器:决定像素的颜色 / 特效(比如让她的衣服发光、变成红色)。
- 材质:Shader 的 “实例化”(比如给同一个 Shader 调不同参数,做出 “普通衣服” 和 “雷电特效衣服” 两种材质,绑到模型上就有不同效果)。
总结(一句话记全流程)
你的 3D 模型先由 CPU 加载数据并传给 GPU,经过 4 次空间变换把 3D 顶点转成屏幕像素坐标,再通过 GPU 渲染流水线(顶点着色器→图元装配→剔除→光栅化→片段着色器→测试混合),最终把三角形变成带颜色的像素存入帧缓存,输出到屏幕;Shader 控制 “怎么变、怎么上色”,各种剔除 / Early-Z 优化让这个过程更快。
关键要点
- 空间变换的核心是把 3D 模型映射到 2D 屏幕像素,裁剪空间是为了 GPU 高效裁剪。
- 光栅化是 “找像素 + 填属性”,片段着色器是 “算最终颜色”。
- 所有优化的核心:早剔除无效图元 / 片段,减少 GPU 的无用计算。
正文开始!!!
目录
渲染流水线概念
显卡怎么把 3D 模型变成屏幕上一张图的全过程
3D图形渲染完整流水线
渲染的本质,是把一堆数学描述的3D数据,变成一个能让你大脑觉得“真实”或“可信”的2D图片。
实现这个“骗局”,要解决三个根本矛盾:
- 3D到2D的矛盾:如何把三维空间里的东西“拍”到二维屏幕上,还要有立体感?
- 连续到离散的矛盾:模型是连续的曲面,屏幕是离散的像素点,怎么对应?
- 无限细节与有限算力的矛盾:现实世界无限复杂,显卡算力有限,怎么办?
第一部分:空间魔术 —— 3D到2D的“拍摄”过程
搭建舞台(世界空间):
你在做什么:把各种模型(房子、小人、树)摆在一个巨大的虚拟三维舞台(世界坐标系)上,定好各自的位置、大小、朝向。
背后逻辑:建立一个统一的参考系,让所有物体都知道彼此的关系。这是“全局”视角。
架设摄像机(视图空间):
你在做什么:把摄像机摆在某个位置,对准某个方向。从现在开始,你不再关心舞台的绝对坐标,只关心从摄像机看过去,东西是什么样子。
背后逻辑:进行“视图变换”。把所有物体的坐标,从“世界坐标”重新计算为相对于摄像机(原点)的“本地坐标”。这是从“上帝”视角切换到“玩家”视角的关键一步。
选择镜头和构图(投影与裁剪):
广角还是长焦?:你用“投影矩阵”来模拟镜头。透视投影(近大远小,有灭点)模拟人眼/相机;正交投影(无大小变化)常用于CAD或2D游戏。
取景框在哪?:你不可能拍下整个宇宙,所以定义一个“视锥体”——一个被近、远裁剪平面和视角范围切割出的金字塔形空间。只处理这个空间里的东西,外面的全部扔掉。这是最重要的性能优化之一,叫“视锥体剔除”。
冲印照片(屏幕映射):
你在做什么:经过投影,所有物体都被“压扁”到了一个标准的2D矩形里(范围是-1到1)。现在,你把这张“标准底片”缩放、平移,对应到最终屏幕的像素分辨率上。
背后逻辑:得到一个从3D世界到2D屏幕像素位置的数学映射关系。到这里,所有3D位置信息都已经转换成了2D屏幕上的“大概位置”。
第二部分:填色游戏 —— 从“形状”到“像素”
“点阵化”形状(光栅化):
你在做什么:把连续的三角形(所有3D模型的基石)边界,分解成一个个离散的屏幕像素格子。判断“哪些像素的中心点落在这个三角形内部”。
背后逻辑:这是用离散逼近连续的核心算法。硬件有极其高效的电路(扫描线算法等)并行处理海量三角形。每个被覆盖的像素生成一个片元(你可以理解为“候选像素”,带有颜色、深度、纹理坐标等信息)。
解决“谁在前谁在后”的问题(深度测试/Z-Buffer):
核心矛盾:多个三角形的片元可能投影到同一个像素位置,该显示哪个?
天才的解决方案:为每个像素点额外开辟一块内存,不存颜色,只存一个数字——深度值(Z值),代表这个像素点对应的物体离摄像机的距离。
工作流程:当一个新的片元要写入某个像素时:
先看这个像素原来记录的深度值(之前最近物体的距离)。
再看新片元的深度值(当前物体的距离)。
“谁近,谁赢”:如果新片元更近,就用它的颜色覆盖像素颜色,并更新深度值为新值;如果更远,就默默丢弃。
为什么是“天才”:这个方法无需对物体进行从后往前的排序,可以以任何顺序处理三角形,硬件并行度极高,是实时图形的基石技术。
第三部分:让画面“活”起来 —— 着色的把戏
现在每个像素都知道该显示哪个三角形了,但颜色是单调的。怎么让它看起来有质感、有光影?
“查表”艺术(纹理映射):
你在做什么:每个片元不仅知道自己的屏幕位置,还通过插值得到了一个“纹理坐标”(UV坐标),就像一张“寻宝图”。
背后逻辑:拿着这个坐标,去一张巨大的、预先画好的图片(纹理贴图)里查找对应的颜色。这张图可以模拟木纹、砖墙、皮肤等一切表面细节。这是用极低的多边形+高分辨率贴图,来模拟高精度模型的“障眼法”。
光影魔术(着色计算):
你在做什么:在片元着色器中,结合纹理颜色、法线方向、光源信息、观察方向,计算最终颜色。
核心把戏:
法线贴图:不增加任何多边形,只通过改变每个像素点的“法线方向”,让一个平坦的表面看起来有凹凸不平的细节光影。
PBR(基于物理的渲染):用一套符合物理规律的数学模型(如BRDF),计算光在不同材质(金属、布料、塑料)上的反射、折射、散射,让结果无比真实可信。它的核心是“欺骗”变得有章可循,遵循物理规律,效果更统一可控。
为什么要在像素级做?:在顶点级计算光照再插值,在多边形少的地方会露出破绽(如马赛克状光影)。在像素/片元级计算,无论多边形多少,每个像素都有独立、精确的光影,质量极高。这是性能(顶点着色)与质量(片元着色)的经典权衡。
需要注意的、容易误解的“点”
“渲染管线是线性的”是错的:现代GPU是大规模并行流水线。当顶点着色器在处理第100个三角形时,片元着色器可能在处理第1个三角形生成的像素。它不是等一步全部做完再做下一步。
深度测试在片元着色之后?不一定!:为了性能,现代GPU有“提前深度测试”。如果一个片元确定会被前面物体遮挡,GPU会尽早将其丢弃,避免执行昂贵但无用的片元着色计算。这是关键的优化。
CPU和GPU的分工是性能生命线:CPU擅长逻辑复杂、串行任务(决定画什么,准备数据),GPU擅长数学简单、海量并行任务(处理顶点和像素)。瓶颈往往在它们的通信上。一次DrawCall(CPU让GPU画一个东西)就是一次通信,通信次数太多,CPU就忙不过来了,GPU却在“饿肚子”。这就是为什么游戏要尽量减少绘制调用,合并物体。
“实时”与“离线”的根本区别:游戏是“实时渲染”,要求至少每秒30/60帧。这意味着每帧必须在33或16毫秒内完成所有计算。所以必须用各种“骗术”(预计算光照、简化模型、LOD)来保证速度。而电影是“离线渲染”,一帧可以花几个小时,可以用物理上绝对精确但极慢的算法(如路径追踪)。
补充的几个“为什么”
为什么一定是三角形?
三角形是最简单的多边形,其关键特性是:三个点永远唯一确定一个平面。这意味着三角形内部任意一点的属性(颜色、深度)都可以通过其三个顶点的属性线性插值得到,且结果确定、无歧义。四边形或其他多边形则可能发生扭曲(不共面),给插值和光栅化带来巨大麻烦。三角形是复杂曲面离散化的“原子”,稳定且高效。
“视图变换”更深层的天才之处:
将坐标系原点移到摄像机。它的精妙在于,变换后,深度(Z值)直接代表了到摄像机的距离,这为后续的深度测试(Z-Buffer)提供了完美的数据基础。整个流水线的很多步骤都依赖这个“以摄像机为中心”的坐标系。
“视锥体剔除”的局限性:
需要知道,这只是“粗剔除”。一个物体在视锥体内,不代表它完全可见。它可能被其他物体挡住(遮挡剔除),或者其背面朝向我们(背面剔除)。现代引擎在CPU端有更复杂的算法(如层次Z缓冲、软件遮挡剔除)来提前丢弃更多不可见物体,减少GPU的无用功。剔除是实时渲染性能的头等大事。
Z-Buffer的“阿喀琉斯之踵”:
Z-Buffer是天才,但也有问题。它存储的是非线性的深度值(通常是在投影变换后,经过1/z映射的值),这导致近处的深度精度很高,远处的精度很低。这就是为什么在超大场景中,远处物体会出现“Z-fighting”(深度冲突闪烁)。解决方案是精心设置近、远裁剪平面的距离,让精度分布更合理。
【“阿喀琉斯之踵” 是一个著名的希腊神话典故,用来比喻一个强大事物身上唯一的、致命的弱点。
典故来源:英雄阿喀琉斯出生时被母亲提着脚踝浸入冥河,使他全身刀枪不入,唯独被手握着的脚踝没有沾到河水。这处脚踝就成了他唯一的弱点。在特洛伊战争中,他被敌人一箭射中脚踝而亡。】
“着色”的终极目标与妥协:
要理解,实时渲染的着色永远在物理正确性和艺术可控性之间做权衡。PBR提供了一套物理上可信的参数(金属度、粗糙度),但艺术家仍可调节它们来达到想要的风格化效果。它不是追求绝对物理正确(那是离线渲染的路径追踪),而是追求视觉上的一致性和可预测性。
现代管线早已不“固定”:
你提到GPU是并行流水线,非常对。现代图形API(Vulkan, DX12)更进一步,它们暴露的是可编程、可重配置的流水线。开发者可以绕过传统固定管线的某些阶段,甚至改变执行顺序(如计算着色器先行处理),以获得极致性能。但你的比喻依然是理解其基础工作原理的最佳方式。
最大的“骗局”——人眼和大脑:
渲染技术能成功的根本前提,是你提到的“欺骗你的眼睛”。人眼视觉系统有大量“缺陷”:对边缘和对比度敏感,对绝对亮度和颜色不敏感,有视觉暂留……渲染技术大量利用这些特点。例如,屏幕空间环境光遮蔽 模拟了缝隙的阴影,即使不物理准确,人眼也会觉得场景更扎实,因为大脑“脑补”了细节。
对整个场景进行预先设置(如摄像机视角,灯光设置)——>
基于摄像机视角检测场景中所有物体的可见性——>
设置物体的渲染状态——>
向渲染API提交几何体数据(三角形的顶点数据,如顶点坐标、法线向量、UV)——>
将顶点坐标从模型空间变换到摄像机空间,并且同时进行顶点光照计算——>
剔除掉背对着摄像机的三角形,变换到裁切空间,将视锥体之外的部分裁切掉——>
剩余会通过投影从三维变为平面,输出到屏幕空间中——>
光栅化阶段——>
计算每个像素的颜色,并把颜色信息输出到屏幕上
空间变换
1.模型空间(本地空间)与世界空间
物体的顶点数据最开始是以模型空间作为参考进行描述的。
如果描述的是不同物体之间的相对位置,则需要使用一个共同的坐标空间,这个共同的坐标空间被称为世界空间。
而把顶点坐标从模型空间转变为世界空间的过程称为模型变换,需要使用模型变换矩阵
2.摄像机空间(观察空间)
引擎在进行渲染的时候是以摄像机作为观察视角的,就像是人的眼睛
将世界空间中的物体转换到摄像机空间中,这个转换的过程称为视变换
3.裁切空间
裁切就是在摄像机视锥体的边界上对几何体进行切割,然后将视锥体范围外的几何体丢弃的过程
摄像机的视锥体决定了摄像机可以看到的区域
将顶点坐标变换到裁剪空间再进行裁切,这个变换过程叫作投影变换,需要使用裁切矩阵(投影矩阵)
注意:这个过程虽然被称为投影变换,但是几何体在裁切空间中其实没有进行投影,该操作只是为了接下来的投影操作提前做好准备
真正做的事
你用投影矩阵把顶点从观察空间转到裁剪空间,此时只干了两件事:
- 把视锥体(金字塔形状)拉伸 / 压缩成长方体
- 让每个顶点的坐标满足:−w≤x≤w,−w≤y≤w,−w≤z≤w这样 GPU 就能非常简单地判断顶点在不在视锥里,从而做裁剪。
重点:为什么说 “还没有真正投影”?
真正的投影,是指:
- 把 3D 点变成 2D 屏幕点
- 把立体压扁到平面
而裁剪空间里还没做这一步!裁剪空间仍然是 4D 齐次坐标,仍然是立体空间,只是被矩阵变形了,方便裁剪。
顶点在裁剪空间中主要做了以下事情:
1)计算顶点坐标 的w分量,用于接下来的透视除法
什么是 w?
在摄像机空间里,顶点是
(x,y,z),是普通 3D 坐标。乘上投影矩阵后,变成:(x_clip, y_clip, z_clip, w)这里的 w 不是随便算的,它约等于摄像机空间里的 z(深度)。
2)对摄像机的视锥体进行不均等缩放,使视锥体的梯形结构变成正方体
摄像机空间里的视野是什么形状?
是一个金字塔被削掉尖:平截头体(视锥体)
- 近平面小
- 远平面大
- 整体是梯形、锥形、不规则
GPU 讨厌不规则形状
因为:
- 锥形不好判断点在不在里面
- 不好裁剪三角形
- 不好做插值
所以投影矩阵干了一件事:
不均匀拉伸 / 缩放
- 远处的部分:往中间挤一挤
- 近处的部分:基本不动
最后把:锥形(梯形) → 拉成一个标准正方体
这个正方体就是裁剪空间的范围:−w≤x≤w,−w≤y≤w,−w≤z≤w
这句话到底什么意思?
投影矩阵不是均匀放大缩小,而是远处压缩得多、近处压缩得少,把原本一头大一头小的锥形视野,强行变成对称、规整的正方体,让 GPU 能超级简单地做裁剪。
裁剪空间和摄像机空间的区别?
- 摄像机空间 = 以相机为原点的真实 3D 世界
- 裁剪空间 = 为了裁剪、为了 GPU 方便算,变形后的 3D 空间
它们都是 3D(齐次 4D),只是坐标系不一样、用途不一样。
摄像机空间(观察空间 View Space)
怎么来的?
把世界坐标,用视图矩阵(View Matrix) 变换过来。
特点:
- 原点在摄像机位置
- 坐标轴:
- Z 轴:朝向摄像机看的反方向(看向屏幕外)
- X 轴:右
- Y 轴:上
- 这是一个正常、直观的 3D 空间
- 可见范围是一个金字塔:视锥体
缺点:
- 视锥体是棱锥,形状不规则,GPU不好直接做裁剪。
- 没法直接判断一个点 “在不在屏幕里”。
裁剪空间(Clip Space)
怎么来的?
在摄像机空间基础上,再乘一个投影矩阵(Projection Matrix) 得到。
特点:
- 它把视锥体(金字塔) 给拉伸、压缩成一个规则的长方体
- 坐标变成 齐次坐标 (x, y, z, w)
- 裁剪规则变得超级简单:−w≤x≤w,−w≤y≤w,−w≤z≤w满足就在视锥内,不满足就裁掉。
作用只有一个:
方便 GPU 做裁剪!
| 空间 | 从哪来 | 形状 | 目的 | 是否直观 |
|---|---|---|---|---|
| 摄像机空间 | 世界坐标 × 视图矩阵 | 正常 3D 空间,视锥体是金字塔 | 让物体以相机为中心,方便观察、计算光照 | 非常直观 |
| 裁剪空间 | 摄像机空间 × 投影矩阵 | 被矩阵扭曲过的 3D 空间 | 只为了方便裁剪,把不规则锥体变成长方体 | 不直观 |
4.屏幕空间
屏幕空间 = 以像素为单位、对应你显示器屏幕的 2D 坐标空间。
首先将顶点的x,y,z坐标分别除以w分量,得到标准化的设备坐标(NDC)。w分量已经在裁切空间中准备好了,因此无须再重新计算。然后将标准化的设备坐标屏幕像素上进行映射,就会得到屏幕空间下的像素坐标
视口变换是如何将NDC空间映射到屏幕空间的?
先回忆两个空间
-
NDC 空间(标准化设备坐标)
- 范围:
x ∈ [-1, 1],y ∈ [-1, 1] - 左手 / 右手无所谓,范围固定在 -1 ~ 1
- 范围:
-
屏幕空间
- 假设窗口:宽度
width,高度height - 像素坐标:
screenX [0, width],screenY [0, height] - 原点通常在左上角
- 假设窗口:宽度
视口变换做的事:线性映射
一句话:把 [-1, 1] 的数,线性映射到 [0, 宽度] 和 [0, 高度]。



- 世界空间 → 视图矩阵 →摄像机空间(View):相机在原点,视野是锥形
- 摄像机空间 → 投影矩阵 →裁剪空间(Clip):锥变成规则盒子,做裁剪,算出 w
- 裁剪后 → 齐次除法 (x/w,y/w,z/w) →NDC 标准化设备坐标:一个
[-1,1] × [-1,1] × [-1,1]的正方体- NDC → 映射到窗口像素 →屏幕空间(Screen Space)
整条流水线终极通俗易懂
- 世界空间:物体在世界哪里
- 摄像机空间:换到相机视角,看到一个锥形视野
- 裁剪空间:用投影矩阵把锥拉成盒子,裁掉外面,算出 w
- NDC 空间:除以 w,变成 **[-1,1] 标准盒子 **,真正完成透视
- 屏幕空间:映射成像素,GPU 开始画点、画线、画像素
5.多个坐标空间存在的意义
为何不从开始就统一使用一个坐标空间,以避免在渲染过程中空间的频繁变换?是否可以把模型空间、世界空间和摄像头空间这三个3D 空间的坐标空间统一成一个呢?
-
不能只用一个空间,因为每个空间有专门用途:
- 模型空间:方便模型复用、旋转缩放;
- 世界空间:方便放场景、做物理、AI;
- 相机空间:方便算光照、裁剪。
-
频繁变换只是乘个矩阵,对 GPU 极快、几乎不耗性能。
-
强行统一成一个空间,模型不能复用、代码极复杂、做不了大型游戏,完全得不偿失。
分成多个空间,是为了逻辑清晰、好用、好复用,不是为了复杂。
现代GPU渲染流水线
- CPU(中央处理器)
- 内存(RAM)
- GPU(图形处理器)
用 “玩《原神》” 这个场景, CPU、内存、GPU 怎么配合:
- 你双击游戏图标,CPU先 “指挥”:启动游戏程序、加载账号数据、判断你点的 “开始游戏” 指令。
- 游戏要加载地图、角色模型,这些临时数据会先存到内存里 —— 因为内存快,CPU 和 GPU 要数据时能立刻取,不用等慢硬盘。
- 进入游戏后,要显示角色动作、光影特效,GPU就接手:快速计算每个像素的颜色,把 3D 模型变成屏幕上的画面,全程 CPU 会偶尔 “查岗”,确保流程不卡。
CPU:负责逻辑、指令、场景管理、AI、物理、把模型 / 贴图 / 指令发给 GPU。
内存(RAM):临时存放游戏数据,让 CPU 和 GPU 快速读取。
GPU:专门负责把 3D 模型变成屏幕画面,也就是执行下面的渲染流水线。
GPU中图形渲染流水线的详细流程:
顶点数据——>顶点着色器——>装配图元——>光栅化——>片段着色器——>测试和混合——>帧缓存
首先,图形渲染流水线以顶点数据作为开始,当GPU获取到CPU传递的顶点数据之后,整个图形渲染流水线正式开始运作。
图形渲染流水线的第一个“站点”是顶点着色器,它允许使用者通过程序进行配置。在顶点着色器中,顶点坐标会从模型空间变换到裁切空间。(这个阶段还可以通过Shader程序对顶点进行处理)
装配图元阶段将顶点着色器输出的顶点数据装配成指定的几何图元,基本图元包括:点、线、面。
光栅化是将几何图元转变为片段的过程。
屏幕上显示的图像都是由像素组成,而3D物体是由点、线、面组成的,要让几何图元能在屏幕上显示为像素就需要经过光栅化处理。
该阶段包含两部分的工作:
1)确定屏幕坐标中的哪些整型栅格区被基本图元占用。
2)分配颜色值和深度值到各个区域。
光栅化就两步:
- 找像素:这个三角形盖住哪些屏幕格子?
- 填像素:每个格子涂什么颜色、深度是多少?
光栅化就是:把经过变换后的几何图元,找出屏幕上被覆盖的像素,对顶点属性进行插值计算,生成带颜色、深度等信息的片段,供后续渲染使用的过程。
片段在经过视锥体裁切之后,就会被传递到片段着色器,它的最主要目的是计算每一个像素的颜色(这个阶段还可以通过Shader程序对顶点进行处理)。这个阶段中,片段着色器会计算光照,阴影,纹理等所有颜色数据,最终计算出像素颜色。
当所有像素的颜色都确定下来之后,最终会进入到测试(Test)和混合(Blending)阶段,在这个阶段会检测所有像素的深度值,将当前片段的深度值与深度缓存中的数值对比,从而判断这个像素的前面是否有物体对它进行遮挡,进而决定这个像素是否应该被丢弃。
通过测试的像素会与已经绘制好的图像进行混合,从而得到最终的颜色。
帧缓存是图形渲染流水线的最后一个“站点”,帧缓存中存储着用于渲染到屏幕上的像素,等待下一步输出到屏幕上。
GPU 从 CPU 拿到顶点数据,经过顶点着色器变换坐标,装配成三角形图元,再通过光栅化生成屏幕片段,由片段着色器计算颜色,经过深度测试与混合后,最终写入帧缓存显示到屏幕。
如何提高光栅化的效率?
提高光栅化效率的核心思路
光栅化慢,主要就两个原因:
- 要处理的像素 / 片段太多
- 无效计算太多(被挡住、看不见、超小图元)
所以优化方向就是:能早扔就早扔,能少算就少算,能并行就并行。
一、减少要光栅化的图元(从源头减负)
- 视锥体裁剪(Clipping)不在相机视野里的三角形,直接丢掉,不进光栅化。
- 背面剔除(Back-face Culling)背对相机的面,直接不画。一个立方体,一次最多看到 3 个面,另外 3 个直接扔掉。
- 视口剔除完全在屏幕外的三角形,直接跳过。
- 细节层次 LOD远处物体用面数更少的模型,减少三角形数量。
二、减少片段数量(光栅化本身提速)
- 降低分辨率 / 渲染缩放像素越少,光栅化越快。
- 早测试、早丢弃
- Early-Z / 深度测试提前先比深度,被挡住的片段直接丢掉,不进片段着色器。
- Alpha Test 提前透明丢弃的片元,越早扔越好。
- 小图元优化特别小、占不到 1 个像素的三角形,减少遍历开销。
- 压缩 / 简化包围盒只遍历三角形周围最小矩形,不遍历全屏幕。
Early-Z(深度测试提前)是什么?
正常流程(慢)
光栅化 → 片段着色器(算光照、纹理、特效) → 深度测试 → 扔掉被挡住的像素
问题:明明被挡住了,还先算半天颜色,最后才扔掉 → 纯浪费性能!
Early-Z 流程(快)
光栅化 → 先做深度测试
Alpha Test 提前 是什么?
Alpha Test 就是:纹理里有些地方是透明的,直接丢掉,不画。
比如树叶、栏杆、头发。
正常流程(慢)
进片段着色器 → 采样纹理 → 发现 Alpha=0 → 扔掉
提前 Alpha Test(快)
光栅化后 尽早判断透明度
- 透明?
- 直接丢!
- 不进片段着色器
一句话解释:
透明的像素,越早扔掉,GPU 越轻松,尽量在进入片段着色器之前,把没用的像素扔掉,减少 GPU 压力。
Early-Z 是将深度测试提前到片段着色器之前,被遮挡的片段直接丢弃,避免执行无效的片段着色运算;
Alpha Test 提前则是尽早丢弃透明片段,两者都是为了减少片段着色器压力,提高渲染效率。
三、硬件层面优化(GPU 天生就这么干)
- 2×2 像素块并行处理GPU 不是一个像素一个像素算,而是按块算,并行拉满。
- 片元批量处理同一三角形、同一区域的像素一起算,提高缓存命中率。
- 深度缓存、颜色缓存高速缓存减少读写延迟,让光栅化更快。
四、Shader & 程序层面优化
- 减少片段着色器计算量越简单,光栅化后跑得越快。
- 避免过度细分曲面三角形爆增 → 光栅化爆炸。
- 减少透明物体数量透明无法 Early-Z,必须按顺序画,极慢。
片段和像素有什么区别?
答案:
- 片段:光栅化生成的候选像素,还没经过测试、混合
- 像素:最终写入帧缓存、显示在屏幕上的点
Early-Z 为什么能提升性能?
答案:把深度测试提前到片段着色器之前,被遮挡的片段直接丢弃,不用执行片段着色器,大幅减少计算量。
顶点着色器和片段着色器的区别?
答案:
- 顶点着色器:处理顶点,主要做坐标变换
- 片段着色器:处理像素,主要做颜色计算(光照、纹理、阴影)
Shader概念
流水线中有两个阶段开放给用户自定义配置,以实现各种不同的效果,而自定义配置的方式就是通过Shader程序
1.什么是Shader
运行在GPU上,目的是为了告诉GPU如何计算和输出图像
Shader所处的阶段只是渲染流水线的一部分,它主要由顶点着色器和片段着色器组成。
编写Shader的语言:
“CG 是 Unity Shader 首选语言” 是早期 Unity 版本(5.x 及之前)的说法,现在(Unity 2018+)已经完全更新:
- CG 语言:NVIDIA 和微软联合开发,本质是HLSL 的超集,语法几乎和 HLSL 一致。
- 现状:NVIDIA 在 2012 年就停止更新 CG,Unity 从 2018 开始主推HLSL(微软的高级着色器语言),但为了兼容,Unity 依然支持 CG 语法(写 CG 的代码会被 Unity 自动转成 HLSL)。
简单说:
现在写 Unity Shader,写的是 HLSL 语法,只是还能沿用 CG 的老写法。
2.Shader和材质的关系与区别
Shader实际上就是一段程序,它负责把输入的顶点数据按照代码里指定的方式进行处理,并对输入的颜色或者贴图等进行计算,然后输出数据。图像绘制单元获取到输出的数据便可将图像绘制出来,最终呈现在屏幕上。
Shader = 给 GPU 的 “绘图规则”,告诉 GPU:怎么处理顶点、怎么算颜色,最终画出什么样的像素。
Shader 是运行在 GPU 上的小程序,核心作用是定制图像的绘制规则。
Shader程序代码,再加上开放的参数设置以及关联的贴图等资源,为实现某种效果而打包存储在一起,最终得到的就是材质
材质是Shader的实例化资源,一个Shader可以实例化多个材质,并且调节为不同的材质效果。最后把材质指定给某个模型就可以渲染出对应的效果了。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)