在玩一些3A大作时,你可能会注意到角色走到屋檐下,影子自然地落在地面,甚至能看清墙角细微的投影。这种真实感背后,离不开阴影映射(Shadow Mapping)技术与图形渲染管线的深度集成。
什么是阴影映射
简单来说,阴影映射是一种通过从光源视角“拍照”来判断哪些区域被遮挡的技术。这张“照片”就是深度图,记录了光源到场景中最近物体的距离。之后在主摄像机渲染时,逐像素比对当前点到光源的距离与深度图中的值,就能判断它是否处于阴影中。
它怎么接入渲染流程
现代GPU的渲染管线是高度可编程的,阴影映射正是利用这一点嵌入多个阶段。第一步是在光源位置进行一次“预渲染”,只写深度不写颜色,生成一张深度纹理。这个过程类似于监控摄像头拍下整个房间的深度画面。
<!-- 伪代码示意:生成阴影贴图 -->\nPass1: \n SetRenderTargets(null, shadowDepthTexture); \n SetViewProjection(LightView, LightProj); \n DrawScene();
接下来,在常规的逐像素着色阶段,片段着色器会采样这张深度纹理,并将当前像素变换到光源空间,做一次深度比较。如果当前点比记录的深度更远,说明它被别的物体挡住了光,就应该变暗。
<!-- 片段着色器中的阴影判断伪代码 -->\nfloat4 worldPos = mul(input.Position, WorldMatrix); \nfloat4 lightSpacePos = mul(worldPos, LightVP); \nfloat depthInShadowMap = ShadowSampler.Sample(lightSpacePos.xy); \nfloat currentDepth = lightSpacePos.z / lightSpacePos.w; \nfloat shadow = currentDepth > depthInShadowMap ? 0.3 : 1.0;
硬件支持让这一切更高效
现在的显卡早就不是单纯画图的工具。它们内置了专门处理深度比较和纹理过滤的功能,比如PCF(Percentage Closer Filtering),能在硬件层面完成多次采样并平均结果,让影子边缘更柔和。这就像手机夜景模式连拍多张合成,但速度极快。
此外,GPU的多渲染目标(MRT)和异步计算能力也允许阴影贴图和其他通道并行处理。比如在VR应用中,左右眼视图可以共享同一张阴影贴图,避免重复计算,节省大量带宽。
常见问题与优化思路
阴影映射也不是万能的。有时你会看到影子边缘出现锯齿或“闪烁”,这通常是因为阴影贴图分辨率不够,或者视角移动时采样不稳定。解决办法之一是使用级联阴影映射(CSM),把近处的区域用高分辨率贴图,远处用低分辨率,合理分配资源。
还有一种叫VSM(方差阴影映射)的方法,存储深度和深度平方,利用统计学估算遮挡概率,适合需要大面积软阴影的场景,比如户外阳光下的树影婆娑。
这些技术的背后,其实是GPU不断进化的体现。从固定功能管线到完全可编程着色器,再到如今支持光线追踪的混合渲染,阴影处理越来越精细,也越来越依赖硬件与算法的协同设计。