前向渲染和延迟渲染
🌈 前向渲染(Forward Rendering)和延迟渲染(Deferred Rendering)——详细阶段对比
这两种渲染方式都属于 基于光照的渲染管线(Lighting Pipeline),区别在于光照和材质的计算时机不同。
🎯 一、前向渲染(Forward Rendering)
✅ 渲染阶段流程:
每个物体 → 顶点着色器 → 光栅化 → 片元着色器(计算光照) → 合并输出到帧缓冲
🚩 每个阶段解释:
阶段 | 作用 | 关键点 |
---|---|---|
1️⃣ 顶点着色器 | 处理模型的顶点(变换到屏幕空间) | 模型、视图、投影矩阵 |
2️⃣ 光栅化 | 将三角形“填充”为像素(片元) | GPU自动处理 |
3️⃣ 片元着色器 | 每个像素计算颜色,此时就计算光照! | 光照开销大 |
4️⃣ 合并输出 | 输出最终像素颜色到帧缓冲 | 支持透明效果 |
🟢 优点:
-
实现简单,渲染路径直接
-
透明对象支持良好
-
所有硬件都支持
🔴 缺点:
-
每个像素都要重复计算光照
-
光源多了性能急剧下降
-
阴影、全局光照不易扩展
🎯 二、延迟渲染(Deferred Rendering)
✅ 渲染阶段流程(更复杂):
1. 几何阶段(Geometry Pass):所有物体 → 写入 G-buffer(记录位置、法线、材质信息)2. 光照阶段(Lighting Pass):每个像素 → 读取 G-buffer → 统一计算光照3. 合成阶段(Composition Pass):合成所有信息输出最终颜色
🚩 阶段细节:
阶段 | 内容 | 数据 |
---|---|---|
1️⃣ 几何阶段 | 所有物体写入 G-buffer | 位置、法线、反射率、颜色、深度等 |
2️⃣ 光照阶段 | 使用 G-buffer 对每个像素统一处理光照 | 高效统一处理多个光源 |
3️⃣ 合成阶段 | 最终输出颜色图像 | 可加后期处理(Bloom、HDR等) |
🟢 优点:
-
多光源性能优越(只计算一次像素)
-
易于集成后处理效果(如Bloom、SSA0)
-
几何与光照解耦,灵活
🔴 缺点:
-
不支持透明对象(因为没有透明顺序)
-
消耗显存大(多个G-buffer)
-
不适合低端设备
🧪 对比总结表:
比较项 | 前向渲染 | 延迟渲染 |
---|---|---|
光照计算 | 每物体每像素 | 每像素一次,统一光照 |
光源数量 | 少则性能好,多则急剧下降 | 多光源效率更高 |
支持透明 | ✅ 支持良好 | ❌ 支持差 |
后处理 | 较困难 | 容易扩展 |
内存占用 | 低 | 高,需要多个缓冲区 |
实现复杂度 | 低 | 高,需要设计G-buffer |
📘 初学者建议:
建议 | 说明 |
---|---|
学习顺序 | 先理解前向渲染,再学习延迟渲染 |
推荐路径 | Cocos / Unity 都默认使用前向渲染,也提供延迟渲染(在高质量设置中) |
着重点 | 理解 为什么延迟渲染更适合多光源环境,以及 它为何不适合处理透明物体 |
📎 附加资料(可选)
-
G-buffer 通常包含哪些内容:
-
worldPos
: 世界坐标 -
normal
: 法线向量 -
albedo
: 反照率 -
specular
: 镜面系数 -
depth
: 深度信息
-
新的一篇==========================================
延迟渲染的几何阶段和光照阶段(Lighting Pass)都会走顶点着色器和片段着色器,不过有区别
下面我给你详细拆解这个流程,作为初学者能快速理解:
🌟 延迟渲染的两个主要阶段回顾:
阶段 | 用途 | 使用顶点/片元着色器? | 说明 |
---|---|---|---|
1️⃣ 几何阶段(G-Buffer Pass) | 把场景信息写入 G-Buffer(颜色、法线、深度等) | ✅ 会走顶点和片元着色器 | 每个物体一个一个画 |
2️⃣ 光照阶段(Lighting Pass) | 利用 G-buffer 数据,逐像素计算光照 | ✅ 也会走顶点和片元着色器 | 通常只画一个全屏 Quad |
🧩 光照阶段怎么还用“顶点着色器”?
这个顶点着色器非常简单!
我们会画一个 全屏幕的矩形(Full-Screen Quad),通常由 2 个三角形组成。
顶点着色器的作用是:
-
把这 4 个顶点从局部坐标变换到屏幕坐标。
-
输出纹理坐标(UV)给片元着色器。
📌 示例顶点着色器代码(伪代码):
attribute vec2 a_position;
varying vec2 v_uv;void main() {v_uv = a_position * 0.5 + 0.5; // [-1,1] → [0,1]gl_Position = vec4(a_position, 0.0, 1.0); // 投影到屏幕空间
}
💡 光照阶段的“片元着色器”做了什么?
片元着色器会:
-
从 G-buffer 读取当前像素的:
-
位置(或重建位置)
-
法线
-
材质属性(如颜色、金属度、粗糙度)
-
-
对每个光源进行逐像素光照计算
-
输出最终颜色
📌 示例片元着色器伪代码:
varying vec2 v_uv;uniform sampler2D u_albedo;
uniform sampler2D u_normal;
uniform sampler2D u_position;void main() {vec3 albedo = texture2D(u_albedo, v_uv).rgb;vec3 normal = texture2D(u_normal, v_uv).xyz;vec3 position = texture2D(u_position, v_uv).xyz;vec3 lightDir = normalize(lightPos - position);float NdotL = max(dot(normal, lightDir), 0.0);vec3 color = albedo * lightColor * NdotL;gl_FragColor = vec4(color, 1.0);
}
✅ 所以总结:
阶段 | 顶点着色器作用 | 片元着色器作用 |
---|---|---|
几何阶段 | 变换物体顶点位置 | 输出 G-buffer 数据 |
光照阶段 | 绘制全屏 Quad(输出 UV) | 从 G-buffer 获取数据进行光照计算 |
🚀 延伸补充:
-
全屏 Quad 不需要复杂变换,所以光照阶段的顶点着色器很简单,不涉及模型矩阵。
-
光照阶段最关键的是片元着色器中的光照计算。
-
多数延迟渲染框架中,光照阶段只有一个 Draw Call,但可以处理成百上千个光源。
新的一篇==========================================
✅ 回答:
是的,延迟渲染中的 Lighting Pass 也会走一遍顶点着色器和片段(片元)着色器。
但:
-
顶点着色器很简单,仅用于生成一个全屏四边形(Full-Screen Quad)。
-
真正进行光照计算的是片元着色器。
🌈 延迟渲染两个阶段对比(都走完整管线):
Pass 阶段 | 走顶点着色器? | 走片元着色器? | 用途 |
---|---|---|---|
Geometry Pass(几何阶段) | ✅ 是 | ✅ 是 | 渲染场景所有物体,写入 G-Buffer(颜色、法线、深度等) |
Lighting Pass(光照阶段) | ✅ 是 | ✅ 是 | 渲染一个全屏 Quad,从 G-Buffer 读取数据,计算光照 |
🔍 Lighting Pass 为何还要走顶点着色器?
因为我们需要在屏幕上画一个全屏幕矩形(Full-Screen Quad)
这个矩形也有顶点,需要送进 GPU 渲染:
过程如下:
1️⃣ 顶点着色器:- 输入全屏 Quad 的顶点位置(例如左下角 [-1,-1])- 输出到裁剪空间(gl_Position)- 生成对应 UV 坐标传给片元着色器2️⃣ 光栅化:- 把这个矩形切成像素3️⃣ 片元着色器:- 对每个像素,从 G-Buffer 取出该点的材质、法线、深度信息- 计算光照结果(如漫反射、镜面反射、PBR 等)
✅ Lighting Pass 的顶点着色器通常如下(GLSL 示例):
attribute vec2 a_position; // [-1,-1] ~ [1,1]
varying vec2 v_uv;void main() {gl_Position = vec4(a_position, 0.0, 1.0);v_uv = a_position * 0.5 + 0.5; // 映射到 [0,1] UV
}
✅ Lighting Pass 的片元着色器通常如下:
varying vec2 v_uv;uniform sampler2D u_albedo;
uniform sampler2D u_normal;
uniform sampler2D u_position;void main() {vec3 albedo = texture2D(u_albedo, v_uv).rgb;vec3 normal = texture2D(u_normal, v_uv).xyz;vec3 position = texture2D(u_position, v_uv).xyz;vec3 lightDir = normalize(lightPos - position);float NdotL = max(dot(normal, lightDir), 0.0);vec3 color = albedo * lightColor * NdotL;gl_FragColor = vec4(color, 1.0);
}
🧠 总结:
内容 | 是否走 | 说明 |
---|---|---|
Geometry Pass 顶点着色器 | ✅ | 每个物体都跑一次,做变换 |
Geometry Pass 片元着色器 | ✅ | 输出材质信息到 G-Buffer |
Lighting Pass 顶点着色器 | ✅ | 只画一个全屏 Quad,输出 UV |
Lighting Pass 片元着色器 | ✅ | 每像素从 G-Buffer 取数据并计算光照 |