前向渲染与延迟渲染
需要预先学习: Phong模型与Blinn-Phong模型 全局光照与局部光照 帧缓冲 PBR 前向渲染(Forward Rendering) 前向渲染(forward rendering)是 着色与渲染管线 描述的标准流程:对每个物体,用一套顶点着色器 + 片元着色器完成变换与光照,结果直接写入颜色缓冲。 基本流程 前向渲染中,光照发生在片元着色器里:片元拿到位置、法线、材质后,遍历场景中的每一个光源,逐个累加光照贡献: // 伪代码:前向渲染的片元着色器 vec3 lighting = vec3(0.0); for (int i = 0; i < lightCount; i++) // 遍历所有光源 { lighting += calcLighting(fragPos, normal, albedo, lights[i]); } FragColor = vec4(lighting, 1.0); 思路直接:每个片元、每个光源算一遍。于是总光照成本大致是: O(物体数×光源数×...
摄像机
需要预先学习: 视图(摄像机)与投影 网格、材质与模型
半透明物体渲染
需要预先学习: 着色与渲染管线 深度缓冲 帧缓冲 透明与混合 玻璃、水面、烟雾、粒子——真实感场景离不开半透明。不透明物体的遮挡由深度缓冲解决,谁近谁显示,与顺序无关(见 深度缓冲);半透明则不同:它的颜色要与背后已经画好的内容按比例叠加,而"按比例叠加"天然依赖顺序。这篇笔记讨论半透明渲染的所有关键问题:混合怎么做、顺序怎么排、什么时候可以不排序、以及不排序的精确方案(OIT)。 alpha 通道 颜色由 RGBA 四个分量组成,第四个分量 alpha 表示不透明程度,范围 [0,1][0, 1][0,1]: alpha 含义 1 完全不透明,正常覆盖背后内容 0 完全透明,不可见 0 ~ 1 半透明,与背后内容按比例混合 over 混合方程 半透明合成使用 over 操作符:源颜色(source,即将写入的片元)以自身 alpha 为权重,覆盖目标颜色(destination,颜色缓冲中已有的内容): Cresult=αs⋅Csrc+(1−αs)⋅CdstC_{result} = \alph...
PBR
需要预先学习: Phong模型与Blinn-Phong模型 渲染方程与重要性采样 PBR 基于物理的渲染 Phong模型与Blinn-Phong模型 中的两个模型都是经验模型(empirical model):用环境光、漫反射、高光三项拼凑出"看起来对"的结果,系数(shininess、ambientStrength)没有物理含义,换个光照环境就得重新调参。 PBR(Physically Based Rendering,基于物理的渲染)从物理规律出发建立反射模型:能量守恒、微表面反射、菲涅尔效应都被显式建模,材质参数直接对应真实世界的属性(金属度、粗糙度)。同一套参数在任何光照环境下都成立,这是 PBR 被游戏与影视工业广泛采用的原因。这篇笔记介绍渲染方程与 Cook-Torrance BRDF,并给出直接光照的 GLSL 实现。 渲染方程与 BRDF 辐射度量学速览 描述光的能量需要一套度量单位,辐射度量学(radiometry)提供这套语言: 物理量 符号 含义 单位 辐射通量 Φ\PhiΦ 单位时间通过的能...
IBL
需要预先学习: PBR 纹理(cubemap 部分) 帧缓冲(渲染到纹理部分) IBL 概述 PBR 的直接光照部分把渲染方程(渲染方程与重要性采样)的积分退化成对光源求和,但环境光仍是个常数——没有方向性,金属物体反射不出环境的颜色,粗糙金属的反射也不会模糊。要补全 PBR 观感,需要来自环境各方向的光。 IBL(Image Based Lighting,基于图像的光照)的做法:用一张环境贴图(cubemap,见 纹理)记录周围环境各方向的入射光 LiL_iLi,把渲染方程的积分预先算好存进纹理,运行时查表代替求积分。 这套"预计算换实时性"的思路与 ShadowMap与PCF、PCSS、纹理 的 mipmap 一脉相承,是实时渲染的核心套路。 渲染方程的拆分 漫反射 IBL 与高光 IBL 分开处理,因为两者的积分性质不同: 项 积分 依赖 漫反射 ∫ΩLicosθ dωi\int_\Omega L_i \cos\theta\, d\omega_i∫ΩLicosθ...
伽马校正
需要预先学习: 纹理 着色器 为什么需要伽马校正 显示器是非线性的 伽马(gamma)问题源于一个物理事实:显示器的亮度响应不是线性的。CRT 时代的显像管,输入电压与输出亮度的关系近似幂函数 L=VγL = V^{\gamma}L=Vγ,γ≈2.2\gamma \approx 2.2γ≈2.2——输入电压翻倍,亮度远达不到两倍。LCD 虽原理不同,但为了兼容,也模拟这条曲线。 所以显示器上"0.5 的像素值"显示出来的物理亮度,只有"1.0 的像素值"的 0.52.2≈0.220.5^{2.2} \approx 0.220.52.2≈0.22,而不是一半。 人眼对暗部更敏感 人眼对暗部的亮度差异比亮部敏感得多——暗处稍微亮一点就能察觉,亮处变化却不易发现。利用这个特性,sRGB 编码(sRGB encoding)在存储颜色时给暗部分配更多精度:编码曲线近似 γ=1/2.2\gamma = 1/2.2γ=1/2.2 的幂函数,正好与显示器的 2.22.22.2 抵消。 于是图像文件(.png、.jpg 等)里存的不...
色调映射
需要预先学习: 帧缓冲 伽马校正 从 LDR 到 HDR 真实场景的动态范围 传统渲染的帧缓冲用 8bit 无符号整数存储颜色,取值范围 [0,1][0, 1][0,1],称为 LDR(Low Dynamic Range,低动态范围)。但真实场景的亮度范围远不止如此:阳光直射下的亮度是阴影处的几万倍(动态范围约 10510^5105)。把这样的场景塞进 [0,1][0, 1][0,1],结果只有两种: 把曝光调到阴影可见,太阳就变成一片纯白; 把曝光调到太阳可见,阴影就变成一片纯黑。 无论哪种,亮部或暗部的细节都被截断(clamp)了。 HDR 渲染:浮点帧缓冲 HDR(High Dynamic Range,高动态范围)渲染的做法:中间结果用浮点纹理存储,亮度超过 1.0 的值不被截断,等最后再压缩到显示范围。创建浮点帧缓冲: // 创建 HDR 帧缓冲:颜色附件用半浮点纹理 unsigned int hdrFBO; glGenFramebuffers(1, &hdrFBO); glBindFramebuffer(GL_FRAMEBUFFER...
Bloom
需要预先学习: 帧缓冲 色调映射 纹理 Bloom 原理 Bloom(辉光)是 HDR 渲染的经典后处理效果:场景中的亮部向周围"渗光",形成柔和的光晕。看夜空中的路灯、显示器上的高光,人眼与镜头都会看到这种光晕——光线在眼球晶状体/镜头内部散射,把亮部的能量摊到了周围。 实现的思路很直接,三步: 提取亮部:从 HDR 画面中取出超过亮度阈值的部分; 模糊:把亮部图模糊成光晕; 合成:模糊结果叠加回原图。 为什么必须用 HDR 亮度阈值要区分"亮"与"不亮",只能在 HDR 空间做。LDR 画面里所有像素都被截断在 1.0,无法区分"亮度 1.0 的灰"和"亮度 100 的太阳"——两者提取出来一模一样。HDR 帧缓冲(色调映射)保留了超过 1.0 的亮度信息,阈值才有意义。 实现步骤 1. 渲染 HDR 场景 场景渲染进浮点帧缓冲(色调映射 中的 hdrFBO),光源亮度可以超过 1.0,例如点光源给 vec3(10.0) ...
屏幕空间技术
需要预先学习: 帧缓冲 深度缓冲 前向渲染与延迟渲染 Bloom 什么是屏幕空间 前向渲染与延迟渲染 的光照 Pass 展示了一种新视角:不再遍历场景,而是对屏幕上每个像素从纹理里取数据做计算。把这种思路推广——所有输入只有"当前视角下已渲染的屏幕图像(颜色、深度、法线)"、所有计算都发生在全屏 pass 上的技术,统称屏幕空间(screen space)技术。 它与 全局光照与局部光照 的关系:环境光遮蔽、间接光、反射这些 GI 效果需要整条光路的信息,实时算不动;屏幕空间技术退而求其次——屏幕上看得见的部分,就是我能拿来用的全部: 真实 GI: 光线在完整场景中反弹(包括屏幕外、被遮挡的部分) 屏幕空间: 光线只在一层"屏幕贴片"上反弹(屏幕拍到的 2.5D 深度场) 代价与收益同样明确: 优点 缺点 输入只是几张全屏纹理,复杂度与场景规模无关 屏幕外的信息不存在:反射/AO 在物体进入屏幕的边缘消失 天然适配全屏 pass / compute shader,实现...
Compute Shader
需要预先学习: 着色器 缓冲对象与顶点 帧缓冲 Compute Shader 简介 计算着色器(compute shader)是 OpenGL 4.3 引入的着色器阶段。与顶点、片元着色器不同,它不在渲染管线里——不处理顶点、不产生片元,而是直接执行通用计算(GPGPU,General-Purpose GPU computing,用 GPU 做非图形计算)。 普通管线是单向的数据流:顶点 → 光栅化 → 片元 → 帧缓冲,着色器只是管道上的加工站。计算着色器是一块独立的"通用计算单元":你告诉它"开多少个线程、每个线程干什么",它就并行执行,结果可以写进纹理、缓冲,供后续渲染使用,也可以被 CPU 读回做逻辑。 线程模型:工作组 计算着色器的执行模型分两级:一次调度(dispatch)启动若干个工作组(work group),每个工作组内又有若干调用(invocation,即线程)。 概念 说明 工作组 一次调度划分出的执行块,数量由调度参数决定 调用 工作组内的单个线程,数量由着色器内的 ...



