ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

体绘制中的剪裁方案:CPU、GPU与包围盒对比与选型

体绘制中的剪裁方案:CPU、GPU与包围盒对比与选型 做体绘制开发这几年“剪裁”Clipping是我见过实现方式最多样、最容易写出性能灾难的功能之一。同样是裁掉一块区域用CPU处理体数据、在GPU着色器里做逐采样点判断、还是用包围盒控制光线步进区间三种方案的实现思路、性能表现和适用场景天差地别。没有哪一种是“标准答案”更多的是在具体项目约束下做取舍。这篇文章就围绕这三种方案把原理、实现、性能和坑都摊开来讲。1. 剪裁在体绘制中的定位不是“切一刀”那么简单1.1 体绘制剪裁的三种语义很多人一开始会把“剪裁”简单理解成“把体数据切开”。但真正做起来你会发现体绘制里的剪裁至少包含三种不同的语义。第一种是裁剪平面Clipping Plane用一个或多个无限平面把体数据切成两部分只显示其中一侧。医学影像里最常见的“剖切面”就是这个用来把骨骼或者皮肤挡住内部组织去掉。第二种是裁剪盒Clip Box / Cropping Box用一个长方体区域圈定感兴趣区只渲染盒子内部的体素。第三种是裁剪几何体Clip Geometry用任意形状的几何体球、圆柱、自定义网格去限制显示范围。这三种语义听起来差不多但实现方式完全不同。裁剪平面尤其简单本质上就是一个点积判断裁剪盒稍微复杂一点涉及六个面的组合判断裁剪几何体则可能要走带符号距离场SDF或者深度测试方案。在做方案选型之前先搞清楚业务要的是哪一种能省下大量返工时间。1.2 剪裁方案的差异根源在哪个环节动手CPU、GPU、包围盒这三种方案的本质区别不在于“用什么语言写”而在于在渲染管线的哪个环节做剪裁。CPU方案是在数据准备阶段动手脚直接把体数据改掉或者标记掉。GPU方案是在像素着色/采样阶段做判断每个采样点决定要不要参与合成。包围盒方案则是在光线起步之前动手把采样区间算好让整条光线根本不会去碰被剪掉的部分。这个区别直接决定了三种方案的上限和下限。CPU方案数据变了后续所有渲染操作都受益但响应慢GPU方案实时性好但每个帧都要重复做同样的判断渲染开销是固定的包围盒方案什么都不改纯粹靠几何求交压缩采样区间性能和裁剪区域的大小直接相关。想清楚这一点再看后面的实现细节就不会乱。1.3 一个贯穿全文的例子CT体数据的剖切浏览为了让三种方案的差异更直观我拿一个典型场景来说一份512×512×256的CT体数据医生想沿着一个倾斜平面剖开看内部的软组织和病灶。这个需求需要支持鼠标拖拽实时调整剖切角度帧率不能低于30帧。这个例子会贯穿全文。在不同的下文里我会分别用三种方案去实现它并对比最终的效果、性能和开发成本。如果你手头的项目不是医学影像而是工业无损检测、地质勘探或者流体可视化也可以把“医生拖拽”替换成“查看内部缺陷”“观察流场截面”等场景结论是通用的。2. CPU剪裁数据层面的“物理切割”慢但彻底2.1 CPU剪裁的核心思路CPU剪裁的思路是最直观的把体数据当作一个三维数组遍历每一个体素判断它是否落在裁剪区域内。落在区域外的要么直接置为背景值要么生成一个mask标记要么干脆重新拷贝出一份“瘦身”后的数据。用伪代码表示大致是这样// 裁剪盒区域 struct ClipBox { int xmin, xmax, ymin, ymax, zmin, zmax; }; void applyClipBox(uint16_t* volume, int dimX, int dimY, int dimZ, const ClipBox box) { for (int z 0; z dimZ; z) { for (int y 0; y dimY; y) { for (int x 0; x dimX; x) { int idx z * dimX * dimY y * dimX x; bool inside (x box.xmin x box.xmax y box.ymin y box.ymax z box.zmin z box.zmax); if (!inside) { volume[idx] 0; // 或者写 mask[idx] 0 } } } } }如果是裁剪平面判断就变成计算体素中心到平面的符号距离float signedDistance(const Vec3 p, const Vec3 planeNormal, float planeD) { return dot(p, planeNormal) - planeD; } // 距离小于0说明在平面内侧根据需求决定保留还是裁剪逻辑确实很简单上个培训班的人都会写。但它真正的复杂点在于“改完数据之后怎么办”。2.2 数据布局与标记删除体素没那么简单直接置0是最省事的做法但有一个隐蔽的问题体数据通常是连续存储的三维数组置0之后数据的总大小没变256MB还是256MB渲染时GPU照样要把整个纹理上传上去。也就是说置0这种方式并没有“裁剪”掉数据只是把一部分体素值变成了0。如果你的传输函数恰好给0值分配了透明度和黑色那渲染结果看起来像裁掉了但如果传输函数里0值附近有颜色灰度体数据经常会做窗宽窗位映射你就会发现“被裁掉”的区域还会残留一层雾蒙蒙的影子。这个问题我实际踩过排查了很久才发现是0值本身参与了渲染。更可靠的做法有两种。第一种是生成一个独立的mask数组1表示保留0表示丢弃。渲染时把mask当作额外的三维纹理传到GPU采样时乘一下。缺点是多占用一份内存但好处是原始数据完全不动而且mask可以被多个渲染管线共享。第二种是重新紧凑拷贝数据也就是把保留区间的体素重新排列到一个更小的三维数组里。这种做法内存占用最小渲染时采样范围也确实变小了但需要维护一个坐标映射原始坐标到紧凑坐标的线性变换而且裁剪区域的边界必须是轴对齐的盒否则映射会非常复杂。2.3 CPU剪裁真正的痛点和适用场景CPU剪裁最大的痛点不是慢而是无法实时响应交互。回到CT剖切的例子医生拖拽一下剖切面CPU方案需要重新遍历整个体数据。512×256×256一份uint16体数据大约128MB遍历一遍的时间取决于内存带宽。实测下来在普通桌面级CPU上遍历一遍就需要几百毫秒到一秒左右。这个延迟放到交互里是灾难级别的——医生拖一下鼠标一秒后才有反应是完全不可接受的。所以CPU方案在交互式应用里基本只能用于“预设裁剪”也就是裁剪区域在运行前就定好了运行期间不改变。它的真正优势在另外两个场景预处理阶段减体积把无关区域在数据加载时就去掉之后无论用GPU还是包围盒方案都能减少上传和采样开销。比如做血管分析时只关注某个局部区域先把周围组织裁掉后续所有操作都变轻了。离线渲染和批处理电影特效、科学计算可视化里的离线渲染不关心实时性用CPU做一次彻底的裁剪可以显著减少后续采样次数一台机器一晚上渲染几百帧的时候省下的时间相当可观。2.4 CPU剪裁什么时候反而是最优解有一个容易忽视的场景数据量极大GPU显存装不下。比如一个工业CT扫描出来的体数据可能超过4GB而消费级显卡显存只有8-12GB传一份完整纹理进去就已经捉襟见肘。这时候用CPU把无关区域裁掉哪怕只是裁掉50%让原本放不下的数据勉强放得下CPU哪怕花个三五秒去处理也是值得的。我在一个工业无损检测项目里就是这种用法。一个2.5GB的涡轮叶片CT数据GPU根本传不进去。后来在CPU端把检测区域外的背景裁掉数据缩到1.2GB刚好塞进显存。那次裁剪是在数据加载时做的只需要运行一次CPU慢就慢点完全不影响后续交互。所以我的结论是**CPU剪裁适合“一次裁剪、反复使用”的场景不适合“实时交互裁剪”的场景。**它的实现成本最低但响应速度是硬伤选它之前先确认你的裁剪区域是否会在运行中频繁变化。3. GPU剪裁在着色器里做判断的“动态手术刀”3.1 GPU剪裁的两种典型路线GPU剪裁的思路是把裁剪判断搬到渲染阶段用着色器代码决定每个采样点是否参与合成。最流行的体绘制管线是光线步进Ray MarchingGPU剪裁在这个框架下有两种典型的实现路线。第一种是固定循环内的条件跳过。光线进入体数据后从一个起点开始以固定步长向前采样每次采样前先判断当前点是否在裁剪区域内不在则直接跳过不参与合成。第二种是直接修改采样起点和终点。在光线进入循环之前就算好裁剪区域与光线的交叠区间让采样直接从这个区间开始到区间结束就停止。严格来说第二种已经是包围盒方案的雏形了但很多GPU实现里是把两者混着用的先算裁剪盒的采样区间再在区间内对裁剪平面做逐点判断。这里先讲第一种——在循环体内部做判断的方式这也是最简单、最通用、最容易写坏的一种。3.2 逐采样点裁剪ray marching 里的 continue 与 discard在GLSL里实现逐采样点裁剪代码大概长这样uniform vec3 clipPlaneNormal; uniform float clipPlaneOffset; uniform float stepSize; uniform sampler3D volumeTex; uniform mat4 volumeTransform; // 体积模型矩阵 vec4 rayMarch(vec3 rayOrigin, vec3 rayDir, float tEntry, float tExit) { vec4 colorAccum vec4(0.0); float alphaAccum 0.0; float t tEntry; for (int i 0; i maxSamples; i) { if (t tExit) break; vec3 pos rayOrigin rayDir * t; float dist dot(pos, clipPlaneNormal) - clipPlaneOffset; if (dist 0.0) { // 在裁剪侧跳过 t stepSize; continue; } vec3 uvw (volumeTransform * vec4(pos, 1.0)).xyz; vec4 sampleColor texture(volumeTex, uvw); // 前置合成front-to-back compositing colorAccum.rgb (1.0 - alphaAccum) * sampleColor.a * sampleColor.rgb; alphaAccum (1.0 - alphaAccum) * sampleColor.a; if (alphaAccum 0.99) break; // 不透明了直接终止 t stepSize; } colorAccum.a alphaAccum; return colorAccum; }这套代码用continue跳过了裁剪侧的采样点逻辑很简单。但要注意几个细节continue跳过的采样点仍然消耗了循环迭代。GPU的循环没有传统CPU那种“跳过很快”的概念迭代次数是固定的预算跳过只是不做纹理采样和合成循环开销还是在。分支发散问题。如果一条光线在裁剪区外、另一条光线在裁剪区内GPU的SIMT架构会让两组线程走不同的分支路径导致效率下降。不过现代GPU对这种基于uniform变量的分支处理得比较好只要裁剪平面的参数是uniform而不是每像素都不同发散损失就比较小。不要随意用discard。上面这段代码用的是continue不是discard。discard会直接丢弃整个fragment在ray marching循环里这意味着循环还没跑完就终止了整个像素都不写入颜色缓冲最终画面会出现空洞。只有在裁剪区域完全不需要渲染时才应该考虑discard比如裁剪外区域根本不需要显示背景的场景。3.3 边界质量为什么裁剪面经常“脏”用GPU逐点裁剪最大的问题在边界处——裁出来的截面经常带锯齿、黑边或者颜色过渡不自然。原因在于体绘制的空间连续性和采样离散性之间的矛盾。裁剪判断用的是采样点的空间位置步长一般设置在体素尺寸的0.5到1.0倍之间判断结果是一个非0即1的硬开关。在裁剪面的两侧采样点是离散的于是渲染出来的边界就会呈现锯齿状像低分辨率图放大了一样。解决办法分为两类。一类是**“软裁剪”**在裁剪面附近加一个过渡带让透明度或者颜色权重在过渡带内平滑衰减。这个思路的做法类似float softness 2.0 * stepSize; float clipFactor smoothstep(-softness, 0.0, dist); vec4 sampleColor texture(volumeTex, uvw); sampleColor.a * clipFactor; // 裁剪面附近透明度渐变softness越大过渡越平缓但也会让真正的边界位置变得模糊。这个参数需要根据体数据的分辨率和步长来调我一般习惯设成2到4个步长。另一类是**“贴面增强”**在裁剪面上额外渲染一个半透明的彩色面片模拟现实中“切开的表面”应该有组织纹理的效果。这个做法需要拿到裁剪面在三维空间的几何形状对裁切面重新采样体数据计算一个等值面颜色叠加上去。效果很好但实现复杂度跳了一个量级一般只有在医疗精准手术规划这种高要求场景才值得做。3.4 GPU裁剪的隐藏成本GPU裁剪看起来“实时、不占内存”很多人就觉得这是最优方案了。实际做起来你会发现它有几个隐藏成本。首先是每个采样点都在做额外计算。一条光线平均采200-400个点每个点都做一次点积和比较。如果裁剪区域很大这些判断大部分都在裁剪区内做了也白做。帧率的损失和裁剪判断本身的计算量成正比单帧可能只慢几毫秒但如果渲染分辨率是1080p每个像素都要跑一遍ray march总体GPU开销就上来了。其次是无法缩短光线步进的采样次数。无论裁剪区域多小光线进来的那条路径上采样次数没有减少除非你同时做了区间裁剪。这意味着即使只渲染“一片薄薄的截面”GPU仍然要按最长路径去遍历体数据时间和空间效率都不理想。还有显存带宽压力裁剪之外的部分虽然没显示但GPU端仍然持有完整的三维纹理数据这对显存带宽和显存容量没有半点节省。如果你的数据大到要压缩、要流式加载才能放进显存GPU裁剪方案就帮不上任何忙了。4. 包围盒方案通过 ray-box 求交缩小采样区间4.1 不要再把包围盒和裁剪区域混为一谈“包围盒”这个词在体绘制里有点歧义。在渲染领域包围盒常常指代一个粗略的、把整个物体包起来的立方体用于加速遮挡剔除和射线求交。但本文说的包围盒方案不一样它把裁剪区域本身定义为一个盒形区间然后通过光线与这个盒体的求交计算直接框定采样区间的起点和终点。所以更准确地说包围盒方案是**“基于裁剪盒的采样区间裁剪”**。它的基本操作是给定一条光线计算这条光线穿过裁剪盒的进入参数和退出参数然后把这个区间和数据包围盒的进入/退出区间取交集最终得到真正需要采样的范围。核心好处是裁剪区域越小采样次数越少。一条光线如果完全不穿过裁剪盒那么连一次体素采样都不用做直接返回背景色。这种优化是GPU逐点裁剪做不到的。4.2 slab 求交与采样区间的确定射线与轴对齐盒的求交有一套经典算法叫slab method。原理是把三维盒体拆成三组平行平面slab每组平面求一个进入/退出区间最后对三组区间求交集。GLSL实现大概长这样struct Ray { vec3 origin; vec3 dir; }; // 返回进入、退出参数如果无交则 tEntry tExit void intersectBox(Ray r, vec3 boxMin, vec3 boxMax, out float tEntry, out float tExit) { vec3 invDir 1.0 / r.dir; vec3 t0 (boxMin - r.origin) * invDir; vec3 t1 (boxMax - r.origin) * invDir; vec3 tMin min(t0, t1); vec3 tMax max(t0, t1); tEntry max(max(tMin.x, tMin.y), tMin.z); tExit min(min(tMax.x, tMax.y), tMax.z); }之后在ray march主函数里确定实际采样范围// 体数据包围盒 float tDataEntry, tDataExit; intersectBox(ray, dataBoxMin, dataBoxMax, tDataEntry, tDataExit); // 裁剪盒包围盒 float tClipEntry, tClipExit; intersectBox(ray, clipBoxMin, clipBoxMax, tClipEntry, tClipExit); // 只取交叠区间 float tStart max(tDataEntry, tClipEntry); float tEnd min(tDataExit, tClipExit); if (tStart tEnd) { // 这条光线完全不在裁剪区域内直接返回背景 return vec4(0.0); }拿到tStart和tEnd之后光线步进就在这个区间内进行。与第3节的实现相比采样点少了很多而且裁剪盒之外的区域根本不进入采样循环。需要注意slab method在光线方向分量为0时会出问题因为1/0会产生无穷大导致区间计算错误。处理方式是对接近0的分量用一个很大的数替代或者用IEEE浮点数处理时加一个epsilonvec3 invDir 1.0 / max(abs(r.dir), vec3(1e-6)) * sign(r.dir);4.3 包围盒方案的进阶玩法OBB 与嵌套盒轴对齐包围盒AABB实现简单但局限性也很明显裁剪区域必须是轴对齐的无法做倾斜裁剪。做CT剖切时如果剖切面是倾斜的纯AABB方案就力不从心了。一种常见的扩展是有向包围盒OBB。做法是先把光线变换到裁剪盒的局部坐标系再做slab求交。这样裁剪盒可以是一个任意朝向的长方体。只需要传一个旋转矩阵在进入函数之前把ray的origin和dir乘以矩阵的逆bool intersectOBB(Ray r, mat4 inverseBoxMatrix, vec3 boxMin, vec3 boxMax, out float tEntry, out float tExit) { Ray localRay; localRay.origin (inverseBoxMatrix * vec4(r.origin, 1.0)).xyz; localRay.dir (inverseBoxMatrix * vec4(r.dir, 0.0)).xyz; // 然后做普通AABB求交 }OBB方案可以覆盖很多原本需要GPU逐点裁剪的场景比如斜着切一刀。而且它保持了CPU端零开销、GPU端采样次数随裁剪区域收缩的特性是我个人比较推荐的一个折中路线。再进一步可以支持嵌套裁剪盒——即一组有序的盒体光线在每对盒体之间进入/离开时决定是否显示。这可以用来实现“隧道效果”或者穿孔效果。实现上只需要把区间求交扩展为多段循环处理每个盒体的进入退出区间。4.4 数值稳定性与边界处理包围盒方案虽然性能好但坑也不少最典型的是数值稳定性问题。slab method在光线平行于某个轴时会得到inf或nan。即使加了epsilon处理在盒体边界上也可能因为浮点误差导致tEntry比tExit大一点点产生一条本应有效的光线被误判为“完全在外”。我在写的时候习惯在最后加一个小阈值if (tStart tEnd - 1e-4) { return vec4(0.0); // 视为无交 }还有一个容易被忽略的问题裁剪盒和渲染背景的遮挡关系。如果背景是透明的、需要透出后面物体那么“完全在裁剪区外”的光线直接返回背景色是对的但如果背景是不透明的深色或者场景里有其他几何体你需要额外考虑背景的深度值。否则裁剪区域外的区域会显示出不该出现的背景颜色。5. 三种方案实测对比与选型建议5.1 同一需求下三种方案的渲染效果与帧率拿CT剖切这个场景我在同一个工程里分别实现了三种方案用一台RTX 3070测试数据是512×512×256的uint16渲染分辨率1280×720光线步长设为体素尺寸的0.8倍。结果整理如下对比维度CPU剪裁GPU逐点剪裁包围盒方案首次裁剪响应延迟700ms-1.2s实时单帧内生效实时单帧内生效拖拽剖切时帧率卡顿明显50-60 FPS60 FPS以上裁剪区域外开销无数据已变仍存在极小提前退出每帧额外GPU开销无每个采样点多一次判断每光线多一次box求交实现复杂度低中中内存占用可能需要额外mask或紧凑拷贝无额外占用无额外占用边界锯齿无但需处理传输函数需要软裁剪处理需要软裁剪处理实测下来包围盒方案在倾斜剖切场景的帧率最高因为光线在裁剪区域外的部分完全不需要采样。GPU逐点裁剪的帧率略低但胜在裁剪区域可以是任意形状不只是盒体灵活性最强。CPU方案在交互上完全不可用只能用于预设裁剪。5.2 从四个维度看差异响应速度、渲染开销、实现成本、内存响应速度。CPU方案是秒级响应GPU逐点和包围盒方案是单帧响应。如果你的业务需要用户拖拽滑块、旋转剖切面CPU方案直接排除。渲染开销。CPU方案在数据修改后渲染开销最小因为它根本不需要再处理被裁剪掉的体素。GPU逐点裁剪的渲染开销是固定的采样点一个不少只是多了一点比较运算性能不会随裁剪区域缩小而改善。包围盒方案的渲染开销和裁剪区域大小强相关裁剪区域越小越快。实现成本。CPU方案实现成本最低一个循环就搞定了。GPU逐点裁剪需要你熟悉ray marching和着色器语法且要处理边界锯齿。包围盒方案需要实现ray-box求交还要考虑OBB变换数学功底要求更高但一旦写好了后续调整裁剪区域非常方便。内存占用。CPU方案如果置0不额外占内存但如果用mask或紧凑拷贝就会增加。GPU逐点和包围盒方案都不改动数据、不增加内存它们更适合显存紧张的情况。5.3 我的选型经验根据跑过的项目我总结出几条比较务实的选型经验如果裁剪区域运行前就固定且数据量不大用CPU方案最简单后续渲染性能也最好。如果需要实时交互但裁剪区域的形状是简单平面或盒体优先选包围盒方案性能上限最高。如果需要实时交互且裁剪区域形状复杂球体、圆柱、自定义网格只能选GPU逐点裁剪。如果数据量极大、显存不够先用CPU把无关区域裁掉再用GPU或者包围盒方案做交互裁剪。大部分实际项目最终会演化成组合方案CPU做一次大范围的预处理包围盒控制采样区间GPU在包围盒内做精细的逐点判断。没有一种方案能通吃所有场景所谓“最优”永远是针对你手头的数据规模、交互要求、开发周期和硬件条件来说的。6. 真实工程中的组合方案与踩坑记录6.1 先用包围盒缩小范围再用GPU做精细裁剪在我参与的一个血管介入手术规划系统里医生需要实时查看血管与周围组织的关系同时能切掉骨骼和肌肉来观察血管走形。这个需求单靠任何一种纯方案都别扭。最终我采用的是组合方案血管的感兴趣区域用一个立方体包围盒框起来ray marching只在包围盒内进行同时在包围盒内部叠加两个GPU裁剪平面一个用来剖切显示层面一个用来遮挡骨骼。这样包围盒保证了GPU采样次数随感兴趣区域收缩而GPU裁剪平面保证了裁剪形状的灵活性。实测在RTX 3060上1024×768渲染分辨率帧率稳定在50 FPS以上拖拽剖切面几乎无感知延迟。这套组合方案的代码核心就是把第3节和第4节已经给出的片段拼起来先算包围盒求交得到tStart和tEnd在采样循环里再对裁剪平面做判断。整体逻辑并不复杂但两种方案的收益都能拿到。6.2 踩坑记录一CPU剪裁后传输函数“跑偏”有一次我在预处理阶段用CPU把一个脑部CT数据的颅骨部分裁掉目的是让软组织的窗宽窗位更容易调。裁完上传到GPU之后我随便拉了一下窗位发现整个渲染画面都发灰、发暗细节反而看不清楚了。排查了一阵才发现问题不在数据裁剪本身而在传输函数。原来裁剪前体数据的值域范围包含了颅骨的高值区域比如1500-3000HU传输函数的窗宽是按这个完整范围去映射的。裁剪后颅骨没了所有值集中在软组织区间比如-50~200HU但传输函数还是按原来1500的范围去查表导致一大片软组织全部压缩在传输函数的低亮度区画面自然发灰。解决办法很简单裁剪之后重新统计体数据的实际值域更新传输函数的映射参数。但很容易被忽略尤其当你的传输函数是预置的或者自动计算的。6.3 踩坑记录二GPU裁剪边界处的一片黑还有一次是GPU裁剪裁完发现裁剪平面位置出现一条细黑线把剖开的两半隔开了。这条黑线在画面里非常扎眼怎么看怎么不对劲。Root cause是采样点间距和裁剪平面的交点不匹配。ray marching的采样点是离散的落在裁剪平面两侧的点之间的空间步长可能刚好跨越了一个很大的体素差异区域于是颜色合成时这个区域生成了接近黑色的不透明值。解决方式是给裁剪边界加一个软化带。用第3.3节说的smoothstep对透明度做渐变之后黑线消失了边界过渡自然多了。后来我把这个软化带做成可配置参数在不同分辨率的数据集上观察效果再调整。这里的关键教训是硬边界的裁剪一定要配软化否则你大概率会在某个角度下看到一条墨线。6.4 踩坑记录三包围盒退出条件的阈值问题包围盒方案我也踩过坑。有一次实现OBB裁剪时某些光线在特定角度下会出现“整条光线都渲染不出来”的情况折腾了很久。最后定位到是slab method里的一个浮点精度问题。当光线方向某个分量非常接近0时invDir会变得极大导致t0、t1的计算结果差了好几个数量级进入区间被错误地算成负数或者NaN。解决方式是三点第一对_dir分量取绝对值小于1e-6直接当作0处理并赋一个极限值避除零第二在最终区间判定时加一个1e-4的容忍阈值第三把求交函数做成可复用的工具函数写单元测试覆盖平行光线、掠射光线等边界情况。不要觉得这是小题大做体绘制里大量难以复现的“随机白点”“随机黑块”都是这类边界问题引起的。体绘制剪裁这三种方案本质上是同一件事在不同层级上的实现CPU改数据、GPU改采样判断、包围盒改光线区间。理解了这一点很多选型和调优问题都会变得清晰。就我个人的看法能把组合方案用好比单纯争论“哪个最好”对项目的价值大得多。如果你的场景也是需要实时剖切浏览的体数据建议优先花时间把包围盒求交和GPU软化裁剪这两个基础模块吃透它们能覆盖掉绝大多数实际需求。
返回列表