本笔记仅为个人的理解,如果有误欢迎指出
Real-Time Deep Shadow Maps 实时深度阴影贴图
在离线渲染中渲染半透明物体的时候会使用到一种深度阴影贴图的东西辅助渲染,例如头发以及烟雾的阴影渲染,本篇文章提供了一种在实时渲染中生成并应用深度阴影贴图的方法,依赖DX11 的API。
一张阴影贴图是光源点为视角,观察不透明物体时生成的深度图,但这张贴图一般是不会考虑半透明物体的,深度阴影贴图中则是专门考量半透明物体对阴影影响,具体影响数值由下面这个公式得出
公式意义很简单,d表示深度,意思是到这个深度前每个透明物体都根据透明度(α)累乘(1-α),
类似这样(1-α1)(1-α2)(1-α3)(1-α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,创建链表:
从光源角度渲染场景,在渲染半透明物体的时候将每个片元的深度信息以及透明度信息保存在链表下。
示意图:
相关代码:
RWStructuredBuffer<StartElementBufEntry> StartElementBuf; RWStructuredBuffer<LinkedListEntryDepthAlphaNext> 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是 6,6对应的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