最近遇到个很小但是很有意思的问题,在unity中如何从屏幕copy depth texutrue出来用?
这个问题的来源是我发现在unity在移动平台上为了得到深度图,通常需要一个单独的pass,例如我们用到的一些后处理特效需要用刀场景深度信息,我们通过把camera的flag设置为depth tex 打开,unity就会在每帧用一个单独的pass绘制深度图,这其实很浪费,我们队全场景的顶点提交了两次,增加了大量drawcall。为了说明解决这个问题的思路,我们从framebuffer说起。
0.关于framebuffer
我们知道把场景渲染出来的过程,最终就是把图像绘制到一个FrameBuffer上的过程,现代gpu支持多个framebuffer,显示器屏幕的framebuffer是默认的fbo,当然除了显示器屏幕我们还可以渲染到其他自己定义的fbo来实现渲染到贴图等等。在unity中可以放置多个多个摄像机,这些摄像机在不同情况下渲染的目的fbo也是不一样的。
我们需要知道一个fbo包括很多位面,通常至少包括两个color buffer(前和后双缓存用来绘制和展示),一个depth buffer
我们可以在不同的framebuffer之间拷贝(gpu上的互拷),包括渲染拷贝color或depth,至于gpu到cpu的拷贝我们可以把framebuffer拷贝回cpu内存,但是只限于color位面(截屏),很多底层不支持depth的cpu回拷
默认被激活的fbo就是屏幕的那个fbo,当然也可以切换激活到自定义的fbo来实现向自定义fbo的绘制。
默认的屏幕那个fbo里面的buffer不能被更改,例如gles中它不有gles提供,二是由egl提供,但是自定义的fbo就可以更改里面的buffer
1.unity的camera的fbo的定向规则:
1.对于一个单个最简单的摄像机,它的渲染的fbo就直接是屏幕的fbo
2.对于多个简单摄像机,也是按照摄像机顺序先后流到屏幕fbo
3.对于带有屏幕后处理特效的单个摄像机,渲染先流到一个自定义fbo,然后一层层的经过后处理,每层后处理都流向一个新的自定义fbo,直到最后一层后处理自动留到屏幕fbo,
4.对个多个带有屏幕后处理特效的摄像机,按照摄像机的顺序逐个渲染,对于第一个摄像机,先渲染到一个自定义fbo,然后一层层的经过后处理,每层后处理都流向一个新的自定义fbo,对于后面的摄像机,显然渲染到上一个的摄像机的最后后处理出来的那个自定义fbo,也一层层后处理下去,直到最后一个摄像机的最后一层后处理,会流向屏幕fbo
2.改变摄像机的fbo流向
有很多方法和很多理由需要改变摄像机的fbo,例如渲染到rendertexture,例如我们最开始的那个问题
1.直接创建一个rendertarget,然后赋给摄像机的target,这样这个摄像机(包括加过后处理特效)的最终流向就到了这个rendertexture,因为本质上rendertexture就是一个自定义的fbo
2.通过camera.settarget(color,depth).,可以分别设置color和depth的buffer,这在底层其实是新建了一个fbo,然后把它激活,但是你不能把color定向到屏幕,而depth定向到别处,因为color和depth的buffer必须待在一个fbo上,而屏幕的fbo中的buffer又不能被更改。
对于被设置了settarget而重定向了buffer的camera的渲染的流向规则比较奇特是这样的:
1.如果没有屏幕特效,它直接渲染到自定义的fbo上,屏幕上不可见
2.如果有屏幕特效,它直接渲染到自定义的fbo上,然后一层层的后处理,知道最后一层后处理直接流向屏幕,可以说在这种情况下和我们的预期有些不符,因为这种情况下我们的自定义的rt上没有屏幕特效,而屏幕上出现了
3.如果有多个摄像机,每个都被定向到自定义的fbo,每个都有后处理特效,那么第一个摄像机先渲染到定向的那个fbo,然后每层特效后渲染到一个新的自定义的fbo,如果不是最后一个摄像机,最后一层特效将重新流向定向的那个fbo,而对于最后一个摄像机,最终流向屏幕fbo。也就是这种情况下定向的fbo少了最后一个摄像机的特效,而屏幕出现了完整的结果
3.如何从屏幕获取depth tex?
回到这个问题。我们自然的会想到为什么unity不直接使用上一帧刚刚在屏幕上绘制好的那个framebuffer中的depth?答案肯定是完全可以,unity没有这样做可能是出于为了得到一个它可控的深度图,例如如果我们在渲染场景时自定义了一些z的操作,那么这个z的深度图可能不准,但是事实上对于不透明物体我们几乎不会干这种事。于是我打算从屏幕直接获取刚刚写过的那个depth,而强制unity不用这个浪费的单独的depth pass。
如何做到?最简单的是直接利用fbo的互拷贝的从屏幕的fbo拷贝过来到一个自定义的fbo,然而使用blit这种操作并不能稳定的获取屏幕的z,查阅一些资料也有说从屏幕fbo拷贝z在很多设备并不稳定。那么我反过来实现,我自定义两个buffer,一个color,一个depth,然后把unity的camera的渲染定向到自己的这两个buffer(为什么不只重定向depth?前面说到color和depth不能一个出于屏幕,一个出于自定义)。这样每帧绘制完透明物体后,我把depth的buffer拷贝出来就得到屏幕的z,可以给程序其他地方用了,当然最后当绘制完毕还要把color blit给屏幕,这样屏幕才看得见图像。这种做法相比多了一个从把color从自定义buffer blit到屏幕的操作,但是显然相比重新来一遍深度pass性能要高很多。
当然这里有个坑,那就是如果你的摄像机上有后处理特效,那么就不要自己最后blit color回去了,至于为什么可以参考上面2.2和2.3的规则