计算机图形学 10:光源看不见的地方怎样变暗
地面没有条纹纹理,渲染结果却出现整齐的明暗带。增大一个深度偏移参数后,条纹消失;继续增大,立方体明明贴着地面,阴影却与底边分开。前一种是错误自阴影,后一种是偏移过大造成的漏影。
本篇固定几何、相机、光源和分辨率,只改变偏移。除了三张实际图像,还用独立射线与盒相交判断地面是否真的被遮挡,分别统计“错误变暗”和“错误受光”,不把看起来干净的图直接认定为正确。
从光源再做一次可见性判断
第06篇从相机出发,为每个像素保存最近表面的深度。阴影映射把同一思路用于光源:第一遍从光源观察场景,保存最近表面的光源深度;第二遍从相机观察可见表面,将每个表面位置投回光源空间,与第一遍比较。
一个表面可能被相机看见,却被另一块几何挡住光源。因此相机深度测试和阴影比较各自回答不同问题,不能复用相机深度值直接判阴影。
设接收点在光源NDC中的深度为z,阴影图在对应位置保存D。沿用近处0、远处1的约定,若z>D,接收点比光源看见的最近表面更远,应被遮挡。加入接收点偏移b后,当前判断为:
增大b使进入阴影的条件更严格。这个符号取决于深度方向与比较方式;若换成反向深度,不能照抄。本篇b以归一化光源深度为单位,不是世界空间距离,也不是相机深度单位。
两遍使用相同的光源变换
程序复用DepthImage完成第一遍。地面和立方体都必须写入阴影图:只画遮挡物虽然也能得到某种投影阴影,却避开了本篇需要研究的接收表面自阴影。
第二遍的SurfaceBuffer复用六平面裁剪、top-left覆盖和深度测试,额外保存可见样本的局部位置、面法线和对象编号。位置随裁剪生成新顶点,并按1/w插值;当前两个投影均为正交,w恰好都为1。
可见位置先转世界坐标,再乘第一遍完全相同的光源视图投影矩阵。除w并映射到阴影图后,用最近纹素的深度比较。本次不启用PCF,不混合多个比较结果,避免过滤掩盖偏移本身的效果。
在光源视锥外的接收点采用受光策略,并另行计数。它只是当前边界约定,不表示光源真的能照到所有视锥外位置。实测地面计数中视锥外为0,因此这次对照没有依赖该策略获得好结果。
固定场景与采样尺度
先在局部坐标中放置地面y=0,范围x∈[-4,4]、z∈[-3,3]。立方体范围x、z∈[-0.5,0.5],y∈[0,1],底面恰好贴地。再用正交基将整个场景倾斜到世界空间:
局部光方向为,从表面指向光源。光源观察位置为8L,朝向原点,正交投影半高4、宽高比1、近远距离1与15。阴影图64×64,整个宽度8个世界单位,每个纹素对应0.125单位。
相机局部位置为(-5,6,8),朝向(-0.5,0,0),正交半高5、近远距离1与30,输出512×512。相机、光源位置及上方向都通过相同正交基变换,不把局部法线与世界光方向混在一起。
照明使用固定底色加直接漫反射。被判阴影时去掉直接项,保留0.1底色,因此阴影不是纯黑。黑色背景则表示没有被场景覆盖。所有颜色先在线性域计算,最后进行一次sRGB编码。
偏移为零:同一个平面也会互相遮挡
条纹来自实际CPU输出。接收点的连续投影位置一般不在阴影纹素中心。一个倾斜平面在这两个位置具有不同深度,却被拿来直接比较;一部分接收点因此误认为自己位于其他表面之后。
即使双精度计算完全没有舍入,这种位置不一致也仍存在。提高数值精度不能消除空间离散采样误差。当前程序还存在固定格光栅化和浮点舍入,但不能把图中所有条纹都归因于“浮点不够精确”。
对当前地面,光源视平面的纵坐标可写成,归一化深度随v变化的斜率为2/14。最近纹素查询的半纹素位置差最多约0.0625,因此理想平面上的深度差量级为:
这个估计说明为什么0.01值得作为第二档参数,但它不覆盖阴影轮廓、立方体边缘或任意投影,不能当作通用偏移公式。
适中偏移与过大偏移
b=0.01时,大面积条纹消失。这个结果说明当前接收表面的位置误差得到补偿,却不能证明全部边界正确。阴影图仍只有64×64,物体轮廓附近仍可能将错误纹素用于比较。
b=0.1时,立方体左侧底边与阴影之间出现亮缝。几何没有抬高,光源和相机也没有移动;改变的是比较条件。真实遮挡造成的深度差不够大时,被偏移抵消,接收点被错误判成受光。
不能只优化“条纹数量最少”。若将b设得足够大,甚至可以让所有点都受光,图像没有自阴影,却同时没有正确阴影。需要把两种错误分别统计。
独立参考不再查询阴影图
对相机可见的每个地面样本,从其局部位置沿L发出射线,与立方体轴对齐包围盒做slab相交。每个轴给出一个允许的参数区间,取三个区间的交集;存在正向交点即表示立方体遮挡光源。
当前光方向的z分量为零,所以z轴只检查样本是否落在[-0.5,0.5]内,不能除以零。这个参考不读取阴影图,不使用它的分辨率或偏移。
还可以直接推导地面阴影区域:
立方体高1,光方向横竖分量比为2,投影在负x方向延伸2单位。代码在所有可见地面样本上确认slab判断与这个解析区域一致,再用它评价阴影图。
参考图的地面灰度用于显示二值分类,不与前面图像的照明亮度逐像素比较。立方体颜色仅作位置参照,误差统计只覆盖地面,不声称独立验证了立方体各面的阴影。
两类错误的实际数量
相机可见且处于光源视锥内的地面样本共64,147个,解析真阴影2,866个、真受光61,281个。三档参数使用同一批样本。
| b | 真阴影判阴影 | 真受光判受光 | 错误阴影FP | 漏影FN |
|---|---|---|---|---|
| 0 | 2866 | 31410 | 29871 | 0 |
| 0.01 | 2866 | 61258 | 23 | 0 |
| 0.1 | 2485 | 61258 | 23 | 381 |
FP的分母是真受光61,281,FN的分母是真阴影2,866。不能用整个512×512画布作为分母稀释错误,因为背景和立方体没有参与这项地面判定。
指定无真实阴影的地面区域1<x<3、|z|<2,共11,043个样本;错误阴影从5,390降到0。接触区−0.60<x<−0.53、|z|<0.4,共82个样本;b=0.01时漏影0个,b=0.1时82个全部漏影。更远的阴影区−2.2<x<−1.8、|z|<0.4,共465个样本,在大偏移下仍全部判阴影,确认现象是接触处脱离,而非整块阴影完全消失。
b=0.01与0.1均残留23个FP,说明当前边界离散误差没有消失。红色与蓝色直接写成诊断码值,不作为需要sRGB解码的物理颜色数据。
为什么接触附近先漏影
从地面沿光线穿过盒子,进入盒子的交点不是阴影图保存的位置。阴影图保留最靠近光源的表面,对应沿当前射线离开盒子的交点。
在真阴影范围内:
当前光源正交投影的近远差为14,射线沿单位光方向移动,所以世界长度与深度差在这里线性对应。b=0.1抵消1.4单位沿光方向距离;靠近接触边时,这已经大于真实遮挡深度差。
忽略纹素离散时,求得到约−0.7522<x<−0.5的脱离带,宽约0.2522。它解释了实际图的方向与量级,不代替真实像素计数。误用进入交点会得到另一条边界,也会误判偏移的效果。
工程中可以结合斜率缩放偏移、合理的光源投影范围和过滤处理这些误差。但斜率偏移中的导数针对光源投影深度与屏幕坐标,简单的启发式不与它严格相等。本篇只验证常量接收点偏移,没有将未实现的改进写成实验结论。
复跑与练习
1 | |
输出目录须存在。程序生成三张着色PPM、三张误差PPM和一张地面参考PPM,正文PNG由sips从实际结果转换。完整计数在writing-plans/computer-graphics/evidence/10-cpu.txt。没有随机数、性能计时或GPU执行;固定场景下运行到所有样本处理完即结束。
练习一:保持同一正交光源场景,把近远距离差从14改为28。若希望偏移代表相同沿光线世界距离,b应怎样变化?
答案:正交深度差与距离除以近远差成正比,因此b应减半。直接保留原b会代表两倍世界距离。透视深度不是这个线性关系,不能直接套用。
练习二:为什么只检查FP是否减少会接受“全部受光”的错误实现?对本次样本集,这种实现的FP和FN分别是多少?
答案:所有点都受光时没有错误变暗,所以FP=0;但2,866个真阴影全部漏掉,FN=2,866。必须同时记录两类错误和对应分母,不能仅凭一个数字宣布修复。
系列导航与资料
前篇:09:法线怎样决定明暗。系列入口。下一篇:11:沿管线定位一张错误图像。
Microsoft:Common Techniques to Improve Shadow Depth Maps,Pass 1/2、Shadow Acne、Peter Panning与Slope-Scale Depth Bias小节,2026-09-20再次查阅。本文采用自己的正交场景与CPU深度格式,不照搬硬件偏移参数。实验设计推导见evidence/10-experiment-design.md,运行数据以10-cpu.txt为准。






