每日编程实践: SSAO - Screen Space Ambient Occlusion 屏幕空间环境光遮蔽
SSAO - Screen Space Ambient Occlusion 屏幕空间环境光遮蔽今天实现了 SSAO(Screen Space Ambient Occlusion)——现代实时渲染管线中最重要的后处理技术之一。从 Crysis(2007)开始被引入游戏工业,此后成为 UE、Unity 等引擎的标配功能,也是《赛博朋克 2077》、《荒野大镖客2》等 AAA 游戏中真实感的重要来源。 本项目在纯 CPU + 软光栅化器上从零实现完整的 SSAO 管线,不依赖 GPU 或任何图形 API,完整掌控每个数学细节。 为什么需要 SSAO?传统环境光的问题实时渲染中,处理全局光照(Global Illumination)在计算上开销极大。最简单的近似是用一个常数环境光项代替所有间接光照: 1ambient = albedo × k_a × L_ambient 这意味着场景中所有点受到同样强度的环境光,结果是球体底部贴地处和空旷的墙面获得完全相同的环境光强度——不真实,缺乏层次感。 现实中,环境光不是均匀到达的: 狭窄缝隙里的光线被周围几何体阻挡 凹陷区域(角落、裂缝)...
每日编程实践: Cascaded Shadow Maps CSM 级联阴影
Cascaded Shadow Maps — CSM 级联阴影昨天实现了 PCSS 软阴影,解决了”阴影软化”的问题。今天面对另一个经典难题:大场景下的阴影精度。 问题是这样的:用一张 Shadow Map 覆盖整个场景,近处的阴影分辨率会严重不足——一个 Shadow Map 的纹素可能对应几十平方米的地面,阴影边缘会出现巨大的锯齿块;但如果把 Shadow Map 只覆盖近处,远处又会完全没有阴影。 CSM(Cascaded Shadow Maps,级联阴影映射) 的解法是:把视锥体按深度切成若干段,每段用一张独立的 Shadow Map,近处精细、远处粗略——跟 Mipmap 的思路如出一辙。 今天完整实现了 4 级 CSM,包含对数线性级联分割、每级独立正交投影和 PCF 软阴影。 效果展示主渲染(含 CSM 阴影) 近景红球、中景紫球橙球、远景山丘都投射出软化阴影,地面阴影由近到远精度自然过渡。 级联区域可视化 颜色叠层:红色 = Cascade 0(最近) | 绿色 = Cascade 1 | 蓝色 = Cascade 2 | 黄色 &...
每日编程实践: TAA 时域抗锯齿渲染器
TAA 时域抗锯齿渲染器打开任何一款现代游戏,进入设置找到抗锯齿选项,几乎一定有 TAA。UE4、UE5、Unity HDRP、寒霜引擎、虚幻竞技场……这项技术几乎无处不在。 但 TAA 背后隐藏着一个精妙的思想:不在当前帧多次采样,而是把采样分散到时间轴上——每帧只用 1 个采样点,但通过累积历史帧信息,等效获得几十次超采样的质量。 今天从零实现这套机制,包括它的两个核心难题:如何选择每帧的采样偏移(Jitter),以及如何防止历史帧信息带来的鬼影(Ghosting)。 四图对比 左上:No-AA(明显锯齿)| 右上:SSAA 4x(参考质量)左下:TAA + Variance Clipping(无鬼影)| 右下:TAA 无 Variance Clipping(残影可见) 第一章:为什么会有锯齿?理解 TAA 之前,先理解锯齿的成因。 像素是离散的小方块,而几何边缘是连续的斜线或曲线。当一条斜线穿过像素时,这个像素要么完全被覆盖(点亮),要么完全没有覆盖(不点亮)——没有中间状态。这种二值化就产生了锯齿(Aliasing)。 香农采样定理给出了理论解释:要正确还原一个频...
每日编程实践 #35: PCSS 软阴影渲染器
PCSS 软阴影渲染器现实世界中,阴影从来不是非黑即白的——靠近物体的地方阴影清晰,远离的地方阴影边缘模糊渐变。这种效果叫做半影(Penumbra),是面光源(有面积的光,区别于理想点光源)的自然产物。 今天实现了三种阴影算法,从最简单的硬阴影,逐步演进到 UE5/Unity HDRP 都在用的 PCSS(Percentage Closer Soft Shadows)——一种能根据遮挡距离自适应调整半影大小的软阴影算法。 三种算法对比 从左到右:Hard Shadow(硬阴影) | PCF(固定软化) | PCSS(自适应软阴影) 注意 PCSS 的阴影:球体与地面接触处的阴影清晰,悬空球体的阴影边缘模糊扩散——这才是物理正确的半影效果。 第一章:为什么会有半影?用一张图来理解: 12345678 ○○○○○ ← 面光源(有面积) ||| A──B──C ← 遮挡物(球体) /|\ / | \ / | \阴影 半影 阴影(全暗)(渐变)(全亮) 本影(Umbra):被遮...
每日编程实践: SPH Fluid Simulation 流体模拟
SPH Fluid Simulation — 流体模拟水往低处流——这句话人人都知道,但计算机怎么”理解”水? 让它记住每个水分子的位置?不现实,一杯水有 $10^{25}$ 个分子。用格子把空间切开记录密度?可以,但不够灵活。今天用的是第三种方法:把水离散成一堆会动的粒子,粒子之间互相感知、互相推挤,集体涌现出流体行为。 这就是 SPH(Smoothed Particle Hydrodynamics,平滑粒子流体动力学),一种用于电影水特效、工程爆炸模拟、宇宙演化计算的经典算法。 最终效果 颜色映射速度:🔵 蓝色 = 静止,🟡 黄色 = 中速运动,🔴 红色 = 高速冲击。 600 个粒子从左下角的方块初始状态,在重力作用下铺展、碰壁、最终沉底——整个过程模拟了约 2 秒的物理时间。 模拟序列——流体如何从方块变成摊开的液体 t=0s(初始方块) t=0.5s(开始扩散) t=1.0s(持续铺展) t=1.5s(接近稳定) t=2.0s(最终状态) 第一章:为什么是”...
每日编程实践: Cloth Simulation 布料模拟
Cloth Simulation — 布料模拟一件衬衫被风吹起的样子,一块丝绸落在桌面上的纹理,游戏里的旗帜随风飘动——这些看似简单的视觉效果,背后隐藏着一个有趣的物理问题:如何用计算机模拟柔软的织物? 今天实现了经典的质点弹簧布料模拟,这是游戏引擎和影视特效中处理布料最常见的基础方案。 最终效果主渲染图(含风力的最终状态) 红色布料从水平展开的初始状态,在重力拉扯下垂落,包裹住下方的球体,随后被侧风吹起一角。Phong 着色区分正面(深红)和背面(暗红),形成明显的布料折叠感。 物理演化序列(0 → 150 → 300 → 600 帧) 从左上到右下:布料从展开状态,逐渐在重力作用下垂落,绕过球体弯曲,最终稳定在平衡态。 第一章:为什么布料模拟难?布料的本质是大量纤维交织的网。真正从纤维层面模拟是不现实的——一件 T 恤大概有几十亿根纤维。 工程上的解法是离散化:把布料简化成有限个质点,再用弹簧连接它们,让弹簧产生的弹力近似真实布料的弹性。这就是 Mass-Spring System(质点弹簧系统)。 直觉模型:想象一个渔网。每个网结是一个质点(有质量,能运动),绳子是弹簧...
Marching Cubes:3D 形状是怎么从数学公式里"长出来"的?(C++ 从零实现)
前言:一个奇怪的问题如果有人给你一个数学公式,比如: $$f(x, y, z) = \sqrt{x^2 + y^2 + z^2} - 1$$ 你能在 3D 空间里把它”画”出来吗? 答案是可以的。$f(x,y,z) = 0$ 的所有点,恰好就是一个半径为 1 的球面。这种”由方程定义的曲面”叫做等值面(Isosurface)。 问题是:计算机显卡只会画三角形,不会直接画”方程”。我们需要一个算法,把等值面转换成三角形。 这就是 Marching Cubes(行进立方体)的工作。 今天我们从零实现这个算法,得到下面这张图: 这是 5 个球用”平滑融合”连接在一起的有机形状,由 15,196 个三角形组成,生成耗时 0.078 秒。 一、等值面是什么?用 SDF 定义三维形状隐式表示 vs 显式表示3D 形状有两种定义方式: 显式:直接列出表面上的点(三角网格、点云)。隐式:用一个函数 $f(x,y,z)$ 描述空间中每个点和形状的关系。 隐式表示里最常用的是 SDF(Signed Distance Field,有符号距离场): $f(x,y,z) &...
次表面散射(SSS)渲染:光是怎么穿过皮肤和蜡烛的?(C++ 从零实现)
前言:一个有趣的现象拿一根蜡烛,把手指挡在烛火和你眼睛之间——手指会变成橙红色,你能看见指骨的轮廓。 同样的事情对一块金属做,金属只是变暗,没有颜色变化。 这两种东西的区别是什么? 答案是:次表面散射(Subsurface Scattering,SSS)。 金属:光打到表面就反射回来,不进入内部 皮肤/蜡烛/玉石:光穿入材质内部,在里面散射若干次,然后从不同位置的表面射出 今天我们从零实现一个 SSS 渲染器,渲染出下面这张图: 左边的橙色球是蜡烛材质,中间偏左是皮肤,小球是玉石,右边银色是金属对比。注意背光(来自后方的蓝光)穿透了蜡烛和皮肤,而金属没有任何穿透效果。 第一步:理解光和材质的关系在讲代码之前,我们先搞清楚两类材质的根本区别: 不透明材质(金属、木头)123光 → 打到表面 → 反射/散射 → 到达眼睛 ↑ 光不进入内部 光照模型:Phong / PBR,只考虑表面那一层。 半透明材质(皮肤、蜡烛、玉石、牛奶)123光 → 穿入表面 → 在内部散射N次 → 从其他位置的表面射出 → 到...
Ray Marching 体积云渲染:从物理原理到代码实现(C++)
前言普通的 3D 渲染处理的是表面——三角形、球面、平面——光线打到表面就结束了。 但云、雾、火焰、体积光……这些东西没有明确的表面。光线穿进去,在里面反复散射,最终才到达眼睛。这类渲染统称体积渲染(Volume Rendering)。 今天我们用 C++ 从零实现一个体积云渲染器,效果如下: 可以看到云层有明显的厚薄变化、云底阴影、受光面亮背光面暗的立体感。 整个项目涉及 4 个核心技术,我们逐一讲清楚: Ray Marching — 如何穿越体积 Perlin FBM — 如何生成云的形状 Beer-Lambert — 如何模拟光被吸收 Henyey-Greenstein 相位函数 — 如何模拟散射方向 一、Ray Marching:像走路一样穿越体积原理表面渲染里,光线打到三角形就有精确的交点。但体积没有”表面”,怎么求交? Ray Marching 的答案很简单:沿光线方向一步一步走,每走一步就采样一次。 123起点 ──→ ● ● ● ● ● ● ● → 终点 ↑ ↑ ↑ ↑ ↑ ↑ 每步采样一次密度场 伪代码: 123456t ...
每日编程实践: CPU粒子系统 - 火焰与烟雾模拟
CPU粒子系统 - 火焰与烟雾模拟粒子系统是游戏引擎和实时渲染的核心技术之一。今天从零实现一个完整的 CPU 粒子系统,模拟真实的火焰、烟雾和飞散火星效果。 效果预览火焰稳定状态(3秒预热后) 时序演变(四帧对比) 从左到右:粒子系统从少到多,逐渐形成稳定火焰形态 项目目标实现一个功能完整的 CPU 粒子系统,具有: 三种粒子类型:火焰、烟雾、火星 物理模拟:浮力、重力、湍流、阻尼 软粒子渲染:高斯衰减 + 加法/Alpha 混合 颜色生命周期:从热核心到消散的完整颜色渐变 系统架构粒子结构每个粒子持有以下状态: 123456789101112struct Particle { Vec2 pos; // 位置 Vec2 vel; // 速度 float life; // 当前生命值 [0,1] float maxLife; // 生命持续时间(秒) float size; // 当前大小(像素) float maxSize; // 最大尺寸 float tur...















