原标题:Unity Shader内用八叉树实现体素化阴影!
需求描述
unity发展之初主要是满足手游的需求,那时候的需求并未充分考虑开放大场景的技术实现。于是关于阴影的技术方案 shadowmap(实现近处动态物体+静态物体)+shadowmask(实现远处静态物体)+ProbesOcclusion(实现远处动态物体)对于超大场景并不适合。主要有几点原因
shadowmask 不能脱离lightmap独立 导致烘焙很慢
ProbesOcclusion 的数据封装太重,postions是只读的,是绑定场景的,无法方便实现随prefab加载实时修改
probe摆放工作量较大 特别是不规则阴影边界处
lod较多的反复在同一空间产生多分shadowmask较为浪费,(修改uv2 共用shadowmask可避免单个问题)
几乎所有大型mesh数据都因需要uv2而增大不少
鉴于此 unity技术内部 提供了一种 紧凑型阴影 适合越来越被需求的超大场景。论文在这,我这篇是具体的unity实现。
http://www.cse.chalmers.se/~uffe/compactPrecomputedVoxelizedShadows.pdfwww.cse.chalmers.se/~uffe/compactPrecomputedVoxelizedShadows.pdf
八叉树实现与合并
前面为了写这篇 写了二叉树和四叉树。基于我实现的四叉树只要稍改2处就可以实现八叉树了,因为八叉树与四叉树原理完全一致,只多了一倍子节点,改个数组的罢了。因为原理完全一样 不再重复解释,没看原理的请先看四叉树链接
设计一个偏移数组描述3d空间 8个象限 位置偏移
与四叉树相比 就是算索引偏移的时候 多一个维度判断 第三维度+4,8个子节点的数组 总偏移量刚好是0到7.
这样在C# 端调试实现的效果如下
可以看到这时候 实时阴影距离是0.这时候看到的静态对象阴影 和动态球体阴影 都是该体素话八叉树方案实现的。unity默认情况下 lightprobe 很难实现这样清晰的阴影过度除非摆放超密烘焙极慢。
shader内实现
因为短时间内没有像论文那样实现极大的压缩,所以我演示的是如何替换shadowmask与ProbesOcclusion,并非直接代替近处实时阴影(那样需要超高密度) shadowmask 与 ProbesOcclusion的shader实现 unity是放在UnityShadowLibrary.cginc文件 我们加入类似insert查找的8叉树查询法 只是数据存在在贴图_OTreeTex里 而非普通数组中。
uniformsampler2D_OTreeTex;
uniformint_OTreeWidth;
uniformint_unitsPerMeter;
uniformhalf4 _wposOffset;
int4 getTreeValue(uint index) {
returntex2D(_OTreeTex, half2(index %_OTreeWidth, index /_OTreeWidth)
/( half)max(_OTreeWidth, 1));
}
fixedshadowValue(float3 wpos)
{
uint x =wpos.x *_unitsPerMeter +0.5;
uint y =wpos.y *_unitsPerMeter +0.5;
uint z =wpos.z *_unitsPerMeter +0.5;
intindex =0;
intsize =1024*_unitsPerMeter;
[unroll( 30)]
while( 1) {
int4 node =getTreeValue(index);
intflag =node.w %10;
node.w =node.w /10;
if(node.w ==0) return1-flag;
if(size ==1) return1;
intchildIndex =0;
if(x >node.x +size /2)
{
childIndex ++;
}
if(y >node.y +size /2)
{
childIndex +=2;
}
if(z >node.z +size /2)
{
childIndex +=4;
}
index =node.w +childIndex;
size /=2;
}
return1;
}
延迟渲染的 这部分功能是在 这个函数
现在用官方的维京场景测试下效果(一立方米4x4x4个体素),在实时阴影距离