Chapter 62: Volumetric Rendering
体积渲染要解决的主问题,是把雾、烟、云、空气尘埃、体积光这类没有清晰表面的介质放进一帧渲染里,并让读者能够追踪它从密度输入、光照注入、沿视线积分、历史复用到最终合成的完整路径。读完本章后,读者应能定位一帧体积雾的主要资源,判断画面变亮、变灰、漏光、闪烁或拖影来自哪一个阶段,并能用固定顺序调整采样、分辨率和时间积累参数。
本章使用一个贯穿场景:夜间街道中有一盏强聚光灯穿过薄雾,近处有建筑遮挡,远处有低密度空气雾。目标画面有三层可观察结果:灯光路径形成可见光束,建筑后方光束被阴影截断,远处物体随距离逐渐降低对比度。这个场景足够小,可以用一个 camera depth、一个低分辨率 froxel volume、一个 shadow map、一个 history buffer 和一次 compositing pass 表达体积管线的关键关系。
体积渲染的难点来自两个事实。第一,介质存在于一段空间路径上,颜色来自沿视线不断累积的吸收和散射,单个表面交点概括不了它。第二,实时管线要在有限步数、低分辨率体素和历史复用中近似这个积分,因此质量问题通常表现为条带、漏光、抖动、重影和边缘光晕。工程判断的核心是把这些视觉症状还原到数据路径,再让雾浓度滑条回到吸收、散射和密度的具体含义中。
本章的体积渲染模型以 participating media 为基础。Participating media 指参与光传输的空间介质,它在光线通过时吸收能量、改变传播方向,必要时还会自己发光。离线渲染资料中,介质常用 absorption、scattering、phase function 和 transmittance 表达;PBRT 第四版的 Volume Scattering 章节用 sigma_a、sigma_s、phase 和 emission 描述一个介质点的属性。实时渲染沿用同一组物理量,再把积分过程压缩成可在一帧内执行的多 pass 近似。
下面的图只描述本章贯穿场景中一帧体积雾的资源路径。它覆盖实时引擎常见的延迟渲染或混合渲染接入方式:先生成可见表面的深度和阴影,再在低分辨率体积网格中计算雾,最后把结果合成回主颜色缓冲。
这条路径给后续小节提供统一判断坐标。密度决定雾在哪里;光照注入决定雾被哪盏灯照亮;march step 决定沿视线采样多少次;shadow map 或 ray traced shadow 决定光束是否被几何遮挡;history buffer 决定噪声如何变稳定;compositing 决定体积颜色如何盖到表面颜色上。任何体积问题都应先落到这几个资源和 pass 上。
62.1 体积渲染的物理基础与表现目标
体积渲染的物理基础可以压缩成一条工程表达:沿视线前进时,介质一边减少已有光能,一边把来自光源的能量散射进相机方向。吸收 absorption 负责让远处物体变暗或变灰;散射 scattering 负责让光束、雾亮边和云层内部亮度出现;相函数 phase function 负责决定散射方向;密度 density 负责决定介质在空间中分布多厚;透射率 transmittance 负责表达光在路径上剩余多少。
吸收系数通常记为 sigma_a,散射系数通常记为 sigma_s,二者相加得到消光系数 sigma_t。在一个均匀短段里,透射率可写成 $T = e^{-sigma_t d}$,其中 d 是这段路径长度。这个公式在 shader 中的意义很具体:当相机射线穿过一段雾时,sigma_t 越大,当前累积颜色保留得越少,后方表面颜色进入最终帧的权重越低。公式来自体积光传输的标准表达,PBRT 的 Phase Functions 章节也围绕散射方向概率描述了相函数在体积积分中的角色。
散射让体积介质在没有表面交点的位置产生可见颜色。夜间街道例子里,聚光灯照向相机附近的薄雾,如果雾只吸收,画面只会变暗;加入散射后,光源能量在雾粒子处被转向相机,观众看到一条亮的体积光束。shader 中通常把每个步长的贡献写成“当前透射率乘以局部密度、散射强度、相函数、入射光照”。这属于面向实时管线的单次散射近似,可在有限步数内提供稳定结果。
相函数解决的是方向选择问题。各向同性相函数把光均匀散到所有方向,适合调试或非常粗略的雾;Henyey-Greenstein phase function 用一个参数 g 描述前向散射或后向散射,常用于雾、云和空气尘埃的近似。g 接近正值时,顺着光方向看会更亮;g 接近负值时,背光方向增强。夜间聚光灯场景中,如果相机接近灯光方向,前向散射会让光束更强;如果相机在侧面,光束亮度下降。
密度是连接艺术控制和物理近似的核心输入。密度可以来自高度雾函数、3D noise、局部 volume texture、体积盒、天气系统或粒子模拟。对 shader 来说,密度只是采样点处的标量或颜色通道;对画面来说,它决定雾边界是否柔和、云层是否有团块、烟柱是否有层次。密度值进入消光和散射计算,因此同一个 density 同时影响遮挡强度和亮度贡献。
体积渲染的表现目标应拆成三类可观察结果。第一类是空间深度,例如远处建筑被空气透视压低对比度。第二类是光路可见,例如手电、车灯、舞台灯或阳光穿过尘埃形成光柱。第三类是介质主体,例如云、烟、爆炸、火焰和魔法能量体。三类目标共享吸收和散射模型,但资源组织差异很大:空气雾通常可用解析函数,烟和云通常依赖三维密度场,爆炸还会叠加 emission。
在贯穿场景里,体积雾的目标结果可以用三张 debug view 检查。Density view 显示哪里有雾;lighting view 显示哪些 froxel 收到了聚光灯;transmittance view 显示相机到每个深度的剩余透射率。若最终画面过灰,先看 transmittance 是否过低;若灯光路径断裂,先看 lighting view 是否被 shadow 或 froxel 分辨率截断;若雾没有层次,先看 density view 是否只有常数。
62.2 采样策略与光传播模型
实时体积渲染的采样策略本质上是在“沿每条视线积分”与“每帧预算有限”之间做取舍。Ray marching 是最直接的方法:从相机出发,沿视线按固定或自适应步长前进,在每个采样点读取密度、计算光照、更新透射率和累积颜色。它的优点是数据路径清晰,适配高度雾、局部 volume、SDF 雾盒和云;代价是步数直接乘到屏幕像素数量上,分辨率和步长会迅速转化为 GPU 时间。
一个最小 ray marching 片段可以用下面的伪代码表达。它省略多重散射和复杂阴影,只保留实时体积雾最常见的单次散射路径。
float3 accumulatedRadiance = 0.0;
float transmittance = 1.0;
for (int stepIndex = 0; stepIndex < stepCount; ++stepIndex) {
float3 worldPosition = rayOrigin + rayDirection * sampleDistance;
float density = SampleDensity(worldPosition);
float extinction = density * extinctionScale;
float scatter = density * scatteringScale;
float lightVisibility = SampleShadow(worldPosition, lightDirection);
float phase = PhaseHG(dot(rayDirection, lightDirection), anisotropy);
float segmentTransmittance = exp(-extinction * stepLength);
float3 inScattered = lightColor * lightVisibility * phase * scatter;
accumulatedRadiance += transmittance * inScattered * stepLength;
transmittance *= segmentTransmittance;
sampleDistance += stepLength;
}
这段代码展示了采样策略与视觉结果的直接关系。stepCount 和 stepLength 决定条带风险与成本;SampleDensity 决定雾的空间形状;SampleShadow 决定体积光是否被遮挡;PhaseHG 决定视角相关亮度;transmittance 决定后方表面保留多少。若画面出现明显层状带,先检查步长和 jitter;若阴影边缘漏光,先检查每个采样点的 shadow 查询和深度偏移。
Froxel grid 是实时引擎常用的结构化近似。Froxel 来自 frustum voxel,意思是在相机视锥中划分出的三维体素格。它把屏幕划成低分辨率的 x、y 网格,把深度划成若干 z slice,每个 froxel 存储该空间块的密度、光照、散射或消光结果。这样做的收益是把光照注入从全分辨率像素搬到低分辨率体积网格,多个像素可以复用同一段体积光照。
Froxel grid 的关键是 z 方向分布。线性深度切片容易把大量 slice 分给远处,近处雾的结构会粗;对数或视距相关切片能给近处更多分辨率,适合第一人称和室内灯光。夜间街道场景中,聚光灯近处光束最显眼,因此近处 froxel 需要更细;远处空气雾主要承担对比度衰减,可以使用更粗切片。
Shadowed volume 指体积采样点在接收入射光时查询阴影或遮挡。对于一个聚光灯,每个采样点可以投影到 shadow map,读取 lightVisibility;对于区域光或复杂遮挡,可以使用体积 shadow cache、ray traced shadow 或 probe visibility。这个阶段决定体积光是否尊重场景几何。若建筑背后仍然出现明亮光束,问题通常在 lightVisibility、shadow bias、shadow resolution 或体积采样点与 shadow map 坐标转换上。
Single scattering 是实时体积雾的主力模型。它只计算光源到介质点再到相机的一次散射路径。多重散射会让云内部更柔和、烟雾更亮、厚雾更均匀,但成本高,实时管线通常用 ambient term、probe lighting、precomputed multiple scattering、volumetric cache 或艺术参数近似。判断 single scattering 是否足够,应看目标介质是否薄、光束是否清晰、内部反弹是否对视觉主体产生强影响。
Temporal accumulation 通过历史帧复用减少采样噪声。体积渲染常用 blue noise 或随机 jitter 改变每帧采样位置,单帧会有噪声,多帧累积后变平滑。它的风险是历史数据在相机移动、光源移动、密度变化、遮挡变化时失效。体积历史复用应记录上一帧 view-projection、深度或体积位置,结合 clamp、variance、velocity 和 rejection 判断历史可信度。
采样策略的可复用判断顺序是:先确定介质厚度与目标视觉范围,再确定 ray marching 或 froxel grid 的主路径;然后检查每个采样点能否获得密度、光照和可见性;接着选择固定步长、抖动步长或自适应步长;最后用 temporal accumulation 把噪声换成稳定性问题,并为快速运动和遮挡变化设置 rejection 条件。
62.3 实现体积光与雾效
实现体积光与雾效时,工程目标是把“场景里有雾”和“灯光照亮雾”拆成明确资源。最小实现需要 density volume、light injection、march step、blue noise、history buffer 和 compositing。它们分别回答不同问题:雾在哪里、哪些灯照到它、相机射线如何穿过它、单帧采样如何打散条带、历史如何稳定、体积结果如何进入最终颜色。
Density volume 可以是解析函数或贴图资源。高度雾通常由 worldPosition.y、起始高度、衰减系数和全局密度计算;局部雾盒通常由 volume bounds 和 3D noise texture 采样;云和烟需要更复杂的稀疏 volume 或 clipmap。资源选择应由介质的空间变化频率决定:平滑空气雾使用解析函数,局部烟雾使用 3D texture,大范围云层使用低频 weather map 加高频 noise。
Light injection 把灯光贡献写进体积结构。对 froxel 管线来说,每盏影响体积的灯遍历覆盖的 froxel,计算距离衰减、聚光角度、shadow visibility、phase 近似和散射强度,然后把结果累加到 volume lighting texture。灯数量少时可以逐灯注入;灯数量多时需要 cluster、tile 或 light list 限制每个 froxel 的灯集合。注入阶段的成本受 froxel 数量、灯覆盖范围和 shadow 查询次数共同约束。
March step 负责沿相机视线把体积光照积分成屏幕颜色。使用 froxel grid 时,march 可以在低分辨率屏幕空间运行,按深度切片读取预先注入的体积光和消光,再输出一个低分辨率 volumetric color buffer。随后通过 bilateral upsample 按深度边缘上采样到全分辨率。若直接在全分辨率 per-pixel ray marching,则每个像素自己采样密度和光照,控制更细但成本更高。
Blue noise 适合体积渲染的单帧抖动。固定步长会在低步数下产生规则条纹;随机白噪声会把条纹变成噪声但颗粒粗;blue noise 把误差推向高频区域,更适合结合 TAA 或 temporal accumulation。实践中可用屏幕坐标和帧号索引 blue noise texture,给 ray start 或 sampleDistance 加一个小偏移。偏移幅度应受 stepLength 控制,过大会导致边界闪烁。
History buffer 存储上一帧体积结果,通常包含 volumetric radiance、transmittance 或已经合成前的体积颜色。复用时要把当前像素或当前 froxel 反投影到上一帧坐标,读取历史值,再按稳定性混合。历史可信度可由 depth difference、camera motion、density change、light change、shadow change 和局部颜色差判断。夜间街道中,如果相机横移后灯柱拖出一条旧影,说明 history rejection 对遮挡变化过宽。
Compositing 把体积结果与主场景颜色合成。常见形式是 finalColor = volumeRadiance + transmittance * sceneColor。这条式子说明体积雾对表面的影响分成两部分:雾自身散射出的亮度加到前面,后方表面按透射率保留。若先对 sceneColor 做 tone mapping 再合成线性空间体积颜色,会造成亮度和对比度异常;稳定做法是在同一线性 HDR 空间内合成,再进入后处理链路。
下面是一条适合本章贯穿场景的实现顺序。它把数据资源、pass 和可观察结果连接起来。
- 生成深度与阴影:主几何 pass 输出 camera depth,聚光灯输出 shadow map,二者为体积遮挡提供边界。
- 构建密度场:全局高度雾提供远处衰减,局部 3D noise 雾盒提供灯下的可见光束。
- 注入体积光:聚光灯把光照写入 froxel volume,shadow map 控制建筑背后的光束截断。
- 沿视线积分:march pass 输出 volumetric radiance 和 transmittance,使用 jitter 分散低步数条纹。
- 历史稳定:history pass 对体积颜色做时域混合,使用深度和光照变化限制旧帧权重。
- 合成输出:composite pass 把体积颜色与 HDR scene color 合成,并保留 debug view 入口。
这条顺序也给排查提供了入口。没有雾,先看 density;雾有形状但没有光束,检查 light injection;光束穿墙,检查 shadowed volume;条纹明显,检查 march step 和 jitter;拖影明显,检查 history rejection;边缘有光晕,检查 upsample 和 depth-aware filter。
62.4 与光线追踪相结合的近似方法
光线追踪与体积渲染结合时,实时管线通常只把昂贵的可见性或局部光照交给 ray tracing,把完整体积积分继续保留在 froxel 或 ray marching 路径中。这样可以把 RT 的精度用在最影响画面的边界上,例如软阴影、局部遮挡和 probe 更新,而把大面积、低频、连续的介质积分交给 raster/compute pass。
Ray traced shadow 是最直接的混合方式。体积采样点需要知道光源是否可见,传统路径会查询 shadow map;RT 路径可以从采样点向光源发射 shadow ray,获得更准确的遮挡,尤其适合动态几何、透明遮挡、接触阴影和远距离阴影贴图精度不足的场景。由于每个采样点都发 ray 成本很高,实时实现通常只在低分辨率 froxel、重要光源、低步数采样或稀疏候选点上使用。
Probe lighting 用低频光照缓存给体积提供环境亮度。许多体积雾并非只受单个直射光影响,室内烟雾会受到墙面反弹、霓虹灯和天空光影响。直接在每个体积点追踪多条间接光路径成本高,因此可以使用 irradiance probe、radiance cache 或 DDGI 类结构,为 froxel 采样点提供低频间接光。probe 的边界是空间分辨率低,薄光束和尖锐阴影仍然依赖直接光与 shadowed volume。
Volumetric cache 把昂贵的体积光照结果缓存到空间网格或时间序列中。云层、多层雾和大型烟雾在相邻帧变化较慢,可以把多重散射近似、局部阴影或环境光预先写入 cache,后续 marching 阶段读取 cache。缓存的风险来自失效条件:灯光移动、密度变化、相机进入体积内部、天气参数改变都会让旧数据带来亮度滞后。
Hybrid pipeline 应把 RT 结果视为体积 pass 的输入,让完整体积积分继续由 froxel 或 ray marching 路径承担。一个可控结构是:G-buffer 和 depth 定义可见表面;TLAS/BLAS 提供 ray tracing 几何;RT shadow pass 生成体积可见性纹理或稀疏样本;volumetric lighting pass 读取密度、可见性和 probe;march pass 做相机方向积分;denoise/temporal pass 稳定结果。这样每个阶段都有资源边界,调试时可以单独显示。
结合 RT 后,质量问题通常有新的来源。若体积阴影噪声明显,先看 ray budget、采样分布和 denoise 半径;若体积阴影延迟跟随物体,检查 RT visibility history;若 RT 结果与 shadow map 结果边界不一致,检查两条路径的几何、bias、透明物体处理和 light radius;若性能突增,统计每个 froxel 或每个像素发出的 shadow ray 数量。
Volumetric ReSTIR、reservoir sampling 和路径复用类技术把样本选择与时空复用带入体积光照。它们适合大量灯光、云、爆炸、复杂体积阴影等高成本场景,但工程复杂度高,需要处理体积路径的重投影、样本权重、可见性验证和偏差控制。本章只把它们作为扩展方向:当传统 froxel 加 temporal accumulation 已达到质量上限,且目标平台有足够 ray tracing 或 compute 预算时,再考虑这类方案。
混合方案的判断顺序应保持克制。先用 shadow map 或 probe 完成稳定体积管线,拿到 debug view 和性能基线;再挑选最明显的失败点,例如近处动态遮挡或复杂软阴影;然后只把这个失败点换成 ray traced shadow 或 cache;最后比较视觉收益和 GPU 时间。RT 加入后,若单独开关证明不了它解决了具体伪影,就说明这部分复杂度还没有进入可控工程状态。
62.5 性能考量与实时优化技巧
体积渲染的性能瓶颈主要来自五条路径:采样步数带来的 shader ALU 与 texture fetch,froxel volume 带来的 3D texture 带宽,shadowed volume 带来的阴影查询,temporal pass 带来的历史读写,以及 upsample/composite 带来的屏幕空间带宽。优化时要先确认瓶颈类型,再选择分辨率、步长、历史复用或质量分级策略。
低分辨率体素是最常见的第一层优化。体积效果通常比表面细节低频,可以用半分辨率、四分之一分辨率或更低的 froxel x/y 分辨率计算,再通过 depth-aware upsample 回到全分辨率。这个策略节省像素数量和体积纹理写入,但会带来边缘 halo、薄物体漏雾和高频光束模糊。upSample 时必须使用 camera depth 和法线或边缘权重限制跨物体混合。
Adaptive step 把采样预算分配给更需要的区域。均匀薄雾可以用大步长;密度变化快、靠近相机、靠近光源、接近阴影边界的位置需要小步长。自适应步长可以依据密度梯度、深度范围、光源距离、shadow change 或 opacity 提前终止。对于夜间街道的聚光灯,光束内部和建筑遮挡边界需要更多采样,远处平滑空气雾可以减少步数。
Temporal reprojection 把空间采样不足转成时间积累。它适合相机缓慢移动、介质变化平稳、光源稳定的场景。实现时应把当前体积采样点或像素反投影到上一帧,读取历史体积颜色,再用当前帧结果校正。历史权重越大,噪声越低,响应越慢;历史权重越小,响应越快,噪声越明显。可调参数应围绕这个取舍命名,例如 historyWeight、varianceClamp、motionRejection。
Checkerboard 渲染把体积计算拆到交错像素或交错 froxel 上,每帧更新一半或四分之一样本,再通过时域重建补齐。它适合大面积低频雾和云,不适合快速闪烁的局部光束。使用 checkerboard 时,blue noise、reprojection 和 rejection 必须配套,否则棋盘采样会在运动时暴露为闪烁纹理。
分级质量应把用户可感知差异映射到资源预算。低档可以降低 froxel x/y 分辨率、减少 z slice、关闭局部体积阴影;中档保留主要灯光的 shadowed volume 和 temporal accumulation;高档增加步数、启用更细 z slice、为关键光源使用更高质量阴影或 RT visibility。质量档位的边界应写成资源变化,用具体资源差异定义“低、中、高”。
性能分析时,RenderDoc、Nsight、Xcode GPU tools 或引擎内 GPU profiler 都只提供证据入口。观察目标应明确:volumetric injection pass 的耗时回答灯光和 froxel 覆盖成本;march pass 的耗时回答步数和纹理读取成本;history pass 的耗时回答读写带宽;upsample pass 的耗时回答屏幕空间过滤成本;debug marker 和 render target 尺寸回答是否存在无意全分辨率计算。
体积渲染的伪影与性能策略通常成对出现。降低分辨率会产生边缘 halo;减少步数会产生 banding;加 jitter 会产生噪声;加 temporal accumulation 会产生 ghosting;加强 history rejection 会让噪声回到画面;使用 aggressive upsample 会让薄物体周围漏雾。调参时应记录每个策略正在交换哪类成本和哪类伪影。
本章最终形成的判断顺序是:先用 debug view 确认 density、lighting、transmittance 三个中间量;再用 profiler 判断成本集中在 light injection、march、history 还是 upsample;接着按瓶颈选择分辨率、步长、阴影查询或时域复用;最后用相机运动、灯光运动和遮挡变化测试稳定性。体积渲染的工程质量取决于这条路径是否闭合。
最小自检任务
给定一个夜间街道场景:相机前方有薄雾,一盏聚光灯照向远处墙面,中间有一根柱子遮挡。最终画面出现三种问题:第一,光束穿过柱子后仍然连续发亮;第二,移动相机时光束后方拖出旧影;第三,把体积 pass 降到四分之一分辨率后,柱子边缘出现一圈亮雾。请按照本章的资源路径,定位每个问题最可能来自哪个阶段,并写出检查顺序。
答案要点
光束穿过柱子后仍然连续发亮,应先定位到 shadowed volume 或 light injection 阶段。检查聚光灯 shadow map 是否包含柱子,再检查体积采样点投影到 light space 的坐标、shadow bias、shadow resolution 和每个 froxel 的 lightVisibility。若使用 ray traced shadow,检查 TLAS/BLAS 是否包含柱子、shadow ray mask 是否覆盖该几何、历史可见性是否被旧帧复用。
相机移动时光束后方拖出旧影,应定位到 history buffer 和 temporal accumulation 阶段。检查上一帧 view-projection 是否用于正确反投影,再检查 depth difference、density change、light change 和遮挡变化是否触发 history rejection。若 historyWeight 过高,当前帧对旧体积颜色的修正不足;若 variance clamp 过宽,旧光束会在相机运动后残留。
四分之一分辨率后柱子边缘出现亮雾,应定位到低分辨率体素、upsample 和 compositing 阶段。检查低分辨率 volumetric color 是否跨越柱子深度边界混合,再检查 bilateral upsample 是否使用 camera depth 或边缘权重限制跨物体采样。最终在同一线性 HDR 空间内验证 volumeRadiance + transmittance * sceneColor,排除合成顺序造成的边缘亮度异常。
完整排查顺序是:先显示 density view,确认柱子附近雾密度没有异常;再显示 lighting view,确认柱子后方 froxel 的光照被遮挡;接着显示 transmittance view,确认视线积分没有在边界处错误累积;然后单独关闭 history,区分拖影与单帧错误;最后切换全分辨率体积 pass,验证亮边来自低分辨率上采样。
本章知识点总结
- 主问题:体积渲染把没有清晰表面的介质放入一帧渲染,并通过密度、光照、积分、历史和合成生成可见结果。
- 介质属性:吸收减少路径上的已有光能,散射把光源能量转向相机方向,二者共同决定雾、烟和云的亮度层次。
- 透射率:透射率表达相机到某个深度之间剩余的光能权重,直接影响后方表面颜色在最终帧中的保留程度。
- 相函数:相函数描述散射方向分布,Henyey-Greenstein 的
g参数可以控制前向散射和后向散射的画面强度。 - 密度场:密度场决定介质空间形状,可以来自高度函数、3D noise、局部体积盒、烟雾模拟或云层资源。
- Ray marching:Ray marching 沿视线逐步采样密度、光照和遮挡,用步数和步长换取体积积分质量。
- Froxel grid:Froxel grid 在相机视锥中复用低分辨率体积光照,适合实时引擎控制大面积体积成本。
- 阴影体积:Shadowed volume 让体积光尊重几何遮挡,问题常出在 shadow 查询、bias、坐标转换和可见性历史。
- 历史积累:Temporal accumulation 通过复用历史帧降低噪声,同时引入重影、滞后和 rejection 参数取舍。
- 实现路径:体积光实现应按 density volume、light injection、march step、blue noise、history buffer、compositing 组织资源。
- 混合追踪:光线追踪适合补强体积可见性、软阴影和局部缓存,完整积分仍可留在 froxel 或 compute 路径中。
- 性能瓶颈:体积成本集中在步数、3D texture 带宽、阴影查询、历史读写和上采样过滤。
- 质量分级:质量档位应对应 froxel 分辨率、z slice、步数、阴影质量、历史复用和关键光源策略的具体变化。
- 排查顺序:先看 density、lighting、transmittance,再定位 injection、march、history、upsample 和 composite 的责任边界。