GPU PRO 4 - 4.1 Real-Time Deep Shadow Maps 笔记

张开发
2026/4/15 3:46:15 15 分钟阅读

分享文章

GPU PRO 4 - 4.1 Real-Time Deep Shadow Maps 笔记
本笔记仅为个人的理解如果有误欢迎指出Real-Time Deep Shadow Maps 实时深度阴影贴图在离线渲染中渲染半透明物体的时候会使用到一种深度阴影贴图的东西辅助渲染例如头发以及烟雾的阴影渲染本篇文章提供了一种在实时渲染中生成并应用深度阴影贴图的方法依赖DX11 的API。一张阴影贴图是光源点为视角观察不透明物体时生成的深度图但这张贴图一般是不会考虑半透明物体的深度阴影贴图中则是专门考量半透明物体对阴影影响具体影响数值由下面这个公式得出公式意义很简单d表示深度意思是到这个深度前每个透明物体都根据透明度(α)累乘1-α类似这样1-α11-α21-α31-α4得到深度阴影贴图后在渲染半透明物体时把对应的值都考虑到最终结果计算即可shading / FILTER_AREA; return float4(finalColor * clamp(shading, 0.1f, 1.0f), 1.0f);书籍中的代码的shading便是计算出来的。简单的说深度阴影贴图是一个专门保存半透明物体对阴影影响的贴图虽然说是贴图但在源代码中并没有贴图生成所有数据都保存在RWStructuredBuffer上这也是为什么要依靠DX11的原因。文章中分了5个部分1创建链表2处理片元3链接邻居4延迟阴影渲染5空间滤波1创建链表从光源角度渲染场景在渲染半透明物体的时候将每个片元的深度信息以及透明度信息保存在链表下。示意图相关代码RWStructuredBufferStartElementBufEntry StartElementBuf; RWStructuredBufferLinkedListEntryDepthAlphaNext LinkedListBufDAN; void ps_main(PS_IN input) { int counter LinkedListBufDAN.IncrementCounter(); LinkedListBufDAN[counter].depth input.pos.z 0.00002f; LinkedListBufDAN[counter].alpha Alpha; int originalVal; InterlockedExchange(StartElementBuf[((uint)input.pos.y) * Dimension (uint)input.pos.x].start, counter, originalVal); LinkedListBufDAN[counter].next originalVal; }代码很简单就是一个常见的创建链表的过程在创建链表的时候会生成一个起点表用来表示每个像素的起点。但这里有个问题半透明物体渲染的顺序一般是远的物体先渲染然后再渲染近的代码上逻辑没有问题最后生成出来的StartElementBuf起点应该是最靠近光源点的但是示意图中的HeadBuffer就反直觉了比如HeadBuffer 中 1像素的位置对应的Next是 66对应的Next 是 -1但是按照代码来看每个LinkedListBufDAN的元素next应该小于索引号或者是-1才对。这里面可能的原因是c代码中并没有对渲染半透明物体进行排序因此引出了后面【处理片元】和【链接邻居】这两个步骤对链表中的数据进行了排序调整所以示意图中的数据是排序后的样子。2处理片元在这里就对上一个步骤中生成的链表根据深度进行插入排序并且计算了每个深度对应的保存为了加速计算如果累乘之后变化值很小的话就会停止计算。对应代码// reduction float shadingBefore 1.0f; for(int i 0; i numElems; i) { float shadingCurrent shadingBefore * (1.0f - list[i].alpha); if(shadingBefore - shadingCurrent 0.001f) { shadingBefore shadingCurrent; list[i].alpha shadingCurrent; } else { numElems i; nextPoints[i - 1] -1; break; } }3链接邻居在这里每个片元的链表都会链接周围深度上接近的链表这个主要是为了后面抗锯齿做的处理为了节省内存文章中只连接了右和上的邻居链表4延迟阴影这里主要是应用DSM在半透明渲染上渲染半透明物体的时候根据当前深度查找链表获取计算到的并应用到最终颜色的渲染上相关代码void depthSearch(inout LinkedListEntryWithPrev entry, inout LinkedListEntryNeighbors entryNeighbors, float z, out float outShading) { LinkedListEntryWithPrev tempEntry; int newNum -1; // -1 means not changed if(entry.depth z) for(int i 0; i NUM_BUF_ELEMENTS; i) { if(entry.next -1) { outShading entry.shading; break; } tempEntry LinkedListBufWPRO[entry.next]; if(tempEntry.depth z) { outShading entry.shading; break; } newNum entry.next; entry tempEntry; } else for(int i 0; i NUM_BUF_ELEMENTS; i) { if(entry.prev -1) { outShading 1.0f; break; } newNum entry.prev; entry LinkedListBufWPRO[entry.prev]; if(entry.depth z) { outShading entry.shading; break; } } if(newNum ! -1) // finally lookup the neighbors if we changed entry entryNeighbors NeighborsBufRO[newNum]; } ........... shading / FILTER_AREA; return float4(finalColor * clamp(shading, 0.1f, 1.0f), 1.0f);在这里outShading则是在处理片元步骤中计算的值最终运用到finalColor上5空间滤波基于空间的抗锯齿技术由于在第三步中链接了邻居片元的链表所以文章中用了类似PCF的方式通过邻居链表遍历每个片元右边以及上边的邻居获取他们的值总和平均计算出最终的。思路其实是和PCF一样的获取周边像素并平均在这里则是通过邻居链表来获取周边像素来平均文章中还提及了可以用ESM来处理抗锯齿图则是效果左边是没有通过空间滤波直接使用当前深度的中间用的PCF抗锯齿右边则是ESM来抗锯齿参考链接[GPU Pro4] 阴影篇【屏幕空间深度阴影贴图】书籍源码图形学基础 - 着色 - 透明度混合-OIT

更多文章