GPU Gems 1 · Chapter 41. Real-Time Stereograms
从深度图和重复纹理生成单图立体图:用垂直条带传播、深度水平位移和实时 z-buffer,把普通 3D 场景变成可交互更新的裸眼立体效果。
学习目标
- 能解释双目视差、随机点图案和单图立体图如何让一张二维图像携带隐藏的三维结构
- 能把深度图、重复纹理、垂直 strip、水平位移和 fragment pass 组织成一个可执行的立体图生成器
- 能说明条带数、深度因子、深度反转、过滤和 z-buffer 动画分别改变什么视觉效果与实现成本
- 能通过静态与动态实验建立正确的融合观看距离,并用连续性、位移范围和每帧 pass 成本评估结果
GPU Gems 1 第 41 章讨论的是把 GPU 的纹理和片段处理能力用来生成实时立体图。普通立体摄影用左右两个相机位置制造水平视差;随机点图案把“可匹配的纹理”与“看不见的几何”分开;单图版本则把相邻的垂直条带设计成可重复匹配的样本,让双眼通过不同的融合方式感知深度。
这里的“观看”不是医疗、驾驶或安全关键应用的视觉校准。实现部分关注数据流和工程边界:深度如何变成位移、为什么每条 strip 依赖上一条结果、怎样从 z-buffer 做动态更新,以及条带数和采样成本如何影响实时性。
1. 从双目视差到随机点图案
↡用随机点纹理编码左右视差,使双眼融合后出现隐藏三维结构的立体图 建立在一个简单事实之上:人的两只眼睛相距约 65 毫米,从略有水平位移的两个位置观察同一场景,大脑会利用两幅图的差异推断深度。经典 RDS 先生成随机点图,再复制某个区域并水平移动,最后用新的随机点填补移动产生的空隙。
如果左图给左眼、右图给右眼,移动量越大,隐藏区域看起来越深;如果左右图交换,前后关系会反转。交叉观看和使用设备观看的图像排列不同,应用必须明确输出给哪种观看方式,不应把一个“深度值正负号”当成通用答案。
随机点的价值在于它没有真实物体轮廓可以直接追踪,眼睛必须依赖相邻区域的对应关系。对算法而言,这要求条带之间有稳定的参考样本;对用户而言,这也解释了为什么过滤、重复图案和深度连续性会影响“能不能看出来”。
2. Single-image stereogram:把两张图压成一张
↡只使用一张二维图像,通过相邻垂直条带的重复匹配和水平位移来呈现隐藏深度的立体图 不需要并排的 stereo pair。它把输出图像切成若干垂直 strips,相邻条带之间的重复纹理提供匹配线索;因为单条带的位移更小,观看者可以在更接近图像平面的距离上融合。
条带数越少,每条带越宽,需要的位移通常越大,观看距离也更敏感;条带数增加,单次位移可以变小,但生成 pass 和纹理传播的组织更复杂。GPU Gems 的示例常使用 8 到 24 条带作为可调范围,具体选择取决于画布尺寸、图案周期和目标观看方式。
两种立体图的关系可以这样记:RDS 类似两条大条带组成的立体图,SIS 把画面拆成更多小条带,让匹配和融合更局部。不要只看“图像有几列”,要看相邻条带是否拥有可重复、连续的纹理身份。
3. 深度图与图案:生成器的两个输入
↡以灰度或归一化值表达每个像素相对深度、并控制输出纹理位移的输入图 是一个包含几何深度的灰度图。它可以来自手工绘制、离线渲染,也可以来自实时场景的深度缓冲。白色是否表示近处、黑色是否表示远处并没有天然唯一答案,因此生成器要提供明确的 invert depth 选项。
另一个输入是 <Term def="被重复铺设、再依据深度位移以产生相邻条带匹配关系的二维纹理">tile pattern</Term>。随机点 tile 适合演示隐藏结构;彩色或有规律的 tile 也可以生成艺术效果,但重复周期过强会让观看者直接看到图案接缝。必要时先对图案做平滑或设计足够长的周期,再让深度位移在有限范围内变化。
depthMap[x, y] → displacement[x, y]
tilePattern[u,v] → repeated reference texture
result[x, y] ← sample(previousStrip, x + displacement, y)深度图和图案的值域必须分开:深度用于位移,图案用于颜色。把深度直接乘到颜色上,或把 tile 的亮度误认为深度,都会让调参无法解释。生成器的 UI 应同时展示当前深度、位移和条带宽度,方便定位是数据还是观看的问题。
4. 条带传播:为什么每一条都读上一条
以 num_strips = N 为例,输出结果需要 N 加 1 个条带,其中第 0 条是参考条带。先用 tile pattern 绘制它,再把这一小块复制到 result map。之后启用 fragment program,对每个下一条带读取上一条已完成的 result map,依据当前深度在同一水平扫描线上寻找偏移位置,并把结果写回新的条带。
draw strip 0 with tile pattern
copy strip 0 into result map
for i in 0 ... N-1:
draw strip i + 1
depth = sample(depth map, current strip coordinate)
displacement = depth × abs(depthFactor) × stripWidth
previousUv.x = currentUv.x - stripWidth + displacement
color = sample(result map, previousUv)
write current strip这个顺序不是可随意并行的数组 map:第 i 加 1 条带依赖第 i 条带刚刚写出的颜色。历史实现通过 copy-to-texture 保持 result map 的可读版本;现代实现可以用 ping-pong texture、render pass 依赖或显式资源 barrier 表达相同的阶段边界。若在同一目标上无序读写,结果会出现条纹和帧间不稳定。
5. 深度因子、位移与观看舒适度
↡把归一化深度值转换为条带水平位移强度、控制立体图视觉深度的标量参数 通常取 0 到 1 的范围。它越大,深度图中的差异被映射成越大的水平位移,隐藏结构的立体感可能越强;但过大的位移会让相邻条带找不到对应样本,产生空洞、断层、重复纹理或难以融合的视差。
depth factor 还与条带宽度耦合。条带越宽,单步位移的绝对像素数越大;条带越窄,观看交叉点更接近图像平面,但过小的周期也会让图案变得难以分辨。一次只改一个旋钮,先固定条带数,再逐步增大深度因子,能更容易判断舒适范围。
深度反转并不是“增加深度”,而是把近远解释交换。若观看方式导致图像看起来向屏幕内凹,反转深度或调整交叉观看方向可能解决前后关系;如果条带本身已经错位,反转只会把错误翻面,不能代替坐标检查。
6. 过滤、深度台阶与动画来源
深度图的值如果没有过滤,连续的几何表面可能变成明显的 depth stairs。对实时场景来说,z-buffer 还可能受到几何边缘、量化和相机运动影响。线性过滤可以让深度变化更平滑,但也会跨越真实遮挡边界;应用要在连续性和边缘清晰度之间做取舍,而不是默认过滤越强越好。
当深度图来自静态渲染时,用户可以先找到正确的融合距离。动态版本则每帧从一个普通 3D scene 渲染得到深度缓冲,再把这个深度缓冲当作下一帧的输入。如果动画开始前观看基准还没建立,用户很难区分“场景在动”和“眼睛融合失败”。
↡记录屏幕空间每个像素深度的缓冲,可作为实时单图立体图的动态深度图来源 让算法可以消费任意实时三维场景,而不必为每个物体单独制作 depth map。注意深度缓冲的范围、near/far 解释和遮挡关系要与 depth factor 的归一化约定一致;直接把非线性设备深度当作线性距离,可能让远处的位移分配不符合预期。
7. 交互与性能:把观看条件纳入系统设计
立体图生成的片段工作量近似随输出面积和 strip 数增长。每次传播都要读取深度图、读取上一条 result map,并写入下一条区域;开启动画后还要先渲染场景并生成新的深度来源。优化的第一步不是把 depth factor 拉低,而是测量:
- 片段面积:是否只绘制有效条带,是否发生不必要的全屏拷贝?
- 纹理读取:深度图和 result map 是否有缓存友好的访问,过滤是否增加了依赖读取?
- 传播轮数:条带数是否真的提高可观看性,还是只增加了重复工作?
- 动态来源:z-buffer 生成和 stereogram 传播是否共享同一帧预算?
- 用户反馈:静态图是否先能稳定融合,再开启动画和更大的深度?
另一个工程边界是历史 API 的读回或 copy 操作。把整张结果图每一步从 GPU 读回 CPU 会摧毁实时性;应使用 GPU 端的纹理复制、ping-pong surface 或现代 render pass 依赖。为了调试,可以只读取小块统计或抓取少量帧,不要让诊断路径成为发布路径。
stereogram lab
把深度预算变成可观看的视差
这是一个可解释的示意模型:调节条带数、depth factor、filtering 和动态深度,观察 strip propagation 的连续性、生成成本和观看舒适度。它不替代真正的 Magic Eye 图像或医疗/视觉评估。
静态预览:适合先找到正确的交叉观看距离,再调高条带数或开启动画。
8. 复用清单:从深度来源到可观看输出
- 观看契约:明确交叉观看或平行观看、左右方向和深度正负解释。
- 输入契约:深度图值域、near/far 约定、tile pattern 周期和是否过滤。
- 传播契约:参考 strip、previous result map、当前输出区域和资源交换边界。
- 视差预算:条带宽度、depth factor 和最大水平位移必须同时检查。
- 动态契约:z-buffer 来自哪个相机、何时更新、深度是否需要平滑或重映射。
- 发布证据:静态帧先能稳定融合,再检查动画抖动、深度反转、空洞、帧时间和移动端布局。
小结
- 立体图通过双眼水平视差携带深度;随机点图案把可匹配纹理与隐藏几何分离,单图立体图进一步用相邻垂直条带压缩视差。
- 深度图决定位移,tile pattern 提供重复参考;每条输出 strip 读取上一条结果,根据深度水平采样,再写回下一条。
- 条带数和 depth factor 共同决定观看距离、视觉深度和片段成本;过大位移会产生空洞与匹配错误。
- 过滤可以减少深度台阶,但可能跨过遮挡边缘;反转深度改变前后方向,不会修复错误的条带坐标。
- 动态版本使用 z-buffer 作为每帧深度来源;应先建立静态观看基准,再评估动画传播和 GPU 预算。
练习
问题 1|输入关系 深度图和 tile pattern 在单图立体图中分别负责什么?为什么不能只用深度图生成有意义的颜色?
问题 2|传播依赖 为什么第 i 加 1 条带要读取第 i 条带的 result map?如果所有条带同时读取初始图案,会发生什么?
问题 3|动态深度 为什么动态 stereogram 应先用静态帧找观看距离,再切换到 z-buffer 动画?调高 depth factor 能否解决动画融合失败?
名词解释
本章出现的专业名词,用大白话再讲一遍。
- random-dot stereogram
- 用随机点纹理编码左右视差、隐藏三维结构的立体图。
- single-image stereogram
- 用一张图的相邻垂直条带匹配呈现深度的立体图。
- depth map
- 表达每个位置相对深度并控制纹理位移的输入图。
- tile pattern
- 被重复铺设并依据深度水平位移的参考纹理。
- depth factor
- 把归一化深度转换成条带位移强度的参数。
- z-buffer
- 记录屏幕空间像素深度、可作为动态深度图来源的缓冲。